@agent-native/core 0.149.1 → 0.149.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +2 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +150 -46
- package/corpus/templates/analytics/app/global.css +1 -1
- package/corpus/templates/analytics/app/i18n-data.ts +37 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +2 -0
- package/corpus/templates/analytics/changelog/2026-08-10-dashboard-tables-can-be-copied-and-sorted-by-multiple-column.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/assets/app/global.css +1 -1
- package/corpus/templates/assets/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/brain/app/global.css +1 -1
- package/corpus/templates/brain/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-10-clips-desktop-sign-in-now-starts-with-magic-links-and-google.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.css +1 -0
- package/corpus/templates/clips/desktop/src/app.tsx +274 -137
- package/corpus/templates/clips/desktop/src/styles.css +35 -0
- package/corpus/templates/content/app/global.css +1 -0
- package/corpus/templates/crm/app/global.css +1 -1
- package/corpus/templates/crm/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/forms/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/plan/app/global.css +1 -0
- package/corpus/templates/plan/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/plan/server/ui-plan-html.ts +4 -1
- package/corpus/templates/slides/AGENTS.md +11 -10
- package/corpus/templates/slides/actions/add-deck.ts +2 -0
- package/corpus/templates/slides/actions/add-slide.ts +15 -1
- package/corpus/templates/slides/actions/create-deck.ts +18 -6
- package/corpus/templates/slides/actions/get-deck.ts +29 -1
- package/corpus/templates/slides/actions/patch-deck.ts +122 -9
- package/corpus/templates/slides/actions/save-deck.ts +24 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +134 -618
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +69 -60
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +13 -1
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +56 -174
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +239 -116
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +3 -3
- package/corpus/templates/slides/app/components/layout/AgentWorkIndicator.tsx +1 -1
- package/corpus/templates/slides/app/components/layout/Layout.tsx +34 -35
- package/corpus/templates/slides/app/components/layout/layout-route-policy.ts +2 -5
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +45 -40
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +63 -16
- package/corpus/templates/slides/app/hooks/use-generating-slide-preview.ts +25 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +5 -4
- package/corpus/templates/slides/app/lib/recent-references.ts +23 -1
- package/corpus/templates/slides/app/lib/slide-animation-elements.ts +41 -3
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +4 -27
- package/corpus/templates/slides/app/pages/Index.tsx +240 -89
- package/corpus/templates/slides/changelog/2026-08-10-ai-editing-avatar-badge.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-generated-decks-keep-a-clear-human-readable-title.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-new-deck-creation-now-shows-progress-opens-the-editor-direct.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-presentation-reveals-now-follow-valid-targets.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-editing-controls-are-consolidated-into-one-focused-me.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-now-verify-and-restyle-every-imported-slide-in-one-pa.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-only-suggests-a-google-connection-for-relevant-reques.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-the-google-slides-import-option-is-now-labeled-slides.md +6 -0
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -3
- package/corpus/templates/slides/shared/api.ts +2 -1
- package/corpus/templates/slides/shared/deck-title.ts +135 -0
- package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +4 -1
- package/dist/agent/engine/ai-sdk-engine.js +1 -1
- package/dist/agent/production-agent.d.ts +21 -1
- package/dist/agent/production-agent.js +129 -10
- package/dist/agent/tool-approval-store.d.ts +18 -0
- package/dist/agent/tool-approval-store.js +122 -0
- package/dist/agent/types.d.ts +1 -1
- package/dist/cli/code-agent-executor.js +11 -2
- package/dist/client/AgentPanel.js +31 -6
- package/dist/client/AssistantChat.d.ts +3 -2
- package/dist/client/AssistantChat.js +7 -20
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat-adapter.js +11 -0
- package/dist/client/agent-chat.d.ts +1 -1
- package/dist/client/chat/markdown-renderer.js +14 -2
- package/dist/client/chat/message-components.d.ts +6 -2
- package/dist/client/chat/message-components.js +25 -47
- package/dist/client/chat/tool-call-display.d.ts +3 -5
- package/dist/client/chat/tool-call-display.js +7 -212
- package/dist/client/chat-first/apps-rail.js +3 -3
- package/dist/client/chat-first/browser-pane.js +4 -1
- package/dist/client/chat-first/chat-history.d.ts +3 -1
- package/dist/client/chat-first/chat-history.js +2 -2
- package/dist/client/chat-first/surface-tabs.d.ts +1 -1
- package/dist/client/chat-first/surface-tabs.js +52 -41
- package/dist/client/chat-first/types.d.ts +3 -0
- package/dist/client/chat-first.d.ts +10 -0
- package/dist/client/chat-first.js +37 -2
- package/dist/client/components/ui/message-scroller.js +4 -1
- package/dist/client/i18n.js +3 -1
- package/dist/client/resources/McpConnectionSuggestion.js +9 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/use-chat-models.js +156 -80
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/integrations/webhook-handler.js +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/auth.js +262 -31
- package/dist/server/onboarding-html.js +56 -9
- package/dist/shared/reasoning-effort.d.ts +8 -8
- package/dist/shared/reasoning-effort.js +10 -10
- package/dist/shared/streaming-text-smoothing.js +41 -11
- package/dist/styles/agent-native.css +19 -32
- package/dist/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/docs/content/template-chat.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
|
@@ -20,207 +20,12 @@ import { isBuiltinDataWidgetActionRenderer, resolveBuiltinActionChatRenderer, re
|
|
|
20
20
|
export const ChatRunningContext = React.createContext(false);
|
|
21
21
|
export const ChatRunDurationContext = React.createContext(null);
|
|
22
22
|
export const ASSISTANT_VISIBLE_TOOL_CALL_LIMIT = 3;
|
|
23
|
-
const TOOL_CALL_ENTRY_DURATION_MS = 220;
|
|
24
|
-
const TOOL_CALL_STACK_MOTION_DURATION_MS = 220;
|
|
25
|
-
const TOOL_CALL_STACK_EASING = "cubic-bezier(0.23, 1, 0.32, 1)";
|
|
26
|
-
function toolStackMotionKey(element, index) {
|
|
27
|
-
const toolCallId = element.dataset.agentToolCallId;
|
|
28
|
-
if (toolCallId)
|
|
29
|
-
return `tool:${toolCallId}`;
|
|
30
|
-
const summaryId = element.dataset.agentToolSummary;
|
|
31
|
-
if (summaryId)
|
|
32
|
-
return `summary:${summaryId}`;
|
|
33
|
-
return `anonymous:${index}`;
|
|
34
|
-
}
|
|
35
|
-
function prefersReducedMotionForToolStack() {
|
|
36
|
-
return (typeof window !== "undefined" &&
|
|
37
|
-
typeof window.matchMedia === "function" &&
|
|
38
|
-
window.matchMedia("(prefers-reduced-motion: reduce)").matches);
|
|
39
|
-
}
|
|
40
|
-
function startToolStackAnimation(element, keyframes) {
|
|
41
|
-
if (typeof element.animate !== "function")
|
|
42
|
-
return null;
|
|
43
|
-
return element.animate(keyframes, {
|
|
44
|
-
duration: TOOL_CALL_STACK_MOTION_DURATION_MS,
|
|
45
|
-
easing: TOOL_CALL_STACK_EASING,
|
|
46
|
-
fill: "both",
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
function prepareToolStackExitClone(clone, snapshot) {
|
|
50
|
-
clone.classList.remove("agent-tool-call--entering", "agent-tool-call--stack-entering");
|
|
51
|
-
clone
|
|
52
|
-
.querySelectorAll(".agent-tool-call--entering, .agent-tool-call--stack-entering")
|
|
53
|
-
.forEach((element) => {
|
|
54
|
-
element.classList.remove("agent-tool-call--entering", "agent-tool-call--stack-entering");
|
|
55
|
-
});
|
|
56
|
-
clone.classList.add("agent-tool-call-stack__exit");
|
|
57
|
-
clone.setAttribute("aria-hidden", "true");
|
|
58
|
-
clone.removeAttribute("data-agent-tool-call-id");
|
|
59
|
-
clone.removeAttribute("data-agent-tool-summary");
|
|
60
|
-
clone.removeAttribute("data-agent-tool-motion-token");
|
|
61
|
-
clone.style.left = `${snapshot.left}px`;
|
|
62
|
-
clone.style.top = `${snapshot.top}px`;
|
|
63
|
-
clone.style.width = `${snapshot.width}px`;
|
|
64
|
-
clone.style.height = `${snapshot.height}px`;
|
|
65
|
-
clone.style.transform = "translate3d(0, 0, 0)";
|
|
66
|
-
clone.style.transition = "none";
|
|
67
|
-
clone.style.animation = "none";
|
|
68
|
-
clone.style.opacity = "1";
|
|
69
|
-
}
|
|
70
23
|
/**
|
|
71
|
-
* Keeps the
|
|
72
|
-
*
|
|
73
|
-
* untouched; this only animates the DOM's before/after positions.
|
|
24
|
+
* Keeps the tool-call stack layout-transparent. Tool-entry motion is disabled
|
|
25
|
+
* until it can stay stable while streaming calls are added and summarized.
|
|
74
26
|
*/
|
|
75
27
|
export function ToolCallStackMotion({ children, className, }) {
|
|
76
|
-
|
|
77
|
-
const previousRef = useRef(new Map());
|
|
78
|
-
const previousStructureRef = useRef([]);
|
|
79
|
-
const activeMotionsRef = useRef(new Map());
|
|
80
|
-
const exitAnimationsRef = useRef(new Map());
|
|
81
|
-
useLayoutEffect(() => {
|
|
82
|
-
const stack = stackRef.current;
|
|
83
|
-
if (!stack)
|
|
84
|
-
return;
|
|
85
|
-
const previous = previousRef.current;
|
|
86
|
-
const visualPrevious = new Map(previous);
|
|
87
|
-
for (const [key, activeMotion] of activeMotionsRef.current) {
|
|
88
|
-
if (stack.contains(activeMotion.element)) {
|
|
89
|
-
const previousSnapshot = previous.get(key);
|
|
90
|
-
if (previousSnapshot) {
|
|
91
|
-
const rect = activeMotion.element.getBoundingClientRect();
|
|
92
|
-
visualPrevious.set(key, {
|
|
93
|
-
...previousSnapshot,
|
|
94
|
-
top: rect.top,
|
|
95
|
-
left: rect.left,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
activeMotion.animation.cancel();
|
|
100
|
-
}
|
|
101
|
-
activeMotionsRef.current.clear();
|
|
102
|
-
for (const [ghost, animation] of exitAnimationsRef.current) {
|
|
103
|
-
animation.cancel();
|
|
104
|
-
ghost.remove();
|
|
105
|
-
}
|
|
106
|
-
exitAnimationsRef.current.clear();
|
|
107
|
-
const elements = Array.from(stack.querySelectorAll("[data-agent-tool-call-id], [data-agent-tool-summary]"));
|
|
108
|
-
const structure = elements.map((element, index) => toolStackMotionKey(element, index));
|
|
109
|
-
const previousStructure = previousStructureRef.current;
|
|
110
|
-
const structureChanged = structure.length !== previousStructure.length ||
|
|
111
|
-
structure.some((key, index) => key !== previousStructure[index]);
|
|
112
|
-
previousStructureRef.current = structure;
|
|
113
|
-
const current = new Map();
|
|
114
|
-
for (const [index, element] of elements.entries()) {
|
|
115
|
-
const rect = element.getBoundingClientRect();
|
|
116
|
-
current.set(toolStackMotionKey(element, index), {
|
|
117
|
-
top: rect.top,
|
|
118
|
-
left: rect.left,
|
|
119
|
-
width: rect.width,
|
|
120
|
-
height: rect.height,
|
|
121
|
-
clone: element.cloneNode(true),
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
previousRef.current = current;
|
|
125
|
-
// Entry motion changes the measured transform without changing the stack
|
|
126
|
-
// structure. Do not turn that animation's cleanup into a second FLIP pass.
|
|
127
|
-
if (previous.size === 0 ||
|
|
128
|
-
!structureChanged ||
|
|
129
|
-
prefersReducedMotionForToolStack())
|
|
130
|
-
return;
|
|
131
|
-
const summary = elements.find((element) => element.dataset.agentToolSummary !== undefined);
|
|
132
|
-
const summaryRect = summary?.getBoundingClientRect();
|
|
133
|
-
const elementsByKey = new Map(elements.map((element, index) => [
|
|
134
|
-
toolStackMotionKey(element, index),
|
|
135
|
-
element,
|
|
136
|
-
]));
|
|
137
|
-
for (const [key, after] of current.entries()) {
|
|
138
|
-
const before = visualPrevious.get(key);
|
|
139
|
-
if (!before) {
|
|
140
|
-
const node = elementsByKey.get(key);
|
|
141
|
-
if (!node)
|
|
142
|
-
continue;
|
|
143
|
-
if (node.dataset.agentToolSummary !== undefined)
|
|
144
|
-
continue;
|
|
145
|
-
if (node.dataset.agentToolCallId !== undefined &&
|
|
146
|
-
!node.classList.contains("agent-tool-call--entering")) {
|
|
147
|
-
node.classList.add("agent-tool-call--stack-entering");
|
|
148
|
-
window.setTimeout(() => {
|
|
149
|
-
node.classList.remove("agent-tool-call--stack-entering");
|
|
150
|
-
}, TOOL_CALL_ENTRY_DURATION_MS);
|
|
151
|
-
}
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
const dx = before.left - after.left;
|
|
155
|
-
const dy = before.top - after.top;
|
|
156
|
-
if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5)
|
|
157
|
-
continue;
|
|
158
|
-
const node = elementsByKey.get(key);
|
|
159
|
-
if (!node)
|
|
160
|
-
continue;
|
|
161
|
-
if (node.dataset.agentToolSummary !== undefined)
|
|
162
|
-
continue;
|
|
163
|
-
const animation = startToolStackAnimation(node, [
|
|
164
|
-
{ transform: `translate3d(${dx}px, ${dy}px, 0)` },
|
|
165
|
-
{ transform: "translate3d(0, 0, 0)" },
|
|
166
|
-
]);
|
|
167
|
-
if (!animation)
|
|
168
|
-
continue;
|
|
169
|
-
activeMotionsRef.current.set(key, { element: node, animation });
|
|
170
|
-
const clearMotion = () => {
|
|
171
|
-
if (activeMotionsRef.current.get(key)?.animation === animation) {
|
|
172
|
-
activeMotionsRef.current.delete(key);
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
animation.onfinish = clearMotion;
|
|
176
|
-
animation.oncancel = clearMotion;
|
|
177
|
-
}
|
|
178
|
-
for (const [key, before] of visualPrevious.entries()) {
|
|
179
|
-
if (current.has(key) || !key.startsWith("tool:"))
|
|
180
|
-
continue;
|
|
181
|
-
const ghost = before.clone;
|
|
182
|
-
prepareToolStackExitClone(ghost, before);
|
|
183
|
-
document.body?.appendChild(ghost);
|
|
184
|
-
if (!ghost.isConnected)
|
|
185
|
-
continue;
|
|
186
|
-
const targetTop = summaryRect?.top ?? before.top - 8;
|
|
187
|
-
const targetLeft = summaryRect?.left ?? before.left;
|
|
188
|
-
const animation = startToolStackAnimation(ghost, [
|
|
189
|
-
{ transform: "translate3d(0, 0, 0)", opacity: 1 },
|
|
190
|
-
{
|
|
191
|
-
transform: `translate3d(${targetLeft - before.left}px, ${targetTop - before.top}px, 0)`,
|
|
192
|
-
opacity: 0,
|
|
193
|
-
},
|
|
194
|
-
]);
|
|
195
|
-
if (!animation) {
|
|
196
|
-
ghost.remove();
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
exitAnimationsRef.current.set(ghost, animation);
|
|
200
|
-
const clearExit = () => {
|
|
201
|
-
if (exitAnimationsRef.current.get(ghost) === animation) {
|
|
202
|
-
exitAnimationsRef.current.delete(ghost);
|
|
203
|
-
}
|
|
204
|
-
ghost.remove();
|
|
205
|
-
};
|
|
206
|
-
animation.onfinish = clearExit;
|
|
207
|
-
animation.oncancel = clearExit;
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
useEffect(() => {
|
|
211
|
-
return () => {
|
|
212
|
-
for (const { animation } of activeMotionsRef.current.values()) {
|
|
213
|
-
animation.cancel();
|
|
214
|
-
}
|
|
215
|
-
activeMotionsRef.current.clear();
|
|
216
|
-
for (const [ghost, animation] of exitAnimationsRef.current) {
|
|
217
|
-
animation.cancel();
|
|
218
|
-
ghost.remove();
|
|
219
|
-
}
|
|
220
|
-
exitAnimationsRef.current.clear();
|
|
221
|
-
};
|
|
222
|
-
}, []);
|
|
223
|
-
return (_jsx("div", { ref: stackRef, className: cn("agent-tool-call-stack", className), children: children }));
|
|
28
|
+
return (_jsx("div", { className: cn("agent-tool-call-stack", className), children: children }));
|
|
224
29
|
}
|
|
225
30
|
export const ApprovalContext = React.createContext(null);
|
|
226
31
|
/** Pending human-in-the-loop gate still waiting for Approve/Deny. */
|
|
@@ -231,18 +36,8 @@ export function toolCallHasPendingApproval(part) {
|
|
|
231
36
|
approval.dismissed !== true);
|
|
232
37
|
}
|
|
233
38
|
export const TOOL_LONG_RUNNING_HINT_DELAY_MS = 45_000;
|
|
234
|
-
export function ToolActivityPresentation({ toolName, isRunning,
|
|
39
|
+
export function ToolActivityPresentation({ toolName, isRunning, toolCallId, suppressLongRunningHint = false, children, }) {
|
|
235
40
|
const [showLongRunningHint, setShowLongRunningHint] = useState(false);
|
|
236
|
-
// A batched update can first reveal a tool with its result already attached.
|
|
237
|
-
// Presentation follows the active chat tail rather than execution state so
|
|
238
|
-
// that newly revealed completed tools still get their entrance motion.
|
|
239
|
-
const [animateEntry, setAnimateEntry] = useState(isActiveTail);
|
|
240
|
-
useEffect(() => {
|
|
241
|
-
if (!animateEntry)
|
|
242
|
-
return;
|
|
243
|
-
const timeout = window.setTimeout(() => setAnimateEntry(false), TOOL_CALL_ENTRY_DURATION_MS);
|
|
244
|
-
return () => window.clearTimeout(timeout);
|
|
245
|
-
}, [animateEntry]);
|
|
246
41
|
useEffect(() => {
|
|
247
42
|
if (!isRunning || suppressLongRunningHint) {
|
|
248
43
|
setShowLongRunningHint(false);
|
|
@@ -254,7 +49,7 @@ export function ToolActivityPresentation({ toolName, isRunning, isActiveTail, to
|
|
|
254
49
|
}, TOOL_LONG_RUNNING_HINT_DELAY_MS);
|
|
255
50
|
return () => window.clearTimeout(timeout);
|
|
256
51
|
}, [isRunning, suppressLongRunningHint, toolName]);
|
|
257
|
-
return (_jsx("div", { className:
|
|
52
|
+
return (_jsx("div", { className: "agent-tool-call", "data-agent-tool-call-id": toolCallId, "data-running": isRunning ? "true" : undefined, children: _jsxs("div", { className: "agent-tool-call__content", children: [children, isRunning && showLongRunningHint && (_jsx("div", { className: "mt-0.5 px-2.5 pb-2 text-[11px] leading-snug text-muted-foreground/80", children: "Still working. Large updates can take a minute or two." }))] }) }));
|
|
258
53
|
}
|
|
259
54
|
function stringifyToolValue(value, pretty = false) {
|
|
260
55
|
if (typeof value === "string")
|
|
@@ -480,7 +275,7 @@ export function ToolCallDisplay({ toolName, toolCallId, argsText, args, result,
|
|
|
480
275
|
// These must be separate components so hook order in ToolCallDisplayGeneric
|
|
481
276
|
// is always stable (no conditional hook calls).
|
|
482
277
|
const toolKind = structuredMeta?.toolKind;
|
|
483
|
-
const wrapToolDisplay = (children) => (_jsx(ToolActivityPresentation, { toolName: toolName, isRunning: isRunning,
|
|
278
|
+
const wrapToolDisplay = (children) => (_jsx(ToolActivityPresentation, { toolName: toolName, isRunning: isRunning, toolCallId: toolCallId, suppressLongRunningHint: toolName === "call-agent" || toolName.startsWith("agent:"), children: children }));
|
|
484
279
|
if (toolKind === "bash") {
|
|
485
280
|
return wrapToolDisplay(_jsx(BashCell, { meta: structuredMeta, output: result, isRunning: isRunning }));
|
|
486
281
|
}
|
|
@@ -642,7 +437,7 @@ function AgentCallCell({ agentName, activity, progress, responseText, isRunning,
|
|
|
642
437
|
function AgentActivityToolCallRow({ tool, isActiveTail, }) {
|
|
643
438
|
const isRunning = tool.status === "running";
|
|
644
439
|
const ToolIcon = resolveToolIcon(tool.name);
|
|
645
|
-
return (_jsx(ToolActivityPresentation, { toolName: tool.name, isRunning: isRunning,
|
|
440
|
+
return (_jsx(ToolActivityPresentation, { toolName: tool.name, isRunning: isRunning, toolCallId: tool.id, suppressLongRunningHint: true, children: _jsxs("div", { className: "my-0.5 flex w-full items-center gap-1.5 rounded-md py-0.5 text-left text-[13px] text-muted-foreground", children: [_jsx("span", { className: "flex size-4 shrink-0 items-center justify-center", children: isRunning ? (_jsx(IconLoader2, { className: "size-3.5 animate-spin" })) : (_jsx(ToolIcon, { className: "size-3.5" })) }), _jsx("span", { className: cn("min-w-0 truncate font-normal", isActiveTail && "agent-running-shimmer"), children: humanizeToolName(tool.name) })] }) }));
|
|
646
441
|
}
|
|
647
442
|
// ─── ToolCallFallback ──────────────────────────────────────────────────────────
|
|
648
443
|
export function ToolCallFallback({ toolName, toolCallId, args, argsText, result, ...rest }) {
|
|
@@ -15,12 +15,12 @@ function AppRows({ apps, activeAppId, layout, onDragStart, onDrop, onDragEnd, on
|
|
|
15
15
|
const active = activeAppId === app.id;
|
|
16
16
|
const pinned = layout.pinnedIds.includes(app.id);
|
|
17
17
|
const index = orderedApps.indexOf(app);
|
|
18
|
-
return (_jsxs(ContextMenu, { children: [_jsx(ContextMenuTrigger, { asChild: true, children: _jsxs("li", { draggable: true, "data-chat-first-app": true, "data-app-id": app.id, className: cn("group flex h-8 w-full min-w-0 items-center gap-1 rounded-md px-
|
|
18
|
+
return (_jsxs(ContextMenu, { children: [_jsx(ContextMenuTrigger, { asChild: true, children: _jsxs("li", { draggable: true, "data-chat-first-app": true, "data-app-id": app.id, className: cn("group flex h-8 w-full min-w-0 items-center gap-1 rounded-md px-0 text-sm", active
|
|
19
19
|
? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
|
|
20
20
|
: "text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onDragStart: (event) => {
|
|
21
21
|
event.dataTransfer.effectAllowed = "move";
|
|
22
22
|
onDragStart(app.id);
|
|
23
|
-
}, onDragOver: (event) => event.preventDefault(), onDrop: () => onDrop(app.id), onDragEnd: onDragEnd, children: [_jsxs("button", { type: "button", className: "flex h-full min-w-0 flex-1 items-center gap-2 px-
|
|
23
|
+
}, onDragOver: (event) => event.preventDefault(), onDrop: () => onDrop(app.id), onDragEnd: onDragEnd, children: [_jsxs("button", { type: "button", className: "flex h-full min-w-0 flex-1 items-center gap-2 px-2 text-start", onClick: () => onOpenApp(app), onKeyDown: (event) => {
|
|
24
24
|
if (!event.altKey)
|
|
25
25
|
return;
|
|
26
26
|
if (event.key === "ArrowUp") {
|
|
@@ -99,5 +99,5 @@ export const ChatFirstAppsRail = memo(function ChatFirstAppsRail({ apps, activeA
|
|
|
99
99
|
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
|
100
100
|
: "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onClick: () => onOpenApp(app), "aria-label": copy("openApp", { name: app.name }), title: app.name, children: renderIcon(app) }, app.id))), onOpenAllApps ? (_jsx("button", { type: "button", "data-chat-first-all-apps": true, className: "flex size-9 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", onClick: onOpenAllApps, "aria-label": copy("allApps"), title: copy("allApps"), children: _jsx(IconApps, { size: 16, "aria-hidden": "true" }) })) : null, error ? (_jsx("span", { className: "size-1.5 rounded-full bg-destructive", title: error, "aria-label": error })) : null] }));
|
|
101
101
|
}
|
|
102
|
-
return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "mt-3 px-
|
|
102
|
+
return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "mt-3 px-0 pb-2 pt-2", "aria-label": copy("workspaceApps"), children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 px-2 text-[11px] font-medium text-sidebar-foreground/50", children: [_jsx("span", { children: copy("workspaceApps") }), _jsx("span", { className: "ml-auto", children: createTrigger })] }), loading && apps.length === 0 ? (_jsx("div", { className: "space-y-0.5 px-2", children: [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "h-8 w-full rounded-md" }, index))) })) : apps.length === 0 ? (_jsxs("div", { className: "px-2", children: [_jsx("p", { className: "text-xs text-sidebar-foreground/55", children: copy("noWorkspaceApps") }), createAppTrigger ? (createTrigger) : onCreateApp ? (_jsxs("button", { type: "button", className: "mt-1 inline-flex h-6 items-center gap-1.5 rounded-md border border-sidebar-border px-2 text-xs text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onCreateApp, children: [_jsx(IconPlus, { size: 13, "aria-hidden": "true" }), copy("createApp")] })) : (createTrigger)] })) : (_jsx(AppRows, { apps: visibleApps, activeAppId: activeAppId, layout: layout, onDragStart: setDraggedAppId, onDrop: reorderApps, onDragEnd: () => setDraggedAppId(null), onOpenApp: onOpenApp, onTogglePinned: togglePinned, onMove: moveApp, renderIcon: renderIcon, copy: copy })), hasMoreApps || showAllApps ? (_jsxs("button", { type: "button", className: "mt-0.5 flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: () => setShowAllApps((value) => !value), children: [showAllApps ? (_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" })), showAllApps ? copy("showLess") : copy("showMore")] })) : null, onOpenAllApps ? (_jsx("div", { className: "mt-1 border-t border-sidebar-border/60 pt-1", children: _jsxs("button", { type: "button", "data-chat-first-all-apps": true, className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onOpenAllApps, children: [_jsx(IconApps, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: copy("allApps") })] }) })) : null, error ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "mt-1 flex items-center gap-1 px-2 text-[10px] text-destructive/80", onClick: onRetry, "aria-label": error, children: [_jsx("span", { className: "size-1.5 rounded-full bg-destructive" }), onRetry ? copy("retry") : copy("appsLoadError")] }) }), _jsx(TooltipContent, { side: "right", children: error })] })) : null] }));
|
|
103
103
|
});
|
|
@@ -17,6 +17,9 @@ export function ChatFirstBrowserPane({ url, title, status, statusMessage, onClos
|
|
|
17
17
|
setHistoryIndex(0);
|
|
18
18
|
setNavigationError(null);
|
|
19
19
|
}, [url]);
|
|
20
|
+
const currentTarget = resolveChatFirstBrowserTarget({ url: currentUrl });
|
|
21
|
+
const isExternalOnly = currentTarget.status === "ready" &&
|
|
22
|
+
currentTarget.target.openExternally === true;
|
|
20
23
|
function navigate(event) {
|
|
21
24
|
event.preventDefault();
|
|
22
25
|
const target = resolveChatFirstBrowserTarget({ url: draftUrl });
|
|
@@ -56,7 +59,7 @@ export function ChatFirstBrowserPane({ url, title, status, statusMessage, onClos
|
|
|
56
59
|
}
|
|
57
60
|
return (_jsxs("section", { "data-chat-first-browser-pane": true, className: "flex h-full min-h-0 w-full flex-col overflow-hidden bg-background", "aria-label": copy("surface.browser.label"), children: [_jsxs("header", { className: "flex h-12 shrink-0 items-center gap-1.5 border-b border-border bg-card px-2", children: [_jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [_jsx("button", { type: "button", disabled: historyIndex <= 0, onClick: goBack, "aria-label": copy("browserBack"), title: copy("browserBack"), className: "flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-35", children: _jsx(IconArrowLeft, { size: 15 }) }), _jsx("button", { type: "button", disabled: historyIndex >= history.length - 1, onClick: goForward, "aria-label": copy("browserForward"), title: copy("browserForward"), className: "flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-35", children: _jsx(IconArrowRight, { size: 15 }) }), _jsx("button", { type: "button", onClick: () => setReloadKey((key) => key + 1), "aria-label": copy("browserReload"), title: copy("browserReload"), className: "flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconRefresh, { size: 15 }) })] }), _jsxs("form", { className: "flex min-w-0 flex-1 items-center gap-1.5 rounded-md border border-input bg-background px-2", onSubmit: navigate, children: [_jsx(IconWorld, { size: 13, className: "shrink-0 text-muted-foreground", "aria-hidden": "true" }), _jsx("input", { value: draftUrl, onChange: (event) => setDraftUrl(event.target.value), "aria-label": copy("browserAddress"), spellCheck: false, className: "h-7 min-w-0 flex-1 bg-transparent text-xs text-foreground outline-none placeholder:text-muted-foreground" })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [title ? (_jsx("span", { className: "hidden max-w-28 truncate px-1 text-xs text-muted-foreground xl:block", title: title, children: title })) : null, _jsx("button", { type: "button", onClick: () => window.open(currentUrl, "_blank", "noopener,noreferrer"), "aria-label": copy("browserOpenExternal"), title: copy("browserOpenExternal"), className: "flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconExternalLink, { size: 15 }) }), _jsx("button", { type: "button", onClick: onClose, "aria-label": copy("browserClose"), title: copy("browserClose"), className: "flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconX, { size: 15 }) })] })] }), navigationError ? (_jsx("p", { className: "border-b border-destructive/30 bg-destructive/10 px-3 py-1.5 text-xs text-destructive", role: "alert", children: navigationError })) : null, _jsx("div", { className: "relative min-h-0 flex-1 overflow-hidden", children: status === "starting" || status === "error" ? (_jsx("div", { className: "flex h-full items-center justify-center px-6 text-center", role: status === "error" ? "alert" : "status", "aria-live": "polite", children: _jsxs("div", { className: "max-w-xs space-y-1", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: status === "starting"
|
|
58
61
|
? copy("browserPreviewStarting")
|
|
59
|
-
: copy("browserPreviewError") }), statusMessage ? (_jsx("p", { className: "text-xs text-muted-foreground", children: statusMessage })) : null] }) })) : (renderEmbed({
|
|
62
|
+
: copy("browserPreviewError") }), statusMessage ? (_jsx("p", { className: "text-xs text-muted-foreground", children: statusMessage })) : null] }) })) : isExternalOnly ? (_jsx("div", { className: "flex h-full items-center justify-center p-6 text-center", children: _jsxs("div", { className: "max-w-sm space-y-3", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: copy("browserPreviewError") }), _jsxs("a", { href: currentUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent", children: [_jsx(IconExternalLink, { size: 14, "aria-hidden": "true" }), copy("browserOpenExternal")] })] }) })) : (renderEmbed({
|
|
60
63
|
url: currentUrl,
|
|
61
64
|
title: title || copy("browserPage"),
|
|
62
65
|
key: `${currentUrl}:${reloadKey}`,
|
|
@@ -3,10 +3,12 @@ import { type ChatHistoryItem, type ChatHistoryListProps } from "../chat/ChatHis
|
|
|
3
3
|
export interface ChatFirstChatHistoryProps extends Omit<ChatHistoryListProps, "items" | "sections" | "variant"> {
|
|
4
4
|
items: ChatHistoryItem[];
|
|
5
5
|
label?: ReactNode;
|
|
6
|
+
/** Optional contextual actions for the Chats section header. */
|
|
7
|
+
headerAction?: ReactNode;
|
|
6
8
|
}
|
|
7
9
|
/**
|
|
8
10
|
* The single chat-list presentation used by the chat-first rail on every
|
|
9
11
|
* host. Hosts still own thread fetching and actions, but row rhythm and the
|
|
10
12
|
* Chats section treatment stay identical.
|
|
11
13
|
*/
|
|
12
|
-
export declare function ChatFirstChatHistory({ items, label, className, ...props }: ChatFirstChatHistoryProps): import("react").JSX.Element | null;
|
|
14
|
+
export declare function ChatFirstChatHistory({ items, label, headerAction, className, ...props }: ChatFirstChatHistoryProps): import("react").JSX.Element | null;
|
|
@@ -5,8 +5,8 @@ import { ChatHistoryList, } from "../chat/ChatHistoryList.js";
|
|
|
5
5
|
* host. Hosts still own thread fetching and actions, but row rhythm and the
|
|
6
6
|
* Chats section treatment stay identical.
|
|
7
7
|
*/
|
|
8
|
-
export function ChatFirstChatHistory({ items, label = "Chats", className, ...props }) {
|
|
8
|
+
export function ChatFirstChatHistory({ items, label = "Chats", headerAction, className, ...props }) {
|
|
9
9
|
if (items.length === 0)
|
|
10
10
|
return null;
|
|
11
|
-
return (_jsxs("section", { "data-chat-first-chat-history": true, className: ["min-h-0 min-w-0", className].filter(Boolean).join(" "), children: [
|
|
11
|
+
return (_jsxs("section", { "data-chat-first-chat-history": true, className: ["min-h-0 min-w-0", className].filter(Boolean).join(" "), children: [_jsxs("div", { "data-chat-first-chat-history-header": true, className: "group mb-1 flex min-h-6 items-center justify-between gap-2 px-2", children: [_jsx("p", { className: "min-w-0 text-[11px] font-medium text-sidebar-foreground/50", children: label }), headerAction ? (_jsx("div", { className: "shrink-0 opacity-0 transition-opacity duration-100 group-hover:opacity-100 group-focus-within:opacity-100", children: headerAction })) : null] }), _jsx(ChatHistoryList, { ...props, items: items, variant: "rail", className: "min-w-0" })] }));
|
|
12
12
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { type ChatFirstSurfaceTab } from "../chat-first.js";
|
|
3
3
|
import type { ChatFirstSurfaceTabsProps } from "./types.js";
|
|
4
|
-
export declare function ChatFirstSurfaceTabs({ tabs, activeTabId, onActivate, onClose, onCloseOthers, onCloseToRight, onCloseAll, onOpenSurface, copy, }: ChatFirstSurfaceTabsProps): import("react").JSX.Element;
|
|
4
|
+
export declare function ChatFirstSurfaceTabs({ tabs, activeTabId, onActivate, onClose, onCloseOthers, onCloseToRight, onCloseAll, onOpenSurface, apps, onOpenApp, renderAppIcon, copy, }: ChatFirstSurfaceTabsProps): import("react").JSX.Element;
|
|
5
5
|
export declare function ChatFirstSurfaceContent({ tabs, activeTabId, renderTab, }: {
|
|
6
6
|
tabs: readonly ChatFirstSurfaceTab[];
|
|
7
7
|
activeTabId: string | null;
|
|
@@ -17,54 +17,65 @@ function SurfaceIcon({ kind }) {
|
|
|
17
17
|
const Icon = ICONS[kind];
|
|
18
18
|
return _jsx(Icon, { size: 14, "aria-hidden": "true" });
|
|
19
19
|
}
|
|
20
|
-
export function ChatFirstSurfaceTabs({ tabs, activeTabId, onActivate, onClose, onCloseOthers, onCloseToRight, onCloseAll, onOpenSurface, copy = defaultChatFirstCopy, }) {
|
|
21
|
-
return (_jsx(TooltipProvider, { delayDuration: 400, children: _jsx("div", { "data-chat-first-surface-tabs": true, className: "
|
|
20
|
+
export function ChatFirstSurfaceTabs({ tabs, activeTabId, onActivate, onClose, onCloseOthers, onCloseToRight, onCloseAll, onOpenSurface, apps = [], onOpenApp, renderAppIcon, copy = defaultChatFirstCopy, }) {
|
|
21
|
+
return (_jsx(TooltipProvider, { delayDuration: 400, children: _jsx("div", { "data-chat-first-surface-tabs": true, className: cn("min-w-0 border-b border-border bg-card", tabs.length > 0
|
|
22
|
+
? "shrink-0"
|
|
23
|
+
: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden"), children: tabs.length > 0 ? (_jsx("div", { className: "flex min-w-0 items-center gap-1 overflow-x-auto px-2 py-1.5", role: "tablist", "aria-label": copy("openSideSurfaces"), children: tabs.map((tab, index) => (_jsxs(ContextMenu, { children: [_jsx(ContextMenuTrigger, { asChild: true, children: _jsxs("div", { "data-surface-tab-id": tab.id, "data-active": activeTabId === tab.id ? "true" : "false", className: cn("group flex h-8 min-w-0 max-w-48 shrink-0 items-center rounded-md text-xs transition-colors", activeTabId === tab.id
|
|
22
24
|
? "bg-accent text-foreground"
|
|
23
|
-
: "text-muted-foreground hover:bg-accent hover:text-foreground"),
|
|
24
|
-
const nextIndex = event.key === "ArrowRight"
|
|
25
|
-
? (index + 1) % tabs.length
|
|
26
|
-
: event.key === "ArrowLeft"
|
|
27
|
-
? (index - 1 + tabs.length) % tabs.length
|
|
28
|
-
: event.key === "Home"
|
|
29
|
-
? 0
|
|
30
|
-
: event.key === "End"
|
|
31
|
-
? tabs.length - 1
|
|
32
|
-
: -1;
|
|
33
|
-
if (nextIndex < 0)
|
|
34
|
-
return;
|
|
35
|
-
event.preventDefault();
|
|
36
|
-
onActivate(tabs[nextIndex]);
|
|
37
|
-
requestAnimationFrame(() => {
|
|
38
|
-
document
|
|
39
|
-
.querySelector(`[data-chat-first-surface-tab-id="${CSS.escape(tabs[nextIndex].id)}"]`)
|
|
40
|
-
?.focus();
|
|
41
|
-
});
|
|
42
|
-
}, "data-chat-first-surface-tab-id": tab.id, onMouseDown: (event) => {
|
|
25
|
+
: "text-muted-foreground hover:bg-accent hover:text-foreground"), onMouseDown: (event) => {
|
|
43
26
|
if (event.button === 1) {
|
|
44
27
|
event.preventDefault();
|
|
45
28
|
onClose(tab);
|
|
46
29
|
}
|
|
47
|
-
}, title: tab.title, children: [_jsxs("button", { type: "button",
|
|
30
|
+
}, title: tab.title, children: [_jsxs("button", { type: "button", role: "tab", tabIndex: activeTabId === tab.id ? 0 : -1, "aria-selected": activeTabId === tab.id, "data-chat-first-surface-tab-id": tab.id, className: "flex min-w-0 flex-1 items-center gap-1.5 truncate rounded-md px-2 text-start focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", onKeyDown: (event) => {
|
|
31
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
onActivate(tab);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const nextIndex = event.key === "ArrowRight"
|
|
37
|
+
? (index + 1) % tabs.length
|
|
38
|
+
: event.key === "ArrowLeft"
|
|
39
|
+
? (index - 1 + tabs.length) % tabs.length
|
|
40
|
+
: event.key === "Home"
|
|
41
|
+
? 0
|
|
42
|
+
: event.key === "End"
|
|
43
|
+
? tabs.length - 1
|
|
44
|
+
: -1;
|
|
45
|
+
if (nextIndex < 0)
|
|
46
|
+
return;
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
onActivate(tabs[nextIndex]);
|
|
49
|
+
requestAnimationFrame(() => {
|
|
50
|
+
document
|
|
51
|
+
.querySelector(`[data-chat-first-surface-tab-id="${CSS.escape(tabs[nextIndex].id)}"]`)
|
|
52
|
+
?.focus();
|
|
53
|
+
});
|
|
54
|
+
}, onClick: () => onActivate(tab), children: [_jsx(SurfaceIcon, { kind: tab.kind }), _jsx("span", { className: "truncate", children: tab.title })] }), _jsx("button", { type: "button", className: "mr-1 flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground/65 opacity-0 transition-opacity hover:bg-background/70 hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100", "aria-label": copy("closeTab", { name: tab.title }), title: copy("closeTab", { name: tab.title }), onClick: (event) => {
|
|
48
55
|
event.stopPropagation();
|
|
49
56
|
onClose(tab);
|
|
50
|
-
}, children: _jsx(IconX, { size: 12, "aria-hidden": "true" }) })] }) }), _jsxs(ContextMenuContent, { children: [_jsx(ContextMenuItem, { onSelect: () => onClose(tab), children: copy("close") }), _jsx(ContextMenuItem, { disabled: tabs.length < 2, onSelect: () => onCloseOthers(tab), children: copy("closeOthers") }), _jsx(ContextMenuItem, { disabled: tabs[tabs.length - 1]?.id === tab.id, onSelect: () => onCloseToRight(tab), children: copy("closeToRight") }), _jsx(ContextMenuSeparator, {}), _jsx(ContextMenuItem, { onSelect: onCloseAll, children: copy("closeAll") })] })] }, tab.id))) })) : (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
}, children: _jsx(IconX, { size: 12, "aria-hidden": "true" }) })] }) }), _jsxs(ContextMenuContent, { children: [_jsx(ContextMenuItem, { onSelect: () => onClose(tab), children: copy("close") }), _jsx(ContextMenuItem, { disabled: tabs.length < 2, onSelect: () => onCloseOthers(tab), children: copy("closeOthers") }), _jsx(ContextMenuItem, { disabled: tabs[tabs.length - 1]?.id === tab.id, onSelect: () => onCloseToRight(tab), children: copy("closeToRight") }), _jsx(ContextMenuSeparator, {}), _jsx(ContextMenuItem, { onSelect: onCloseAll, children: copy("closeAll") })] })] }, tab.id))) })) : (_jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-y-auto px-2 py-1", "data-surface-empty-state": true, "aria-label": copy("openSideSurfaces"), children: [CHAT_FIRST_SURFACE_CATALOG.map((surface) => {
|
|
58
|
+
const label = copy(`surface.${surface.kind}.label`);
|
|
59
|
+
const reason = surface.availability === "deferred"
|
|
60
|
+
? copy(`surface.${surface.kind}.reason`)
|
|
61
|
+
: copy(`surface.${surface.kind}.reason`);
|
|
62
|
+
const isDeferred = surface.availability === "deferred";
|
|
63
|
+
const canOpenSurface = onOpenSurface &&
|
|
64
|
+
(surface.kind === "browser" ||
|
|
65
|
+
surface.kind === "side-chat" ||
|
|
66
|
+
surface.kind === "agents");
|
|
67
|
+
const row = (_jsxs("div", { className: cn("flex h-7 min-w-0 items-center gap-2 rounded px-2 text-xs transition-colors", canOpenSurface
|
|
68
|
+
? "text-foreground hover:bg-accent"
|
|
69
|
+
: isDeferred
|
|
70
|
+
? "text-muted-foreground/60"
|
|
71
|
+
: "text-muted-foreground hover:bg-accent hover:text-foreground"), title: !isDeferred ? reason : undefined, "aria-label": `${label}: ${reason}`, children: [_jsx(SurfaceIcon, { kind: surface.kind }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: label }), isDeferred ? (_jsx("span", { "data-surface-availability": "deferred", className: "size-1.5 shrink-0 rounded-full bg-muted-foreground/45", "aria-label": copy("deferred") })) : null] }));
|
|
72
|
+
if (canOpenSurface) {
|
|
73
|
+
return (_jsx("button", { type: "button", className: "block w-full text-start", title: surface.kind === "browser" ? reason : copy("openActivity"), "aria-label": surface.kind === "browser" ? label : copy("openActivity"), onClick: () => onOpenSurface(surface.kind === "side-chat" ? "agents" : surface.kind), children: row }, surface.kind));
|
|
74
|
+
}
|
|
75
|
+
if (!isDeferred)
|
|
76
|
+
return _jsx("div", { children: row }, surface.kind);
|
|
77
|
+
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("div", { className: "text-start", children: row }) }), _jsx(TooltipContent, { side: "left", className: "max-w-72", children: reason })] }, surface.kind));
|
|
78
|
+
}), apps.length > 0 && onOpenApp ? (_jsxs("div", { className: "mt-1 border-t border-border pt-1", children: [_jsx("p", { className: "px-2 py-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: copy("workspaceApps") }), _jsx("div", { className: "space-y-0.5", children: apps.map((app) => (_jsxs("button", { type: "button", "data-chat-first-surface-app": app.id, className: "flex h-8 w-full min-w-0 items-center gap-2 rounded px-2 text-start text-xs text-foreground transition-colors hover:bg-accent", title: copy("openApp", { name: app.name }), "aria-label": copy("openApp", { name: app.name }), onClick: () => onOpenApp(app), children: [renderAppIcon?.(app) ?? _jsx(SurfaceIcon, { kind: "app" }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: app.name })] }, app.id))) })] })) : null] })) }) }));
|
|
68
79
|
}
|
|
69
80
|
export function ChatFirstSurfaceContent({ tabs, activeTabId, renderTab, }) {
|
|
70
81
|
return (_jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: tabs.map((tab) => {
|
|
@@ -31,6 +31,9 @@ export interface ChatFirstSurfaceTabsProps {
|
|
|
31
31
|
onCloseToRight: (tab: ChatFirstSurfaceTab) => void;
|
|
32
32
|
onCloseAll: () => void;
|
|
33
33
|
onOpenSurface?: (kind: ChatFirstSurfaceKind) => void;
|
|
34
|
+
apps?: readonly ChatFirstAppItem[];
|
|
35
|
+
onOpenApp?: (app: ChatFirstAppItem) => void;
|
|
36
|
+
renderAppIcon?: (app: ChatFirstAppItem) => ReactNode;
|
|
34
37
|
copy?: ChatFirstCopy;
|
|
35
38
|
}
|
|
36
39
|
export interface ChatFirstAppRailProps {
|
|
@@ -36,11 +36,14 @@ export interface ChatFirstSessionWatchStore {
|
|
|
36
36
|
close: () => void;
|
|
37
37
|
}
|
|
38
38
|
export type ChatFirstSurfaceKind = "app" | "browser" | "terminal" | "diff" | "files" | "side-chat" | "agents";
|
|
39
|
+
export type ChatFirstAppSurfacePlacement = "main" | "side";
|
|
39
40
|
export interface ChatFirstSurfaceTab {
|
|
40
41
|
id: string;
|
|
41
42
|
kind: ChatFirstSurfaceKind;
|
|
42
43
|
title: string;
|
|
43
44
|
appId?: string;
|
|
45
|
+
/** App tabs can either replace the main chat or sit beside it. */
|
|
46
|
+
placement?: ChatFirstAppSurfacePlacement;
|
|
44
47
|
path?: string;
|
|
45
48
|
view?: string;
|
|
46
49
|
url?: string;
|
|
@@ -128,6 +131,7 @@ export type ChatFirstBrowserResolution = {
|
|
|
128
131
|
target: {
|
|
129
132
|
url: string;
|
|
130
133
|
title?: string;
|
|
134
|
+
openExternally?: boolean;
|
|
131
135
|
};
|
|
132
136
|
} | {
|
|
133
137
|
status: "unresolved";
|
|
@@ -202,6 +206,12 @@ export declare function subscribeChatFirstOpenApp(listener: ChatFirstOpenAppList
|
|
|
202
206
|
export declare function emitChatFirstOpenApp(detail: ChatFirstOpenAppDetail): ChatFirstOpenAppDelivery;
|
|
203
207
|
export declare function subscribeChatFirstOpenBrowser(listener: ChatFirstOpenBrowserListener): () => void;
|
|
204
208
|
export declare function emitChatFirstOpenBrowser(detail: ChatFirstOpenBrowserDetail): ChatFirstOpenAppDelivery;
|
|
209
|
+
/**
|
|
210
|
+
* Builder's web app sends frame-blocking headers. Its branch/editor URLs are
|
|
211
|
+
* open-browser targets, not the iframe-backed Fusion preview URLs served from
|
|
212
|
+
* builder.cloud, so keep them out of the chat-first browser iframe.
|
|
213
|
+
*/
|
|
214
|
+
export declare function shouldOpenChatFirstBrowserExternally(value: string): boolean;
|
|
205
215
|
export declare function resolveChatFirstAppTarget(detail: ChatFirstOpenAppDetail, apps: readonly ChatFirstAppRegistration[], options?: {
|
|
206
216
|
currentOrigin?: string;
|
|
207
217
|
}): ChatFirstAppResolution;
|
|
@@ -338,6 +338,9 @@ export function resolveChatFirstBrowserTarget(detail) {
|
|
|
338
338
|
target: {
|
|
339
339
|
url: url.href,
|
|
340
340
|
...(detail.title?.trim() ? { title: detail.title.trim() } : {}),
|
|
341
|
+
...(shouldOpenChatFirstBrowserExternally(url.href)
|
|
342
|
+
? { openExternally: true }
|
|
343
|
+
: {}),
|
|
341
344
|
},
|
|
342
345
|
};
|
|
343
346
|
}
|
|
@@ -464,9 +467,16 @@ function readChatFirstSurfaceTabsState(scope) {
|
|
|
464
467
|
return { tabs: [], activeTabId: null };
|
|
465
468
|
}
|
|
466
469
|
const value = scoped;
|
|
470
|
+
let migratedLegacyPlacement = false;
|
|
467
471
|
const tabs = Array.isArray(value.tabs)
|
|
468
472
|
? value.tabs.flatMap((candidate) => {
|
|
469
473
|
const tab = normalizePersistedChatFirstSurfaceTab(candidate);
|
|
474
|
+
if (tab?.kind === "app" &&
|
|
475
|
+
candidate !== null &&
|
|
476
|
+
typeof candidate === "object" &&
|
|
477
|
+
!("placement" in candidate)) {
|
|
478
|
+
migratedLegacyPlacement = true;
|
|
479
|
+
}
|
|
470
480
|
return tab ? [tab] : [];
|
|
471
481
|
})
|
|
472
482
|
: [];
|
|
@@ -474,7 +484,10 @@ function readChatFirstSurfaceTabsState(scope) {
|
|
|
474
484
|
tabs.some((tab) => tab.id === value.activeTabId)
|
|
475
485
|
? value.activeTabId
|
|
476
486
|
: (tabs[0]?.id ?? null);
|
|
477
|
-
|
|
487
|
+
const state = { tabs, activeTabId };
|
|
488
|
+
if (migratedLegacyPlacement)
|
|
489
|
+
writeChatFirstSurfaceTabsState(scope, state);
|
|
490
|
+
return state;
|
|
478
491
|
}
|
|
479
492
|
catch {
|
|
480
493
|
return { tabs: [], activeTabId: null };
|
|
@@ -498,11 +511,23 @@ function normalizePersistedChatFirstSurfaceTab(candidate) {
|
|
|
498
511
|
const appId = typeof value.appId === "string" ? value.appId.trim() : "";
|
|
499
512
|
if (!appId)
|
|
500
513
|
return null;
|
|
514
|
+
const placement = value.placement;
|
|
515
|
+
if (placement !== undefined &&
|
|
516
|
+
placement !== "main" &&
|
|
517
|
+
placement !== "side") {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
501
520
|
if (value.path !== undefined &&
|
|
502
521
|
(typeof value.path !== "string" || normalizedPath(value.path) === null)) {
|
|
503
522
|
return null;
|
|
504
523
|
}
|
|
505
|
-
return {
|
|
524
|
+
return {
|
|
525
|
+
...tab,
|
|
526
|
+
id,
|
|
527
|
+
title,
|
|
528
|
+
appId,
|
|
529
|
+
placement: placement ?? "main",
|
|
530
|
+
};
|
|
506
531
|
}
|
|
507
532
|
if (kind === "browser") {
|
|
508
533
|
const url = typeof value.url === "string" ? value.url.trim() : "";
|
|
@@ -700,6 +725,16 @@ function parseAbsoluteHttpUrl(value) {
|
|
|
700
725
|
return null;
|
|
701
726
|
}
|
|
702
727
|
}
|
|
728
|
+
/**
|
|
729
|
+
* Builder's web app sends frame-blocking headers. Its branch/editor URLs are
|
|
730
|
+
* open-browser targets, not the iframe-backed Fusion preview URLs served from
|
|
731
|
+
* builder.cloud, so keep them out of the chat-first browser iframe.
|
|
732
|
+
*/
|
|
733
|
+
export function shouldOpenChatFirstBrowserExternally(value) {
|
|
734
|
+
const url = parseAbsoluteHttpUrl(value);
|
|
735
|
+
return Boolean(url &&
|
|
736
|
+
(url.hostname === "builder.io" || url.hostname.endsWith(".builder.io")));
|
|
737
|
+
}
|
|
703
738
|
function registrationUrls(app, currentOrigin) {
|
|
704
739
|
const values = [app.url, app.devUrl];
|
|
705
740
|
if (app.path && currentOrigin)
|
|
@@ -10,7 +10,10 @@ function MessageScroller({ className, ...props }) {
|
|
|
10
10
|
}
|
|
11
11
|
function MessageScrollerViewport({ className, children, ...props }) {
|
|
12
12
|
const { start: hasContentAbove } = useMessageScrollerScrollable();
|
|
13
|
-
return (_jsx(ShadcnMessageScroller.Viewport, { className: cn("min-h-0 flex-1 overflow-y-auto overflow-x-hidden",
|
|
13
|
+
return (_jsx(ShadcnMessageScroller.Viewport, { className: cn("min-h-0 flex-1 overflow-y-auto overflow-x-hidden",
|
|
14
|
+
// The base class carries the mask itself so it is never added or
|
|
15
|
+
// removed; the modifier only widens the fade. See agent-native.css.
|
|
16
|
+
"message-scroller-viewport", hasContentAbove && "message-scroller-viewport--top-fade", className), ...props, children: children }));
|
|
14
17
|
}
|
|
15
18
|
function MessageScrollerContent({ className, spacerClassName, ...props }) {
|
|
16
19
|
return (_jsx(ShadcnMessageScroller.Content, { className: cn("flex flex-col", className), spacerClassName: cn("shrink-0", spacerClassName), ...props }));
|