| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { useEffect, useRef, useState } from 'react' |
| import useAppStore from '../store/appStore' |
|
|
| const ROTATE_MS = 7_500 |
|
|
| |
| |
| 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) |
| |
| |
| |
| const [idx, setIdx] = useState(0) |
| const orderRef = useRef([]) |
|
|
| useEffect(() => { |
| if (!isStreaming) return |
| |
| |
| 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> |
| ) |
| } |
|
|