feat: add AI assistant example using AI widgets#738
Conversation
📝 WalkthroughWalkthroughAdds an AI Assistant example under ChangesAI Assistant Example Application
AI Assistant Template for Code Generation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
examples/ai-assistant/src/index.tsx (2)
337-339: ⚡ Quick winPolling loop: prefer Promise-based control flow.
The
while (!toolDone)loop polls every 100ms until the tool completes. While this works (theawaityields control), it's not idiomatic. A cleaner pattern would wrap the approval workflow in a Promise that resolves whenonApproveoronDenyfires.♻️ Use Promise-based completion
private async demonstrateToolApproval(): Promise<void> { // ... existing ToolCall widget code ... - const tool = EXAMPLE_TOOLS[Math.floor(Math.random() * EXAMPLE_TOOLS.length)]; - let toolDone = false; + const tool = EXAMPLE_TOOLS[Math.floor(Math.random() * EXAMPLE_TOOLS.length)]; + await new Promise<void>((resolve) => { const toolWidget = new ToolApproval( { name: tool.name, args: tool.args, status: 'pending', collapsed: false, onApprove: () => { toolWidget.setStatus('running'); setTimeout(() => { toolWidget.setStatus('done'); toolWidget.setResult(`${tool.name} executed successfully`); - toolDone = true; + resolve(); }, 500); }, onDeny: () => { toolWidget.setStatus('error'); toolWidget.setResult('Tool execution denied'); - toolDone = true; + resolve(); }, }, { border: 'single', height: 6 } ); this.toolApprovalWidget = toolWidget; this.chatContainer.addChild(toolWidget); this.markDirty(); - - // Wait for approval/denial - while (!toolDone) { - await new Promise(r => setTimeout(r, 100)); - } + }); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/ai-assistant/src/index.tsx` around lines 337 - 339, The polling loop using toolDone should be replaced with a Promise-based completion to avoid busy-waiting: create a function (e.g., waitForApproval) that returns new Promise and resolves when the approval handlers run (resolve on onApprove or onDeny), wire the modal/approval callbacks to call those resolve paths, and await that Promise instead of looping; update usages that currently rely on the while (!toolDone) loop to await waitForApproval so control resumes once onApprove or onDeny fires.
207-209: ⚡ Quick winDead code:
allMessagesis constructed but never meaningfully used.The
allMessagesarray is initialized with just the assistant greeting, but the user's message (userText) is never added to it. Later at lines 235-238, a newaiMessagesarray is constructed that includes the same greeting and the user message. TheallMessagesvariable adds no value and makes the flow harder to follow.♻️ Simplify by removing unused variable
- // Collect all messages for API call - const allMessages: AIMessage[] = [ - { role: 'assistant', content: 'Hi! I am Claude. How can I help you?' }, - ]; - // Stream response let fullResponse = '';Then update line 235-237:
} else { const aiMessages: AIMessage[] = [ - ...allMessages, + { role: 'assistant', content: 'Hi! I am Claude. How can I help you?' }, { role: 'user', content: userText }, ];🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/ai-assistant/src/index.tsx` around lines 207 - 209, Remove the unused allMessages variable and any references to it: delete the const allMessages: AIMessage[] = [{ role: 'assistant', content: 'Hi! I am Claude. How can I help you?' }]; declaration and ensure the aiMessages construction (where aiMessages is built from the assistant greeting and userText) remains the single source of truth; verify aiMessages uses userText and the greeting so user input is included and no dead variable remains (check usages of allMessages elsewhere and remove or replace with aiMessages if present).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/ai-assistant/README.md`:
- Line 72: The README says tool calls should only run in real mode but the code
uses Math.random() > 0.6 without a mode check; update the condition around the
Math.random() call so it also verifies the app is in real mode (e.g., combine a
check like mode === 'real' or !isMock with the existing Math.random() > 0.6) so
tool demonstrations only trigger in real mode; locate the Math.random() > 0.6
expression and change the surrounding if to include the real-mode flag (for
example: if (mode === 'real' && Math.random() > 0.6) or equivalent based on the
existing mode variable).
In `@examples/ai-assistant/src/index.tsx`:
- Around line 249-250: The inputTokens counter is only updated inside the
real-mode branch, causing the "in:" counter to remain 0 in mock mode; move the
increment and call to updateTokenLabel so it runs for both modes (i.e., take the
lines that update this.inputTokens and this.updateTokenLabel out of the else
block and place them after the mock/real conditional). Locate the token handling
around the output token updates (where output tokens are already updated in both
branches) and ensure the same input-token update is executed regardless of mock
mode by referencing the this.inputTokens property and the updateTokenLabel()
method.
In `@packages/create-termui-app/templates/ai-assistant/src/index.tsx`:
- Around line 157-194: The streaming widget (this.streamingTextWidget) must be
removed from this.chatContainer on all paths; move the cleanup that currently
sits before creating the assistantMessage into the finally block and guard it
with a null check (e.g., if (this.streamingTextWidget) {
this.chatContainer.removeChild(this.streamingTextWidget);
this.streamingTextWidget = undefined; }) so the StreamingText instance is
removed whether the stream completes or an exception is thrown; keep the
assistant error/regular ChatMessage creation in catch/try as-is but remove the
earlier unconditional removeChild call to avoid double-removal.
- Around line 1-4: The file contains a duplicate JSX app implementation (a
second "function App()" and a render(<App />...) block) that redeclares IS_MOCK
and mockStream and references undeclared symbols (useTheme, useState, useKeymap,
ErrorBoundary, AutoThemeProvider, Message, TokenUsageData, ToolCallState) and
conflicts with the imported App from '`@termuijs/core`'; remove or extract that
trailing JSX example (the code after main()) into a separate template/file and
keep this entrypoint as a single self-booting implementation that calls main(),
ensuring you do not redeclare IS_MOCK or mockStream here and either import or
mock any previously missing symbols in the separate example file if kept.
- Around line 42-49: The code currently treats API-key presence (IS_MOCK) as the
source of truth while ignoring runtime failures from useAI('anthropic', ...) so
the app still shows "[claude]" and real greeting even when this.aiAdapter
failed; change the failure path to set a runtime "mock fallback" state and
surface it to the UI and send logic: in the catch block after useAI(...) fails,
set this.aiAdapter = null and set a new flag (e.g., this.isMockFallback = true
or update existing state used by AiAssistant) and ensure handleSendMessage,
mockStream selection (IS_MOCK || !this.aiAdapter), and the AiAssistant
component's ai/send logic read that runtime flag (or directly test
this.aiAdapter presence) so the header label and initial greeting reflect the
mock fallback; also keep the console.error call with the captured error.
---
Nitpick comments:
In `@examples/ai-assistant/src/index.tsx`:
- Around line 337-339: The polling loop using toolDone should be replaced with a
Promise-based completion to avoid busy-waiting: create a function (e.g.,
waitForApproval) that returns new Promise and resolves when the approval
handlers run (resolve on onApprove or onDeny), wire the modal/approval callbacks
to call those resolve paths, and await that Promise instead of looping; update
usages that currently rely on the while (!toolDone) loop to await
waitForApproval so control resumes once onApprove or onDeny fires.
- Around line 207-209: Remove the unused allMessages variable and any references
to it: delete the const allMessages: AIMessage[] = [{ role: 'assistant',
content: 'Hi! I am Claude. How can I help you?' }]; declaration and ensure the
aiMessages construction (where aiMessages is built from the assistant greeting
and userText) remains the single source of truth; verify aiMessages uses
userText and the greeting so user input is included and no dead variable remains
(check usages of allMessages elsewhere and remove or replace with aiMessages if
present).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 329ace8c-d934-4e3e-b67c-09b2d396fa1f
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (6)
README.mdexamples/ai-assistant/README.mdexamples/ai-assistant/package.jsonexamples/ai-assistant/src/index.tsxpackages/create-termui-app/templates/ai-assistant/package.jsonpackages/create-termui-app/templates/ai-assistant/src/index.tsx
|
Hi @Karanjot786, This PR only adds the AI Assistant example, updates the create-termui-app template, and documentation. The Meter file is not modified in this PR. Please let me know if I should rebase once the issue is resolved. |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
packages/create-termui-app/templates/ai-assistant/src/index.tsx (1)
43-52:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift
useAI()is too lazy for this fallback check.
useAI('anthropic', ...)only returns an adapter object. The Anthropic SDK is loaded later insidechat()/generate(), so thiscatchwill not fire for the common “SDK missing” path. The template can still render[claude]and the Claude greeting even though the first send will fail. Move the mock/real-mode decision to an eager provider check, or flip the UI into fallback mode when the first real stream errors. Based on learnings: "A template must boot on its own; if it references a widget or adapter that does not exist yet, provide a self-contained mock so the scaffold runs without that dependency."#!/bin/bash set -euo pipefail echo "== Template constructor ==" sed -n '43,52p' packages/create-termui-app/templates/ai-assistant/src/index.tsx | nl -ba echo echo "== Adapter implementation ==" sed -n '1,120p' packages/adapters/src/ai/index.ts | nl -ba echo echo "== Relevant adapter symbols ==" rg -n "export function useAI|loadAnthropic|async generate|async \\*chat" packages/adapters/src/ai/index.tsExpected result:
useAI()returns an adapter immediately, andloadAnthropic()only appears insidegenerate()/chat(), not during construction.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/create-termui-app/templates/ai-assistant/src/index.tsx` around lines 43 - 52, The constructor's try/catch around useAI('anthropic', ...) is ineffective because loadAnthropic is lazy and errors only surface in chat()/generate(); change the logic so useAI always returns an adapter object immediately (do not rely on that try/catch to detect missing SDK) and implement an eager-fallback strategy: keep this.aiAdapter = useAI('anthropic', {...}) as-is, remove the constructor-level SDK detection, and instead handle failures inside the async paths (aiAdapter.chat / aiAdapter.generate) by catching SDK errors there, setting this.isMockMode = true, swapping this.aiAdapter for a self-contained mock adapter, and updating the UI to show the fallback; ensure loadAnthropic remains only inside generate/chat and that useAI returns a safe adapter object even when the SDK is absent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@packages/create-termui-app/templates/ai-assistant/src/index.tsx`:
- Around line 43-52: The constructor's try/catch around useAI('anthropic', ...)
is ineffective because loadAnthropic is lazy and errors only surface in
chat()/generate(); change the logic so useAI always returns an adapter object
immediately (do not rely on that try/catch to detect missing SDK) and implement
an eager-fallback strategy: keep this.aiAdapter = useAI('anthropic', {...})
as-is, remove the constructor-level SDK detection, and instead handle failures
inside the async paths (aiAdapter.chat / aiAdapter.generate) by catching SDK
errors there, setting this.isMockMode = true, swapping this.aiAdapter for a
self-contained mock adapter, and updating the UI to show the fallback; ensure
loadAnthropic remains only inside generate/chat and that useAI returns a safe
adapter object even when the SDK is absent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 22d59161-a27b-4ef2-ab42-9469e12c0cfe
📒 Files selected for processing (3)
examples/ai-assistant/README.mdexamples/ai-assistant/src/index.tsxpackages/create-termui-app/templates/ai-assistant/src/index.tsx
✅ Files skipped from review due to trivial changes (1)
- examples/ai-assistant/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
- examples/ai-assistant/src/index.tsx
|
Thank you so much for your contribution, @kajalpa302005! Your PR #738 added an AI assistant example using the AI widgets. It shows exactly how to wire up a streaming AI response inside a terminal UI. Keep contributing. We appreciate your work. |
Co-authored-by: Karanjot786 <karanjots801@gmail.com>
Description
Adds a new AI Assistant example under
examples/ai-assistantdemonstrating the existing AI-related widgets available in the repository. The example supports both mock mode and real Anthropic integration throughuseAI. This PR also updates thecreate-termui-appAI assistant template and the root README.Related Issue
Closes #102
Which package(s)?
Type of Change
type:feature)type:docs)Checklist
needs-starcheck blocks your merge otherwise.bun vitest runbun run buildbun run typecheckCONTRIBUTING.md.type: short description.markDirty()(if your change affects rendering).anytypes without an inline comment explaining why.GSSoC 2026 Participation
https://gssoc.girlscript.org/profile/84adde4d-7c32-44f4-8f86-5a5f723d95cfScreenshots / Recordings (UI changes)
N/A
Notes for the Reviewer
Uses existing AI widgets available in the repository:
ChatMessageStreamingTextToolCallToolApprovaluseAIMock mode is available when
ANTHROPIC_API_KEYis not configured.The example follows patterns already used in:
examples/ai-streamingexamples/chat-appThe repository currently does not contain
ChatThreadorTokenUsagewidgets, so the example demonstrates the AI widgets that are currently available in the codebase.Summary by CodeRabbit
New Features
Documentation
Templates