Tesseract / studio /src /components /WorkingHints.jsx
rkbosamia's picture
hints modification
0e3cfa9
Raw
History Blame Contribute Delete
5.52 kB
/**
* WorkingHints β€” soft, rotating product-feature tips shown while the agent
* is streaming (and there's no PacingPill already occupying the slot).
*
* Why: the user is otherwise just watching tokens land. A subtle hint every
* few seconds surfaces features they might not know about β€” @mentions,
* snapshots, the DB tab, slash commands. Strictly informational. Hint
* pool comes from real shipped features; if a feature changes name, this
* is the one place to update.
*
* Behavior:
* - Visible whenever `isStreaming` is true β€” INCLUDING during a pacing
* wait, since that's when the user has the most idle time staring at the
* screen. Stacks below the PacingPill; the two are complementary.
* - First hint shown immediately on streaming start (no awkward blank).
* - Rotates every ROTATE_MS while still streaming.
* - Pre-shuffled order β€” cycles through ALL hints before repeating.
*/
import { useEffect, useRef, useState } from 'react'
import useAppStore from '../store/appStore'
const ROTATE_MS = 7_500
// Real, shipped features only. Keep each tip ≀ 80 chars so the row fits on
// a narrow chat column without truncation.
const HINTS = [
{ icon: '@', text: 'Type @ to pin a file β€” the agent always reads it on that turn.' },
{ icon: '⌘K', text: '⌘K opens the command palette: run, restart, switch session, find files.' },
{ icon: '/', text: 'Slash commands: /run, /stop, /restart, /reload, /snapshot, /clear-logs.' },
{ icon: '/', text: '/explain, /fix, /refactor, /test, /style β€” templated prompts for the active file.' },
{ icon: '✦', text: 'Hover an error line in the Logs tab to get a "Fix with AI" pill.' },
{ icon: 'πŸ“·', text: 'Snapshot before a risky refactor β€” one click to roll back if it goes sideways.' },
{ icon: '⛁', text: 'The DB tab inspects your SQLite live β€” schema, row counts, paginated data.' },
{ icon: '⌬', text: 'The Terminal tab is a real shell rooted at your project β€” npm / uv / git all work.' },
{ icon: '⚑', text: 'Auto-pacing keeps you under the free-tier rate limit β€” no error toasts.' },
{ icon: 'βŒ₯', text: 'Click the model name (left to Run icon) to switch β€” your choice is saved to your profile.' },
{ icon: '↻', text: 'Edits hot-reload via Vite HMR / node --watch / uvicorn --reload β€” no manual restart.' },
{ icon: 'β—†', text: 'Four templates: React+Express, FastAPI+React, Next.js+Prisma, API-only FastAPI.' },
{ icon: '⌘B', text: '⌘B toggles the sidebar; the file tree lives there with AI-tracked badges.' },
{ icon: '⏎', text: 'Shift+Enter for a newline. Enter alone sends. Esc aborts a running turn.' },
{ icon: '✎', text: 'The agent uses apply_patch (unified diff) for small edits β€” cheaper than rewrites.' },
{ icon: 'β†—', text: 'Open-in-browser (TopBar β†—) launches the running app in a real browser tab.' },
]
export default function WorkingHints() {
const isStreaming = useAppStore((s) => s.isStreaming)
// Start at a stable index; the effect picks the (random) order at run time.
// Doing Math.random in useState/useMemo trips React's "no impure functions
// during render" rule.
const [idx, setIdx] = useState(0)
const orderRef = useRef([])
useEffect(() => {
if (!isStreaming) return
// Build a fresh shuffled order at the start of each streaming session so
// we cycle through every hint before repeating.
const a = HINTS.map((_, i) => i)
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[a[i], a[j]] = [a[j], a[i]]
}
orderRef.current = a
let pos = 0
setIdx(a[pos])
const id = setInterval(() => {
pos = (pos + 1) % a.length
setIdx(a[pos])
}, ROTATE_MS)
return () => clearInterval(id)
}, [isStreaming])
if (!isStreaming) return null
const hint = HINTS[idx]
return (
<div
key={idx} // re-mount on rotate so fade-in re-fires
className="fade-in flex items-center gap-2.5 mb-2 px-3 py-2 rounded-lg"
style={{
background: 'var(--surface-2)',
border: '1px solid var(--border-2)',
borderLeft: '3px solid var(--accent-hi)',
fontSize: 12.5,
color: 'var(--text)',
lineHeight: 1.4,
}}
role="status"
aria-label="Tip"
>
<span
style={{
width: 24,
height: 24,
display: 'inline-flex',
alignItems: 'center',
justifyContent:'center',
borderRadius: 6,
background: 'var(--accent-bg)',
color: 'var(--accent-hi)',
fontFamily: 'ui-monospace, "JetBrains Mono", monospace',
fontSize: 11,
fontWeight: 600,
flexShrink: 0,
}}
>
{hint.icon}
</span>
<span style={{ minWidth: 0, flex: 1 }}>
<span
style={{
display: 'inline-block',
color: 'var(--accent-hi)',
fontSize: 9.5,
fontWeight: 700,
letterSpacing: '0.08em',
textTransform: 'uppercase',
marginRight: 6,
verticalAlign: 'middle',
}}
>
Did you know?
</span>
<span
style={{
color: 'var(--text-dim)',
verticalAlign:'middle',
}}
>
{hint.text}
</span>
</span>
</div>
)
}