Skip to content

Commit 1cb0d13

Browse files
committed
feat(ai-chat reference): explicit Preload button + Runs link in debug panel + sendAction bridge
UX cleanup discovered during the Sessions e2e sweep. Three changes, one commit because they all live in the chat input row / debug panel area: - Explicit "Preload" button next to "Send" that only renders when the chat has no messages and no session yet. Clicking calls transport.preload(chatId), which mints the session and triggers the first run with trigger:"preload". Self-hides once session is truthy. Replaces the inert "Preload new chats" sidebar checkbox (the visible `+ New Chat` button only navigated and never called transport.preload — preloadEnabled was wired through the context but read by nobody, since ChatApp.tsx is no longer the mounted chat sidebar). Drops the dead preloadEnabled state + checkbox from chat-settings-context, chat-sidebar, chat-sidebar-wrapper, and the chat-app.tsx legacy code path. - Debug panel "Runs → View in dashboard" row, gated on dashboardUrl + a new NEXT_PUBLIC_TRIGGER_PROJECT_DASHBOARD_PATH env var. Resolves to the runs-list page filtered by chat:<chatId> tag — so opening the link drops you straight into the run list for the active chat. Threads the new prop through chat-view → chat → DebugPanel. - window.__chat.sendAction(action) bridge wrapper that delegates to transport.sendAction(chatId, action). Lets smoke tests drive aiChatHydrated's actionSchema (undo/rollback/remove/replace) without reaching into React internals.
1 parent bf24ad9 commit 1cb0d13

6 files changed

Lines changed: 30 additions & 33 deletions

File tree

references/ai-chat/src/components/chat-app.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ export function ChatApp({
5555

5656
// Model for new chats (before first message is sent)
5757
const [newChatModel, setNewChatModel] = useState(DEFAULT_MODEL);
58-
const [preloadEnabled, setPreloadEnabled] = useState(true);
5958
const [idleTimeoutInSeconds, setIdleTimeoutInSeconds] = useState(60);
6059

6160
const handleSessionChange = useCallback((chatId: string, session: SessionInfo | null) => {
@@ -107,14 +106,7 @@ export function ChatApp({
107106
setActiveChatId(id);
108107
setMessages([]);
109108
setNewChatModel(DEFAULT_MODEL);
110-
if (preloadEnabled) {
111-
// Eagerly create the session + first run before the user types.
112-
// Transport calls `startSession({ chatId: id, taskId: taskMode })`.
113-
// `idleTimeoutInSeconds` lives on the agent definition's
114-
// `idleTimeoutInSeconds`, not per-call.
115-
void transport.preload(id);
116-
void idleTimeoutInSeconds;
117-
}
109+
void idleTimeoutInSeconds;
118110
}
119111

120112
function handleSelectChat(id: string) {
@@ -173,8 +165,6 @@ export function ChatApp({
173165
onNewChat={handleNewChat}
174166
onDeleteChat={handleDeleteChat}
175167
onWipeAll={handleWipeAll}
176-
preloadEnabled={preloadEnabled}
177-
onPreloadChange={setPreloadEnabled}
178168
idleTimeoutInSeconds={idleTimeoutInSeconds}
179169
onIdleTimeoutChange={setIdleTimeoutInSeconds}
180170
taskMode={taskMode}
@@ -193,6 +183,7 @@ export function ChatApp({
193183
onModelChange={isNewChat ? setNewChatModel : undefined}
194184
session={activeSession}
195185
dashboardUrl={process.env.NEXT_PUBLIC_TRIGGER_DASHBOARD_URL}
186+
projectDashboardPath={process.env.NEXT_PUBLIC_TRIGGER_PROJECT_DASHBOARD_PATH}
196187
onFirstMessage={handleFirstMessage}
197188
onMessagesChange={handleMessagesChange}
198189
/>

references/ai-chat/src/components/chat-settings-context.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import { createContext, useContext, useState, type ReactNode } from "react";
55
type ChatSettings = {
66
taskMode: string;
77
setTaskMode: (mode: string) => void;
8-
preloadEnabled: boolean;
9-
setPreloadEnabled: (enabled: boolean) => void;
108
idleTimeoutInSeconds: number;
119
setIdleTimeoutInSeconds: (seconds: number) => void;
1210
};
@@ -15,14 +13,11 @@ const ChatSettingsContext = createContext<ChatSettings | null>(null);
1513

1614
export function ChatSettingsProvider({ children }: { children: ReactNode }) {
1715
const [taskMode, setTaskMode] = useState("ai-chat");
18-
const [preloadEnabled, setPreloadEnabled] = useState(true);
1916
const [idleTimeoutInSeconds, setIdleTimeoutInSeconds] = useState(60);
2017

2118
const value: ChatSettings = {
2219
taskMode,
2320
setTaskMode,
24-
preloadEnabled,
25-
setPreloadEnabled,
2621
idleTimeoutInSeconds,
2722
setIdleTimeoutInSeconds,
2823
};

references/ai-chat/src/components/chat-sidebar-wrapper.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ export function ChatSidebarWrapper({
2626
const {
2727
taskMode,
2828
setTaskMode,
29-
preloadEnabled,
30-
setPreloadEnabled,
3129
idleTimeoutInSeconds,
3230
setIdleTimeoutInSeconds,
3331
} = useChatSettings();
@@ -83,8 +81,6 @@ export function ChatSidebarWrapper({
8381
onNewChat={handleNewChat}
8482
onDeleteChat={handleDeleteChat}
8583
onWipeAll={handleWipeAll}
86-
preloadEnabled={preloadEnabled}
87-
onPreloadChange={setPreloadEnabled}
8884
idleTimeoutInSeconds={idleTimeoutInSeconds}
8985
onIdleTimeoutChange={setIdleTimeoutInSeconds}
9086
taskMode={taskMode}

references/ai-chat/src/components/chat-sidebar.tsx

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ type ChatSidebarProps = {
2525
onNewChat: () => void;
2626
onDeleteChat: (id: string) => void;
2727
onWipeAll: () => void;
28-
preloadEnabled: boolean;
29-
onPreloadChange: (enabled: boolean) => void;
3028
idleTimeoutInSeconds: number;
3129
onIdleTimeoutChange: (seconds: number) => void;
3230
taskMode: string;
@@ -40,8 +38,6 @@ export function ChatSidebar({
4038
onNewChat,
4139
onDeleteChat,
4240
onWipeAll,
43-
preloadEnabled,
44-
onPreloadChange,
4541
idleTimeoutInSeconds,
4642
onIdleTimeoutChange,
4743
taskMode,
@@ -93,15 +89,6 @@ export function ChatSidebar({
9389
</div>
9490

9591
<div className="shrink-0 border-t border-gray-200 px-3 py-2.5 space-y-2">
96-
<label className="flex items-center gap-2 text-xs text-gray-500 cursor-pointer select-none">
97-
<input
98-
type="checkbox"
99-
checked={preloadEnabled}
100-
onChange={(e) => onPreloadChange(e.target.checked)}
101-
className="rounded border-gray-300"
102-
/>
103-
Preload new chats
104-
</label>
10592
<div className="flex items-center gap-2 text-xs text-gray-500">
10693
<span className="shrink-0">Idle timeout</span>
10794
<input

references/ai-chat/src/components/chat-view.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export function ChatView({
9898
isNewChat={isNewChat}
9999
session={activeSession}
100100
dashboardUrl={process.env.NEXT_PUBLIC_TRIGGER_DASHBOARD_URL}
101+
projectDashboardPath={process.env.NEXT_PUBLIC_TRIGGER_PROJECT_DASHBOARD_PATH}
101102
onFirstMessage={handleFirstMessage}
102103
onMessagesChange={handleMessagesChange}
103104
/>

references/ai-chat/src/components/chat.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ function DebugPanel({
192192
status,
193193
session,
194194
dashboardUrl,
195+
projectDashboardPath,
195196
messageCount,
196197
ttfbHistory,
197198
}: {
@@ -200,9 +201,14 @@ function DebugPanel({
200201
status: string;
201202
session?: { publicAccessToken: string; lastEventId?: string; isStreaming?: boolean };
202203
dashboardUrl?: string;
204+
projectDashboardPath?: string;
203205
messageCount: number;
204206
ttfbHistory: TtfbEntry[];
205207
}) {
208+
const runsUrl =
209+
dashboardUrl && projectDashboardPath
210+
? `${dashboardUrl}${projectDashboardPath}/env/dev/runs?tags=${encodeURIComponent(`chat:${chatId}`)}`
211+
: undefined;
206212
const [open, setOpen] = useState(false);
207213

208214
const latestTtfb = ttfbHistory.length > 0 ? ttfbHistory[ttfbHistory.length - 1]! : undefined;
@@ -243,6 +249,7 @@ function DebugPanel({
243249
<Row label="Model" value={model} />
244250
<Row label="Status" value={status} />
245251
<Row label="Messages" value={String(messageCount)} />
252+
{runsUrl && <Row label="Runs" value="View in dashboard" link={runsUrl} />}
246253
{session ? (
247254
<>
248255
<Row label="Last Event ID" value={session.lastEventId ?? "—"} mono />
@@ -313,6 +320,7 @@ type ChatProps = {
313320
onModelChange?: (model: string) => void;
314321
session?: { publicAccessToken: string; lastEventId?: string; isStreaming?: boolean };
315322
dashboardUrl?: string;
323+
projectDashboardPath?: string;
316324
onFirstMessage?: (chatId: string, text: string) => void;
317325
onMessagesChange?: (chatId: string, messages: ChatUiMessage[]) => void;
318326
};
@@ -327,6 +335,7 @@ export function Chat({
327335
onModelChange,
328336
session,
329337
dashboardUrl,
338+
projectDashboardPath,
330339
onFirstMessage,
331340
onMessagesChange,
332341
}: ChatProps) {
@@ -550,6 +559,7 @@ export function Chat({
550559
promote: (id: string) => actionsRef.current.promote(id),
551560
send: (text: string) => actionsRef.current.send(text),
552561
stop: () => actionsRef.current.stop(),
562+
sendAction: (action: unknown) => transport.sendAction(chatId, action),
553563

554564
// ── Waiters ───────────────────────────────────────────────────
555565
waitForStatus: (target: string, timeoutMs = DEFAULT_TIMEOUT_MS) =>
@@ -844,6 +854,7 @@ export function Chat({
844854
status={status}
845855
session={session}
846856
dashboardUrl={dashboardUrl}
857+
projectDashboardPath={projectDashboardPath}
847858
messageCount={messages.length}
848859
ttfbHistory={ttfbHistory}
849860
/>
@@ -882,6 +893,22 @@ export function Chat({
882893
>
883894
Send
884895
</button>
896+
{/* Preload — only visible before the first message lands. After
897+
the user sends, the transport creates the session lazily, so
898+
session becomes truthy and this button hides itself. The
899+
transport tracks an in-flight preload internally; double-clicks
900+
are a no-op. */}
901+
{messages.length === 0 && !session && (
902+
<button
903+
type="button"
904+
onClick={() => {
905+
void transport.preload(chatId);
906+
}}
907+
className="rounded-lg bg-emerald-600 px-4 py-2 text-sm font-medium text-white hover:bg-emerald-700 disabled:opacity-50"
908+
>
909+
Preload
910+
</button>
911+
)}
885912
{status === "streaming" && (
886913
<button
887914
type="button"

0 commit comments

Comments
 (0)