@agent-native/core 0.157.0 → 0.157.1
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/server/jobs/analytics-bigquery-backfill.ts +19 -4
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +7 -2
- package/corpus/templates/assets/app/global.css +4 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1 -2
- package/corpus/templates/brain/app/routes/sources.tsx +1 -2
- package/corpus/templates/calendar/app/hooks/use-bookings.ts +12 -2
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +64 -2
- package/corpus/templates/calendar/server/handlers/bookings.ts +219 -93
- package/corpus/templates/calendar/server/plugins/auth.ts +0 -2
- package/corpus/templates/clips/actions/create-folder.ts +7 -1
- package/corpus/templates/clips/actions/create-meeting.ts +6 -2
- package/corpus/templates/clips/actions/create-recording.ts +5 -3
- package/corpus/templates/clips/actions/finalize-recording.ts +97 -65
- package/corpus/templates/clips/actions/import-loom-recording.ts +5 -3
- package/corpus/templates/clips/actions/request-recording-access.ts +272 -0
- package/corpus/templates/clips/actions/stitch-recordings.ts +5 -3
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +38 -63
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +75 -75
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +3 -21
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +13 -30
- package/corpus/templates/clips/app/i18n/en-US.ts +10 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -5
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +9 -16
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +135 -18
- package/corpus/templates/clips/changelog/2026-08-14-long-resumable-uploads-now-abort-cleanly-and-show-errors.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-14-standardized-share-popovers.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +11 -3
- package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +37 -8
- package/corpus/templates/clips/chrome-extension/src/github-preview.ts +3 -3
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +8 -25
- package/corpus/templates/clips/desktop/src/app.tsx +24 -0
- package/corpus/templates/clips/desktop/src/lib/media-capture-constraints.ts +42 -13
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +11 -18
- package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +1 -0
- package/corpus/templates/clips/desktop/src/shared/config.ts +1 -0
- package/corpus/templates/clips/desktop/src/styles.css +24 -9
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +8 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +154 -176
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +115 -31
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +7 -0
- package/corpus/templates/clips/server/db/index.ts +1 -0
- package/corpus/templates/clips/server/db/schema.ts +1 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +6 -1
- package/corpus/templates/clips/server/lib/emails.ts +23 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +55 -0
- package/corpus/templates/clips/server/lib/recordings.ts +2 -1
- package/corpus/templates/clips/server/lib/resumable-upload-cleanup.ts +49 -0
- package/corpus/templates/clips/server/lib/s3-upload-provider.ts +34 -7
- package/corpus/templates/clips/server/lib/upload-interruption.ts +15 -1
- package/corpus/templates/clips/server/lib/upload-lease.ts +94 -9
- package/corpus/templates/clips/server/routes/api/agent-context.json.get.ts +82 -7
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +1 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +71 -12
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +4 -3
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +19 -0
- package/corpus/templates/clips/server/routes/api/uploads/reap.post.ts +8 -1
- package/corpus/templates/clips/shared/recording-link.ts +4 -0
- package/corpus/templates/clips/shared/share-loader-response.ts +2 -1
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +7 -16
- package/corpus/templates/factory/AGENTS.md +5 -1
- package/corpus/templates/factory/README.md +6 -2
- package/corpus/templates/factory/actions/view-screen.ts +15 -0
- package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +58 -189
- package/corpus/templates/factory/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +7 -0
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +3 -1
- package/corpus/templates/factory/app/routes/agents.tsx +9 -0
- package/corpus/templates/mail/server/plugins/auth.ts +0 -2
- package/corpus/templates/plan/app/components/plan/LocalCodebasePicker.tsx +50 -14
- package/corpus/templates/plan/app/pages/PlansPage.tsx +9 -19
- package/corpus/templates/slides/app/components/editor/HistoryPanel.tsx +7 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +71 -22
- package/corpus/templates/slides/app/lib/editor-shortcuts.ts +46 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +13 -0
- package/dist/client/AgentPanel.d.ts +13 -1
- package/dist/client/AgentPanel.js +12 -10
- package/dist/client/AssistantChat.js +25 -5
- package/dist/client/HighlightedCodeBlock.js +12 -2
- package/dist/client/agent-chat/index.d.ts +1 -0
- package/dist/client/agent-chat/index.js +1 -0
- package/dist/client/chat/markdown-renderer.js +16 -3
- package/dist/client/chat-first/apps-rail.js +1 -1
- package/dist/client/components/ui/message-scroller.js +1 -1
- package/dist/client/settings/AccountSettingsCard.js +48 -19
- package/dist/client/sharing/ShareButton.d.ts +1 -2
- package/dist/client/sharing/ShareButton.js +5 -41
- package/dist/client/sharing/ShareDialog.js +5 -4
- package/dist/client/use-chat-threads.js +10 -14
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +1 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +15 -15
- package/dist/server/auth-marketing.d.ts +1 -0
- package/dist/server/auth-marketing.js +0 -2
- package/dist/server/auth.d.ts +1 -0
- package/dist/server/auth.js +18 -0
- package/dist/server/onboarding-html.d.ts +1 -0
- package/dist/server/onboarding-html.js +2 -88
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/shared/markdown-block-split.d.ts +16 -0
- package/dist/shared/markdown-block-split.js +81 -5
- package/dist/styles/agent-native.css +13 -0
- package/dist/voice/voice-cleanup-prompt.js +1 -0
- package/package.json +2 -2
- package/corpus/templates/clips/shared/mic-audio-cleanup.ts +0 -350
|
@@ -217,7 +217,10 @@ interface DeckContextType {
|
|
|
217
217
|
) => void;
|
|
218
218
|
reloadDecks: () => Promise<void>;
|
|
219
219
|
reloadDecksWithStatus: () => Promise<DeckReloadStatus>;
|
|
220
|
-
refreshOpenDeck: (
|
|
220
|
+
refreshOpenDeck: (
|
|
221
|
+
deckId: string,
|
|
222
|
+
options?: { clearPendingWrites?: boolean },
|
|
223
|
+
) => Promise<Deck | null>;
|
|
221
224
|
getDeck: (id: string) => Deck | undefined;
|
|
222
225
|
addSlide: (
|
|
223
226
|
deckId: string,
|
|
@@ -312,6 +315,8 @@ function normalizeActionDeck(value: unknown): Deck | null {
|
|
|
312
315
|
const pendingSaves = new Map<string, ReturnType<typeof setTimeout>>();
|
|
313
316
|
const inFlightSaves = new Set<string>();
|
|
314
317
|
const inFlightSaveChains = new Map<string, Promise<void>>();
|
|
318
|
+
const inFlightSaveControllers = new Map<string, AbortController>();
|
|
319
|
+
const deckSaveGenerations = new Map<string, number>();
|
|
315
320
|
const immediateFlushRequests = new Set<string>();
|
|
316
321
|
const deckSaveRetryAttempts = new Map<string, number>();
|
|
317
322
|
const failedSaveDecks = new Set<string>();
|
|
@@ -403,7 +408,11 @@ function deckPayload(deck: Deck): Record<string, unknown> {
|
|
|
403
408
|
* the `save-deck` action is called first, then any trailing granular ops are
|
|
404
409
|
* sent through `patch-deck`.
|
|
405
410
|
*/
|
|
406
|
-
async function persistDeckOps(
|
|
411
|
+
async function persistDeckOps(
|
|
412
|
+
deckId: string,
|
|
413
|
+
ops: GranularOp[],
|
|
414
|
+
signal?: AbortSignal,
|
|
415
|
+
) {
|
|
407
416
|
if (ops[0].op === "full-replace") {
|
|
408
417
|
// Legacy full-deck write — used by undo/redo and setDeckSlides.
|
|
409
418
|
// `callAction` bounds it so a stalled save can't wedge `inFlightSaves`
|
|
@@ -413,20 +422,28 @@ async function persistDeckOps(deckId: string, ops: GranularOp[]) {
|
|
|
413
422
|
await callAction(
|
|
414
423
|
"save-deck",
|
|
415
424
|
{ deckId, deck: deckPayload(deck) },
|
|
416
|
-
{ method: "PUT" },
|
|
425
|
+
{ method: "PUT", signal },
|
|
417
426
|
);
|
|
418
427
|
const trailingOps = ops.slice(1) as PatchDeckOp[];
|
|
419
428
|
if (trailingOps.length > 0) {
|
|
420
|
-
await callAction(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
429
|
+
await callAction(
|
|
430
|
+
"patch-deck",
|
|
431
|
+
{
|
|
432
|
+
deckId,
|
|
433
|
+
operations: trailingOps,
|
|
434
|
+
},
|
|
435
|
+
{ signal },
|
|
436
|
+
);
|
|
424
437
|
}
|
|
425
438
|
} else {
|
|
426
|
-
await callAction(
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
439
|
+
await callAction(
|
|
440
|
+
"patch-deck",
|
|
441
|
+
{
|
|
442
|
+
deckId,
|
|
443
|
+
operations: ops as PatchDeckOp[],
|
|
444
|
+
},
|
|
445
|
+
{ signal },
|
|
446
|
+
);
|
|
430
447
|
}
|
|
431
448
|
}
|
|
432
449
|
|
|
@@ -455,15 +472,23 @@ function drainPendingDeckOps(deckId: string): Promise<void> {
|
|
|
455
472
|
return Promise.resolve();
|
|
456
473
|
}
|
|
457
474
|
|
|
475
|
+
const generation = deckSaveGenerations.get(deckId) ?? 0;
|
|
476
|
+
const controller =
|
|
477
|
+
typeof AbortController === "undefined" ? null : new AbortController();
|
|
478
|
+
if (controller) inFlightSaveControllers.set(deckId, controller);
|
|
458
479
|
inFlightSaves.add(deckId);
|
|
459
|
-
|
|
460
|
-
|
|
480
|
+
const isCurrentGeneration = () =>
|
|
481
|
+
(deckSaveGenerations.get(deckId) ?? 0) === generation;
|
|
482
|
+
const next = persistDeckOps(deckId, ops, controller?.signal)
|
|
461
483
|
.then(() => {
|
|
462
|
-
|
|
484
|
+
if (!isCurrentGeneration()) return;
|
|
463
485
|
deckSaveRetryAttempts.delete(deckId);
|
|
464
486
|
failedSaveDecks.delete(deckId);
|
|
465
487
|
})
|
|
466
488
|
.catch((err) => {
|
|
489
|
+
// A restore or delete invalidated this request. Its result must not
|
|
490
|
+
// resurrect the old queue or schedule a retry after the boundary.
|
|
491
|
+
if (!isCurrentGeneration()) return;
|
|
467
492
|
console.error(`Failed to save deck ${deckId}:`, err);
|
|
468
493
|
const pending = pendingOpsQueue.get(deckId) ?? [];
|
|
469
494
|
pendingOpsQueue.set(deckId, [...ops, ...pending]);
|
|
@@ -488,10 +513,13 @@ function drainPendingDeckOps(deckId: string): Promise<void> {
|
|
|
488
513
|
.finally(() => {
|
|
489
514
|
if (inFlightSaveChains.get(deckId) === next) {
|
|
490
515
|
inFlightSaveChains.delete(deckId);
|
|
516
|
+
if (controller && inFlightSaveControllers.get(deckId) === controller) {
|
|
517
|
+
inFlightSaveControllers.delete(deckId);
|
|
518
|
+
}
|
|
491
519
|
inFlightSaves.delete(deckId);
|
|
492
520
|
const flushImmediately = immediateFlushRequests.delete(deckId);
|
|
493
521
|
notifySaveListeners();
|
|
494
|
-
if (
|
|
522
|
+
if (flushImmediately) {
|
|
495
523
|
void drainPendingDeckOps(deckId);
|
|
496
524
|
}
|
|
497
525
|
}
|
|
@@ -506,13 +534,14 @@ function drainPendingDeckOps(deckId: string): Promise<void> {
|
|
|
506
534
|
* follow-up drain chained by immediateFlushRequests for ops queued while a
|
|
507
535
|
* save was already running, and any requeued retry after a failed attempt.
|
|
508
536
|
* Used when a caller must not proceed (e.g. firing an agent request against
|
|
509
|
-
* a slide
|
|
510
|
-
* server. Throws if the save ultimately fails after
|
|
511
|
-
* `drainPendingDeckOps` swallows save errors internally
|
|
512
|
-
* retry loop and its promise always resolves regardless of
|
|
537
|
+
* a slide or restoring a saved version) until every write issued before that
|
|
538
|
+
* boundary has reached the server. Throws if the save ultimately fails after
|
|
539
|
+
* retries exhaust, since `drainPendingDeckOps` swallows save errors internally
|
|
540
|
+
* to drive its own retry loop and its promise always resolves regardless of
|
|
541
|
+
* outcome.
|
|
513
542
|
*/
|
|
514
543
|
async function flushDeckSave(deckId: string): Promise<void> {
|
|
515
|
-
|
|
544
|
+
while (true) {
|
|
516
545
|
const active = inFlightSaveChains.get(deckId);
|
|
517
546
|
if (active) {
|
|
518
547
|
await active;
|
|
@@ -647,6 +676,8 @@ function flushPendingSaves() {
|
|
|
647
676
|
}
|
|
648
677
|
|
|
649
678
|
function discardPendingDeckOps(deckId: string) {
|
|
679
|
+
deckSaveGenerations.set(deckId, (deckSaveGenerations.get(deckId) ?? 0) + 1);
|
|
680
|
+
inFlightSaveControllers.get(deckId)?.abort();
|
|
650
681
|
const timer = pendingSaves.get(deckId);
|
|
651
682
|
if (timer) clearTimeout(timer);
|
|
652
683
|
pendingSaves.delete(deckId);
|
|
@@ -1354,7 +1385,15 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1354
1385
|
* this, chat-driven edits land in the editor with Undo disabled.
|
|
1355
1386
|
*/
|
|
1356
1387
|
const applyRemoteDeckUpdate = useCallback(
|
|
1357
|
-
(
|
|
1388
|
+
(
|
|
1389
|
+
updated: Deck,
|
|
1390
|
+
label = "Agent edit",
|
|
1391
|
+
options?: { clearPendingWrites?: boolean },
|
|
1392
|
+
) => {
|
|
1393
|
+
if (options?.clearPendingWrites) {
|
|
1394
|
+
discardPendingDeckOps(updated.id);
|
|
1395
|
+
dirtyDeckIdsRef.current.delete(updated.id);
|
|
1396
|
+
}
|
|
1358
1397
|
const before = decksRef.current.find((d) => d.id === updated.id);
|
|
1359
1398
|
if (
|
|
1360
1399
|
before &&
|
|
@@ -1447,7 +1486,10 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1447
1486
|
// - Dirty deck / unsaved local create → additive merge only: surface
|
|
1448
1487
|
// agent-added slides without ever overwriting or dropping local slides.
|
|
1449
1488
|
const refetchOpenDeckIfChanged = useCallback(
|
|
1450
|
-
async (
|
|
1489
|
+
async (
|
|
1490
|
+
currentOpenId: string,
|
|
1491
|
+
options?: { clearPendingWrites?: boolean },
|
|
1492
|
+
): Promise<Deck | null> => {
|
|
1451
1493
|
const requestId =
|
|
1452
1494
|
(openDeckRequestIdByDeckRef.current.get(currentOpenId) ?? 0) + 1;
|
|
1453
1495
|
openDeckRequestIdByDeckRef.current.set(currentOpenId, requestId);
|
|
@@ -1459,6 +1501,13 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1459
1501
|
// still-pending create — nothing authoritative to reconcile.
|
|
1460
1502
|
if (!serverDeck) return null;
|
|
1461
1503
|
const clientDeck = decksRef.current.find((d) => d.id === currentOpenId);
|
|
1504
|
+
if (options?.clearPendingWrites) {
|
|
1505
|
+
lastExternalUpdateRef.current = Date.now();
|
|
1506
|
+
applyRemoteDeckUpdate(serverDeck, "Deck restored", {
|
|
1507
|
+
clearPendingWrites: true,
|
|
1508
|
+
});
|
|
1509
|
+
return serverDeck;
|
|
1510
|
+
}
|
|
1462
1511
|
|
|
1463
1512
|
const hasLocalEdits =
|
|
1464
1513
|
pendingCreateIdsRef.current.has(currentOpenId) ||
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
interface EditorShortcutEvent {
|
|
2
|
+
key: string;
|
|
3
|
+
altKey: boolean;
|
|
4
|
+
ctrlKey: boolean;
|
|
5
|
+
metaKey: boolean;
|
|
6
|
+
shiftKey: boolean;
|
|
7
|
+
repeat: boolean;
|
|
8
|
+
isComposing: boolean;
|
|
9
|
+
target: EventTarget | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isEditableSurface(target: EventTarget | null): boolean {
|
|
13
|
+
return (
|
|
14
|
+
target instanceof HTMLElement &&
|
|
15
|
+
(target.closest(
|
|
16
|
+
"input, textarea, select, [contenteditable='true'], [role='textbox']",
|
|
17
|
+
) !== null ||
|
|
18
|
+
target.isContentEditable)
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function shouldStopSlidesItalicShortcut(event: EditorShortcutEvent) {
|
|
23
|
+
if (
|
|
24
|
+
event.repeat ||
|
|
25
|
+
event.isComposing ||
|
|
26
|
+
event.key.toLowerCase() !== "i" ||
|
|
27
|
+
event.altKey ||
|
|
28
|
+
event.shiftKey ||
|
|
29
|
+
!(event.ctrlKey || event.metaKey)
|
|
30
|
+
) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isSlidesItalicEditableTarget(event: EditorShortcutEvent) {
|
|
38
|
+
return isEditableSurface(event.target);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function shouldSuppressSlidesItalicShortcut(event: EditorShortcutEvent) {
|
|
42
|
+
return (
|
|
43
|
+
shouldStopSlidesItalicShortcut(event) &&
|
|
44
|
+
!isSlidesItalicEditableTarget(event)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -76,6 +76,7 @@ import {
|
|
|
76
76
|
shouldShowDeckEditorSkeleton,
|
|
77
77
|
} from "@/lib/deck-editor-loading";
|
|
78
78
|
import { getPreset } from "@/lib/design-systems";
|
|
79
|
+
import { shouldSuppressSlidesItalicShortcut } from "@/lib/editor-shortcuts";
|
|
79
80
|
import { exportDeckToGoogleSlides } from "@/lib/export-google-slides-client";
|
|
80
81
|
import { exportDeckAsPdf } from "@/lib/export-pdf-client";
|
|
81
82
|
import { exportDeckAsPptx } from "@/lib/export-pptx-client";
|
|
@@ -784,6 +785,18 @@ export default function DeckEditor() {
|
|
|
784
785
|
document.removeEventListener("keydown", handleTextToolShortcut);
|
|
785
786
|
}, [canEdit]);
|
|
786
787
|
|
|
788
|
+
useEffect(() => {
|
|
789
|
+
const handleItalicShortcut = (event: KeyboardEvent) => {
|
|
790
|
+
if (!shouldSuppressSlidesItalicShortcut(event)) return;
|
|
791
|
+
event.preventDefault();
|
|
792
|
+
event.stopImmediatePropagation();
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
document.addEventListener("keydown", handleItalicShortcut, true);
|
|
796
|
+
return () =>
|
|
797
|
+
document.removeEventListener("keydown", handleItalicShortcut, true);
|
|
798
|
+
}, []);
|
|
799
|
+
|
|
787
800
|
// Delete key deletes the current slide
|
|
788
801
|
useEffect(() => {
|
|
789
802
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
@@ -201,6 +201,18 @@ export interface AgentSidebarProps {
|
|
|
201
201
|
chatViewTransitionHandoff?: boolean;
|
|
202
202
|
/** Namespace for persisted chat state. Use the same key as AgentChatHome. */
|
|
203
203
|
storageKey?: string;
|
|
204
|
+
/** Namespace for the persisted open/closed preference. Defaults to storageKey. */
|
|
205
|
+
openStorageKey?: string;
|
|
206
|
+
/** API base URL used by the chat surface. */
|
|
207
|
+
apiUrl?: string;
|
|
208
|
+
/** Runtime surface identity used for server-side chat capabilities. */
|
|
209
|
+
agentChatSurface?: AgentChatSurfaceKind;
|
|
210
|
+
/** Show the chat thread tab row. Default: true. */
|
|
211
|
+
showTabBar?: MultiTabAssistantChatProps["showTabBar"];
|
|
212
|
+
/** Keep inline app-opening results inside the current app chat. */
|
|
213
|
+
suppressInlineOpenApp?: AssistantChatProps["suppressInlineOpenApp"];
|
|
214
|
+
/** Placeholder shown in the chat composer. */
|
|
215
|
+
composerPlaceholder?: AssistantChatProps["composerPlaceholder"];
|
|
204
216
|
/** Open the sidebar when a chat run is active or reconnects. */
|
|
205
217
|
openOnChatRunning?: boolean;
|
|
206
218
|
/** Called when the user selects the full-view action from the chat sidebar. */
|
|
@@ -222,7 +234,7 @@ export interface AgentSidebarProps {
|
|
|
222
234
|
* Wraps app content with a toggleable agent sidebar.
|
|
223
235
|
* Use AgentToggleButton in your header to open/close it.
|
|
224
236
|
*/
|
|
225
|
-
export declare function AgentSidebar({ children, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openOnChatRunning, onFullscreenRequest, scope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, }: AgentSidebarProps): React.JSX.Element;
|
|
237
|
+
export declare function AgentSidebar({ children, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openStorageKey, apiUrl, agentChatSurface, showTabBar, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning, onFullscreenRequest, scope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, }: AgentSidebarProps): React.JSX.Element;
|
|
226
238
|
/**
|
|
227
239
|
* Focus the agent chat composer input.
|
|
228
240
|
* Opens the sidebar if closed, then focuses the text input.
|
|
@@ -722,7 +722,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
|
|
|
722
722
|
(activeTabMessageCount <= 0 && activeTab.status === "idle")) {
|
|
723
723
|
return null;
|
|
724
724
|
}
|
|
725
|
-
return (_jsx(ShareButton, { resourceType: "chat_thread", resourceId: activeTab.id, allowedRoles: ["viewer", "editor", "admin"], resourceTitle: activeTab.label || t("agentPanel.chat"), shareUrl: getChatThreadShareUrl(activeTab.id),
|
|
725
|
+
return (_jsx(ShareButton, { resourceType: "chat_thread", resourceId: activeTab.id, allowedRoles: ["viewer", "editor", "admin"], resourceTitle: activeTab.label || t("agentPanel.chat"), shareUrl: getChatThreadShareUrl(activeTab.id), triggerClassName: "h-7 px-2", defaultOpen: onCollapse && shareFromMenuOpen, onOpenChange: onCollapse ? setShareFromMenuOpen : undefined }));
|
|
726
726
|
})(), feedbackEnabled ? (_jsx(FeedbackButton, { variant: "icon", side: "bottom", align: "end", chatSessionId: activeChatSessionId, chatStorageKey: storageKey, open: feedbackOpen, onOpenChange: setFeedbackOpen, trigger: _jsx("button", { type: "button", tabIndex: -1, "aria-hidden": "true", className: "pointer-events-none absolute end-0 top-full h-px w-px opacity-0" }) })) : null, mode === "chat" && (_jsx(IconTooltip, { content: t("agentPanel.newChat"), children: _jsx("button", { onClick: addTab, "aria-label": t("agentPanel.newChat"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconPlus, { size: 14 }) }) })), !onCollapse && mode === "cli" && canUseCodeTools && (_jsx(IconTooltip, { content: t("agentPanel.newTerminal"), children: _jsx("button", { onClick: addCliTab, "aria-label": t("agentPanel.newTerminal"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconPlus, { size: 14 }) }) })), _jsxs(DropdownMenu, { open: headerMenuOpen, onOpenChange: setHeaderMenuOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { className: cn("flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", (headerMenuOpen || mode === "settings") &&
|
|
727
727
|
"bg-accent text-foreground"), "aria-label": t("agentPanel.panelOptions"), children: _jsx(IconDotsVertical, { size: 14 }) }) }), _jsxs(DropdownMenuContent, { align: "end", sideOffset: 6, className: "w-48", onCloseAutoFocus: (event) => {
|
|
728
728
|
// A sibling overlay owns focus next; restoring it to the menu
|
|
@@ -786,7 +786,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
|
|
|
786
786
|
? tabs.find((tab) => tab.id === activeTabId)
|
|
787
787
|
: undefined;
|
|
788
788
|
const canShareActiveTab = activeTab && (activeTabMessageCount > 0 || activeTab.status !== "idle");
|
|
789
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { "aria-hidden": "true", "data-agent-page-chat-fade": "", className: "pointer-events-none absolute inset-x-0 top-0 z-50 h-16 bg-gradient-to-b from-background via-background/90 to-transparent opacity-0 transition-opacity duration-150" }), _jsx("div", { className: "pointer-events-none absolute inset-x-0 top-3 z-[60] flex justify-end px-3 sm:top-4 sm:px-4", children: _jsxs("div", { className: "pointer-events-auto flex items-center gap-1", children: [canShareActiveTab ? (_jsx(ShareButton, { resourceType: "chat_thread", resourceId: activeTab.id, allowedRoles: ["viewer", "editor", "admin"], resourceTitle: activeTab.label || t("agentPanel.chat"), shareUrl: getChatThreadShareUrl(activeTab.id),
|
|
789
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { "aria-hidden": "true", "data-agent-page-chat-fade": "", className: "pointer-events-none absolute inset-x-0 top-0 z-50 h-16 bg-gradient-to-b from-background via-background/90 to-transparent opacity-0 transition-opacity duration-150" }), _jsx("div", { className: "pointer-events-none absolute inset-x-0 top-3 z-[60] flex justify-end px-3 sm:top-4 sm:px-4", children: _jsxs("div", { className: "pointer-events-auto flex items-center gap-1", children: [canShareActiveTab ? (_jsx(ShareButton, { resourceType: "chat_thread", resourceId: activeTab.id, allowedRoles: ["viewer", "editor", "admin"], resourceTitle: activeTab.label || t("agentPanel.chat"), shareUrl: getChatThreadShareUrl(activeTab.id), triggerClassName: "h-8 px-2 border border-border bg-background/95 shadow-sm backdrop-blur hover:bg-accent" })) : null, _jsxs("button", { type: "button", "data-agent-page-new-chat": "", "aria-label": t("agentPanel.newChat"), onClick: () => {
|
|
790
790
|
addTab();
|
|
791
791
|
}, className: "inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background/95 px-2.5 text-xs font-medium text-foreground shadow-sm backdrop-blur transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [_jsx(IconPlus, { size: 14 }), _jsx("span", { children: t("agentPanel.newChat") })] })] }) })] }));
|
|
792
792
|
}, [getChatThreadShareUrl, t]);
|
|
@@ -1407,14 +1407,16 @@ export function AgentChatSurface({ mode = "panel", className, defaultMode = "cha
|
|
|
1407
1407
|
* Wraps app content with a toggleable agent sidebar.
|
|
1408
1408
|
* Use AgentToggleButton in your header to open/close it.
|
|
1409
1409
|
*/
|
|
1410
|
-
export function AgentSidebar({ children, emptyStateText = "How can I help you?", suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position = "right", defaultOpen = false, animateMobile = true, animateDesktop = true, chatViewTransition = false, chatViewTransitionHandoff = false, storageKey, openOnChatRunning = false, onFullscreenRequest, scope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding = false, }) {
|
|
1410
|
+
export function AgentSidebar({ children, emptyStateText = "How can I help you?", suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position = "right", defaultOpen = false, animateMobile = true, animateDesktop = true, chatViewTransition = false, chatViewTransitionHandoff = false, storageKey, openStorageKey, apiUrl, agentChatSurface, showTabBar = true, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning = false, onFullscreenRequest, scope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding = false, }) {
|
|
1411
|
+
const sidebarOpenStorageKey = openStorageKey ?? storageKey;
|
|
1411
1412
|
const onboardingPreviewMode = useOnboardingPreviewMode();
|
|
1412
1413
|
const firstRunOnboardingGateOwnsSurface = useFirstRunOnboardingGateOwnsSurface();
|
|
1413
1414
|
const showFirstRunOnboarding = !firstRunOnboardingGateOwnsSurface &&
|
|
1414
1415
|
!suppressFirstRunOnboarding &&
|
|
1415
1416
|
(SHOW_FIRST_RUN_ONBOARDING || onboardingPreviewMode);
|
|
1416
1417
|
const initialWidth = defaultSidebarWidth ?? sidebarWidth ?? 380;
|
|
1417
|
-
const [open, setOpen] = useState(() => openOnChatRunning ||
|
|
1418
|
+
const [open, setOpen] = useState(() => openOnChatRunning ||
|
|
1419
|
+
getInitialAgentSidebarOpen(defaultOpen, sidebarOpenStorageKey));
|
|
1418
1420
|
const [presentationMode, setPresentationMode] = useState(false);
|
|
1419
1421
|
const [width, setWidth] = useState(initialWidth);
|
|
1420
1422
|
const [isWideDrawer, setIsWideDrawer] = useState(() => {
|
|
@@ -1471,15 +1473,15 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
1471
1473
|
const setOpenPersisted = useCallback((next) => {
|
|
1472
1474
|
setOpen((prev) => {
|
|
1473
1475
|
const value = typeof next === "function" ? next(prev) : next;
|
|
1474
|
-
setAgentSidebarOpenPreference(value,
|
|
1476
|
+
setAgentSidebarOpenPreference(value, sidebarOpenStorageKey);
|
|
1475
1477
|
return value;
|
|
1476
1478
|
});
|
|
1477
|
-
}, [
|
|
1479
|
+
}, [sidebarOpenStorageKey]);
|
|
1478
1480
|
const applyUrlOpenOverride = useCallback(() => {
|
|
1479
|
-
const override = consumeAgentSidebarUrlOpenOverride(
|
|
1481
|
+
const override = consumeAgentSidebarUrlOpenOverride(sidebarOpenStorageKey);
|
|
1480
1482
|
if (override !== null)
|
|
1481
1483
|
setOpenPersisted(override);
|
|
1482
|
-
}, [setOpenPersisted,
|
|
1484
|
+
}, [setOpenPersisted, sidebarOpenStorageKey]);
|
|
1483
1485
|
useEffect(() => {
|
|
1484
1486
|
applyUrlOpenOverride();
|
|
1485
1487
|
return subscribeAgentSidebarUrlChanges(applyUrlOpenOverride);
|
|
@@ -1924,11 +1926,11 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
1924
1926
|
? "desktop"
|
|
1925
1927
|
: undefined, "data-agent-sidebar-layout": panelLayout, "data-agent-sidebar-position": position, "data-agent-sidebar-state": panelOpen ? "open" : "closed", "data-agent-sidebar-resizing": isResizing ? "true" : undefined, "data-agent-sidebar-chat-handoff": chatViewTransitionHandoff ? "true" : undefined, style: chatViewTransition
|
|
1926
1928
|
? getAgentChatViewTransitionStyle(panelStyle)
|
|
1927
|
-
: panelStyle, inert: sidebarAnimationEnabled && !panelOpen ? true : undefined, "aria-hidden": sidebarAnimationEnabled && !panelOpen ? true : undefined, children: _jsx("div", { className: "agent-sidebar-panel-inner flex min-h-0 flex-1 flex-col", children: _jsx(AgentPanel, { emptyStateText: emptyStateText, suggestions: suggestions, dynamicSuggestions: dynamicSuggestions, composerToolbarSlot: composerToolbarSlot, composerSlot: composerSlot, onComposerTextChange: onComposerTextChange, imageModelMenu: imageModelMenu, threadFooterSlot: threadFooterSlot, missingApiKeySetupLayout: "sidebar", onCollapse: () => setOpenPersisted(false), onSnapTo75Percent: isMobile ? undefined : snapTo75Percent, isWideDrawer: isMobile ? false : isWideDrawer, onExitWideDrawer: isMobile ? undefined : exitWideDrawer, onFullViewRequest: onFullscreenRequest, storageKey: storageKey, scope: scope, showScopeBadge: showScopeBadge, browserTabId: browserTabId, threadUrlSync: threadUrlSync, agentPageHref: agentPageHref, allowSettingsMode: false, chatOnly: true }) }) }), showResizeHandle && isLeft && (_jsx(ResizeHandle, { position: position, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd }))] })) : null;
|
|
1929
|
+
: panelStyle, inert: sidebarAnimationEnabled && !panelOpen ? true : undefined, "aria-hidden": sidebarAnimationEnabled && !panelOpen ? true : undefined, children: _jsx("div", { className: "agent-sidebar-panel-inner flex min-h-0 flex-1 flex-col", children: _jsx(AgentPanel, { emptyStateText: emptyStateText, suggestions: suggestions, dynamicSuggestions: dynamicSuggestions, composerToolbarSlot: composerToolbarSlot, composerSlot: composerSlot, onComposerTextChange: onComposerTextChange, imageModelMenu: imageModelMenu, threadFooterSlot: threadFooterSlot, apiUrl: apiUrl, agentChatSurface: agentChatSurface, showTabBar: showTabBar, suppressInlineOpenApp: suppressInlineOpenApp, composerPlaceholder: composerPlaceholder, missingApiKeySetupLayout: "sidebar", onCollapse: () => setOpenPersisted(false), onSnapTo75Percent: isMobile ? undefined : snapTo75Percent, isWideDrawer: isMobile ? false : isWideDrawer, onExitWideDrawer: isMobile ? undefined : exitWideDrawer, onFullViewRequest: onFullscreenRequest, storageKey: storageKey, scope: scope, showScopeBadge: showScopeBadge, browserTabId: browserTabId, threadUrlSync: threadUrlSync, agentPageHref: agentPageHref, allowSettingsMode: false, chatOnly: true }) }) }), showResizeHandle && isLeft && (_jsx(ResizeHandle, { position: position, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd }))] })) : null;
|
|
1928
1930
|
const drawerPlaceholder = wideDrawerEnabled && !presentationMode && panelOpen ? (_jsx("div", { "aria-hidden": "true", className: "agent-sidebar-drawer-placeholder shrink-0", "data-agent-sidebar-placeholder": "true", style: { width: drawerPlaceholderWidth + 1 } })) : null;
|
|
1929
1931
|
return (_jsx(AgentSidebarOnboardingContext.Provider, { value: true, children: _jsxs(RealtimeVoiceModeProvider, { browserTabId: browserTabId, children: [showFirstRunOnboarding && (_jsx(Suspense, { fallback: null, children: _jsx(FirstRunOnboarding, {}) })), _jsxs("div", { className: "agent-sidebar-shell flex min-w-0 flex-1 h-screen overflow-hidden", "data-agent-sidebar-position": position, "data-agent-sidebar-resizing": isResizing ? "true" : undefined, children: [isMobile &&
|
|
1930
1932
|
!presentationMode &&
|
|
1931
|
-
(mobileAnimationEnabled ? shouldRenderPanel : open) && (_jsx("div", { className: cn("agent-sidebar-backdrop fixed inset-0 bg-foreground/40", mobileAnimationEnabled && !panelOpen && "pointer-events-none"), "data-agent-sidebar-animation": mobileAnimationEnabled ? "mobile" : undefined, "data-agent-sidebar-state": panelOpen ? "open" : "closed", style: { zIndex: SIDEBAR_OVERLAY_Z_INDEX - 1 }, onClick: () => setOpenPersisted(false) })), shouldMountPanel ? _jsx(URLSync, { browserTabId: browserTabId }) : null, isLeft && !presentationMode ? sidebar : null, isLeft && !presentationMode ? drawerPlaceholder : null, _jsx("div", { className: "agent-sidebar-main-surface flex flex-1 flex-col overflow-auto min-w-0", "data-agent-sidebar-main-position": position, "data-agent-sidebar-main-state": !isMobile && !presentationMode && panelOpen ? "open" : "closed", "data-agent-sidebar-resizing": isResizing ? "true" : undefined, children: _jsx(ScreenRefreshBoundary, { children: children }) }), !isLeft && !presentationMode ? drawerPlaceholder : null, !isLeft && !presentationMode ? sidebar : null] })] }) }));
|
|
1933
|
+
(mobileAnimationEnabled ? shouldRenderPanel : open) && (_jsx("div", { className: cn("agent-sidebar-backdrop fixed inset-0 bg-foreground/40", mobileAnimationEnabled && !panelOpen && "pointer-events-none"), "data-agent-sidebar-animation": mobileAnimationEnabled ? "mobile" : undefined, "data-agent-sidebar-state": panelOpen ? "open" : "closed", style: { zIndex: SIDEBAR_OVERLAY_Z_INDEX - 1 }, onClick: () => setOpenPersisted(false) })), shouldMountPanel ? _jsx(URLSync, { browserTabId: browserTabId }) : null, isResizing ? (_jsx("div", { "aria-hidden": "true", className: "agent-sidebar-resize-overlay" })) : null, isLeft && !presentationMode ? sidebar : null, isLeft && !presentationMode ? drawerPlaceholder : null, _jsx("div", { className: "agent-sidebar-main-surface flex flex-1 flex-col overflow-auto min-w-0", "data-agent-sidebar-main-position": position, "data-agent-sidebar-main-state": !isMobile && !presentationMode && panelOpen ? "open" : "closed", "data-agent-sidebar-resizing": isResizing ? "true" : undefined, children: _jsx(ScreenRefreshBoundary, { children: children }) }), !isLeft && !presentationMode ? drawerPlaceholder : null, !isLeft && !presentationMode ? sidebar : null] })] }) }));
|
|
1932
1934
|
}
|
|
1933
1935
|
/**
|
|
1934
1936
|
* Focus the agent chat composer input.
|
|
@@ -4171,13 +4171,33 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
4171
4171
|
}
|
|
4172
4172
|
void addToQueue(suggestion);
|
|
4173
4173
|
}, className: "w-full px-2 py-1 text-center text-[13px] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: suggestion }, suggestion))) })) : null, showInlineEmptyThreadFooterSlot ? (_jsx("div", { className: "agent-thread-footer-slot agent-thread-footer-slot--empty", children: resolvedThreadFooterSlot })) : null] })) : (_jsxs(MessageScrollerContent, { className: "agent-thread-content gap-4 px-4 py-4", children: [_jsx(AssistantMessageListErrorBoundary, { resetKey: messageListResetKey, children: _jsx(UserStoppedRunContext.Provider, { value: wasRecentlyStoppedRun, children: _jsx(ThreadPrimitive.Messages
|
|
4174
|
-
//
|
|
4175
|
-
//
|
|
4176
|
-
//
|
|
4177
|
-
|
|
4174
|
+
// Deliberately NOT keyed on part structure. Doing that
|
|
4175
|
+
// remounted the whole transcript every time a tool call
|
|
4176
|
+
// started or a placeholder id was rewritten — a flash and a
|
|
4177
|
+
// lost scroll position in the middle of an answer. The
|
|
4178
|
+
// error boundary above is the mechanism for assistant-ui's
|
|
4179
|
+
// stale tap-resource errors: it catches them, clears, and
|
|
4180
|
+
// retries, and its retry signature includes the reset key so
|
|
4181
|
+
// a genuinely new structure always gets a fresh budget.
|
|
4182
|
+
// `assistant-ui-part-churn.spec.tsx` drives append, mutate,
|
|
4183
|
+
// rename and splice through both the import and streaming
|
|
4184
|
+
// paths and records that no such error occurs.
|
|
4185
|
+
, {
|
|
4186
|
+
// Deliberately NOT keyed on part structure. Doing that
|
|
4187
|
+
// remounted the whole transcript every time a tool call
|
|
4188
|
+
// started or a placeholder id was rewritten — a flash and a
|
|
4189
|
+
// lost scroll position in the middle of an answer. The
|
|
4190
|
+
// error boundary above is the mechanism for assistant-ui's
|
|
4191
|
+
// stale tap-resource errors: it catches them, clears, and
|
|
4192
|
+
// retries, and its retry signature includes the reset key so
|
|
4193
|
+
// a genuinely new structure always gets a fresh budget.
|
|
4194
|
+
// `assistant-ui-part-churn.spec.tsx` drives append, mutate,
|
|
4195
|
+
// rename and splice through both the import and streaming
|
|
4196
|
+
// paths and records that no such error occurs.
|
|
4197
|
+
components: {
|
|
4178
4198
|
UserMessage: AssistantChatUserMessageItem,
|
|
4179
4199
|
AssistantMessage: AssistantChatAssistantMessageItem,
|
|
4180
|
-
} }
|
|
4200
|
+
} }) }) }), visibleLoopLimit && !showRunningInUI && (_jsx(MessageScrollerItem, { children: _jsx(LoopLimitContinueCard, { info: visibleLoopLimit, onContinue: () => {
|
|
4181
4201
|
setShowContinue(false);
|
|
4182
4202
|
setLoopLimitInfo(null);
|
|
4183
4203
|
addToQueue("Continue from where you left off.", undefined, undefined, undefined, undefined, "queued", "continue");
|
|
@@ -44,6 +44,13 @@ export function HighlightedCodeBlock({ code, lang, containerClass, streaming = f
|
|
|
44
44
|
const renderedHashRef = useRef(null);
|
|
45
45
|
const debounceTimerRef = useRef(null);
|
|
46
46
|
const cancelledRef = useRef(false);
|
|
47
|
+
// Whether this block has already put code on screen. Reserving space
|
|
48
|
+
// invisibly is right on FIRST paint — it avoids an unhighlighted→highlighted
|
|
49
|
+
// flash — but once code is visible, hiding it again to wait for Shiki blanks
|
|
50
|
+
// a code box the user was reading. That is what happened whenever the block
|
|
51
|
+
// lost its html (a re-highlight after the content changed): readable code
|
|
52
|
+
// went blank mid-answer.
|
|
53
|
+
const hasPaintedRef = useRef(false);
|
|
47
54
|
useEffect(() => {
|
|
48
55
|
cancelledRef.current = false;
|
|
49
56
|
return () => {
|
|
@@ -108,11 +115,14 @@ export function HighlightedCodeBlock({ code, lang, containerClass, streaming = f
|
|
|
108
115
|
}
|
|
109
116
|
}, [code, lang, streaming, loadHighlighter]);
|
|
110
117
|
if (html) {
|
|
118
|
+
hasPaintedRef.current = true;
|
|
111
119
|
return (_jsx("div", { className: containerClass, dangerouslySetInnerHTML: { __html: html } }));
|
|
112
120
|
}
|
|
113
121
|
// Streaming: show plain growing text (previous html already kept above when
|
|
114
122
|
// available). Non-streaming first paint: reserve space invisibly so we never
|
|
115
|
-
// flash unhighlighted → highlighted.
|
|
116
|
-
const showPlain = streaming || failed;
|
|
123
|
+
// flash unhighlighted → highlighted. Once painted, never hide again.
|
|
124
|
+
const showPlain = streaming || failed || hasPaintedRef.current;
|
|
125
|
+
if (showPlain)
|
|
126
|
+
hasPaintedRef.current = true;
|
|
117
127
|
return (_jsx("div", { className: containerClass, "aria-busy": !showPlain && !failed, children: _jsx("pre", { children: _jsx("code", { className: lang ? `language-${lang}` : undefined, style: showPlain ? undefined : { visibility: "hidden" }, children: code }) }) }));
|
|
118
128
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { MemoryRouter as AgentChatMemoryRouter } from "react-router";
|
|
1
2
|
export { AgentAskPopover, type AgentAskPopoverProps, } from "../AgentAskPopover.js";
|
|
2
3
|
export { addContextToAgentChat, appendAgentChatContextToMessage, clearAgentChatContext, formatAgentChatContextItemsForPrompt, insertAgentComposerReference, listAgentChatContext, normalizeAgentComposerReference, refreshAgentChatContext, removeAgentChatContextItem, requestAgentChatThreadOpen, requestAgentTaskOpen, sendToAgentChat, sendToAgentChatAndConfirm, reportAgentChatSubmitResult, AGENT_CHAT_SUBMIT_RESULT_EVENT, parseSubmitChatMessage, setAgentChatContextItem, setContextToAgentChat, generateTabId, type ParsedSubmitChat, type AgentChatOpenTaskRequest, type AgentChatOpenThreadRequest, type AgentChatContextItem, type AgentChatContextMessage, type AgentChatContextMutationOptions, type AgentChatContextRemoveOptions, type AgentChatContextSetOptions, type AgentChatContextState, type AgentChatMessage, type AgentChatSubmitResult, type SendToAgentChatAndConfirmResult, type AgentComposerReference, type AgentComposerReferenceInsertOptions, type AgentComposerReferenceInsertPayload, } from "../agent-chat.js";
|
|
3
4
|
export { saveAgentEngineApiKey, saveAgentEngineProviderSettings, type AgentEngineProvider, type SaveAgentEngineApiKeyOptions, type SaveAgentEngineProviderSettingsOptions, } from "../agent-engine-key.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { MemoryRouter as AgentChatMemoryRouter } from "react-router";
|
|
1
2
|
export { AgentAskPopover, } from "../AgentAskPopover.js";
|
|
2
3
|
export { addContextToAgentChat, appendAgentChatContextToMessage, clearAgentChatContext, formatAgentChatContextItemsForPrompt, insertAgentComposerReference, listAgentChatContext, normalizeAgentComposerReference, refreshAgentChatContext, removeAgentChatContextItem, requestAgentChatThreadOpen, requestAgentTaskOpen, sendToAgentChat, sendToAgentChatAndConfirm, reportAgentChatSubmitResult, AGENT_CHAT_SUBMIT_RESULT_EVENT, parseSubmitChatMessage, setAgentChatContextItem, setContextToAgentChat, generateTabId, } from "../agent-chat.js";
|
|
3
4
|
export { saveAgentEngineApiKey, saveAgentEngineProviderSettings, } from "../agent-engine-key.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// Owns: lazy react-markdown/shiki loaders, SmoothMarkdownText, MarkdownText,
|
|
3
3
|
// HighlightedCodeBlock wrapper, and the markdownComponents/markdownUrlTransform
|
|
4
4
|
// used by every markdown render path in AssistantChat.
|
|
@@ -476,13 +476,26 @@ export function SmoothMarkdownText({ text, streaming, resetKey, statusType = "co
|
|
|
476
476
|
const visibleText = useSmoothStreamingText(text, shouldAnimate, resetKey);
|
|
477
477
|
const ReactMarkdown = markdownModule?.default;
|
|
478
478
|
const gfm = remarkGfmFn;
|
|
479
|
-
|
|
479
|
+
// Split whether or not the turn is still streaming. Rendering the finished
|
|
480
|
+
// message as one whole-document ReactMarkdown instead swapped the element
|
|
481
|
+
// tree the instant `streaming` went false, so React unmounted every block and
|
|
482
|
+
// rebuilt it: code blocks re-highlighted, heights collapsed for a frame, and
|
|
483
|
+
// the scroll jumped — the "flash at the end" report. The split is faithful to
|
|
484
|
+
// a whole-document parse (see markdown-block-split.ts), so one tree serves
|
|
485
|
+
// both phases and nothing is rebuilt when streaming stops.
|
|
486
|
+
const markdownBlocks = useMemo(() => splitMarkdownBlocks(visibleText), [visibleText]);
|
|
487
|
+
// The tail renders through the same component as completed blocks, so when it
|
|
488
|
+
// is promoted to a completed block React matches the same type at the same
|
|
489
|
+
// key and keeps its DOM instead of remounting that paragraph.
|
|
490
|
+
const renderedBlocks = useMemo(() => markdownBlocks.tail
|
|
491
|
+
? [...markdownBlocks.completedBlocks, markdownBlocks.tail]
|
|
492
|
+
: markdownBlocks.completedBlocks, [markdownBlocks]);
|
|
480
493
|
useEffect(() => {
|
|
481
494
|
if (!onRevealComplete || !shouldAnimate || visibleText !== text)
|
|
482
495
|
return;
|
|
483
496
|
onRevealComplete();
|
|
484
497
|
}, [onRevealComplete, shouldAnimate, text, visibleText]);
|
|
485
|
-
return (_jsx("div", { className: "agent-markdown break-words", "data-status": statusType, "data-streaming": streaming ? "true" : undefined, children: mdReady && ReactMarkdown && gfm ? (
|
|
498
|
+
return (_jsx("div", { className: "agent-markdown break-words", "data-status": statusType, "data-streaming": streaming ? "true" : undefined, children: mdReady && ReactMarkdown && gfm ? (renderedBlocks.map((blockText, index) => (_jsx(MemoizedMarkdownBlock, { blockText: blockText }, index)))) : (_jsx("span", { style: { whiteSpace: "pre-wrap" }, children: visibleText })) }));
|
|
486
499
|
}
|
|
487
500
|
// ─── MarkdownText ──────────────────────────────────────────────────────────────
|
|
488
501
|
export function MarkdownText() {
|
|
@@ -98,7 +98,7 @@ export const ChatFirstAppsRail = memo(function ChatFirstAppsRail({ apps, activeA
|
|
|
98
98
|
persistLayout({ ...layout, orderedIds: nextOrder });
|
|
99
99
|
}
|
|
100
100
|
if (collapsed) {
|
|
101
|
-
return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "flex flex-col items-center gap-1 px-1.5 pt-2", "aria-label": copy("workspaceApps"), children: [
|
|
101
|
+
return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "flex flex-col items-center gap-1 px-1.5 pt-2", "aria-label": copy("workspaceApps"), children: [loading && apps.length === 0
|
|
102
102
|
? [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "size-9 rounded-md" }, index)))
|
|
103
103
|
: visibleApps.map((app) => (_jsx("button", { type: "button", "data-chat-first-app": true, "data-app-id": app.id, className: cn("flex size-9 items-center justify-center rounded-md", activeAppId === app.id
|
|
104
104
|
? "text-sidebar-foreground"
|
|
@@ -27,6 +27,6 @@ function MessageScrollerItem(props) {
|
|
|
27
27
|
function MessageScrollerButton({ className, children, render, ...props }) {
|
|
28
28
|
const t = useT();
|
|
29
29
|
return (_jsx(ShadcnMessageScroller.Button, { render: render ??
|
|
30
|
-
((buttonProps) => (_jsx("div", { className: "
|
|
30
|
+
((buttonProps) => (_jsx("div", { className: "pointer-events-none absolute inset-x-0 bottom-2 z-10 flex justify-center", children: _jsx("button", { ...buttonProps, className: cn("pointer-events-auto flex h-7 w-7 items-center justify-center rounded-full border border-border bg-background shadow-sm hover:bg-accent", "data-[active=false]:hidden", className, buttonProps.className), "aria-label": t("agentChat.composer.scrollToBottom"), children: children ?? (_jsx(IconChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })) }) }))), ...props }));
|
|
31
31
|
}
|
|
32
32
|
export { MessageScroller, MessageScrollerButton, MessageScrollerContent, MessageScrollerItem, MessageScrollerProvider, MessageScrollerViewport, useMessageScroller, useMessageScrollerScrollable, useMessageScrollerVisibility, };
|