@agent-native/core 0.149.0 → 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 +139 -10
- package/corpus/templates/slides/actions/save-deck.ts +24 -1
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +46 -0
- package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +194 -3
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +10 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +134 -618
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +74 -63
- 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 +29 -7
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +45 -0
- 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/export-pdf-client.ts +84 -25
- package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +11 -0
- 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 +16 -27
- package/corpus/templates/slides/app/pages/Index.tsx +256 -98
- package/corpus/templates/slides/changelog/2026-08-08-google-slides-export-now-tells-you-why-a-deck-fell-back-to-a.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-delete-key-now-removes-a-selected-image.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-editing-a-slide-in-an-imported-deck-no-longer-fails-to-save.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-fixed-pdf-and-custom-size-powerpoint-imports-rendering-with-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-images-from-sites-without-cors-no-longer-export-blank.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-pdf-headings-no-longer-lose-the-space-between-color.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-slide-text-no-longer-renders-too-large-for-its-box.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-large-decks-no-longer-render-slowly-or-glitch-in-the-editor.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-revise-highlighted-slide-text-with-ai.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-the-deck-editor-no-longer-stays-dimmed-after-the-window-narro.md +6 -0
- 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/handlers/import/html-converter.ts +115 -22
- package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +11 -2
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/slides/server/lib/fetch-remote-image.ts +192 -0
- package/corpus/templates/slides/server/lib/image-proxy-url.ts +192 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -3
- package/corpus/templates/slides/server/routes/api/image-proxy.get.ts +65 -0
- 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/progress/RunsTray.js +14 -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/integrations/webhook-handler.js +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- 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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sendToAgentChatAndConfirm } from "@agent-native/core/client/agent-chat";
|
|
1
2
|
import { useT } from "@agent-native/core/client/i18n";
|
|
2
3
|
import {
|
|
3
4
|
IconBold,
|
|
@@ -6,11 +7,15 @@ import {
|
|
|
6
7
|
IconStrikethrough,
|
|
7
8
|
IconLink,
|
|
8
9
|
IconPalette,
|
|
10
|
+
IconPencilStar,
|
|
9
11
|
IconCheck,
|
|
10
12
|
IconX,
|
|
13
|
+
IconArrowUp,
|
|
14
|
+
IconLoader2,
|
|
11
15
|
} from "@tabler/icons-react";
|
|
12
16
|
import { useEffect, useState, useRef } from "react";
|
|
13
17
|
import { createPortal } from "react-dom";
|
|
18
|
+
import { toast } from "sonner";
|
|
14
19
|
|
|
15
20
|
import {
|
|
16
21
|
Tooltip,
|
|
@@ -22,6 +27,17 @@ import { shortcutLabel } from "@/lib/utils";
|
|
|
22
27
|
interface BlockBubbleMenuProps {
|
|
23
28
|
/** The element currently in contentEditable mode. Menu only shows while selection is inside it. */
|
|
24
29
|
editingEl: HTMLElement | null;
|
|
30
|
+
/** Slide the edited block belongs to, so an AI revision can target it. */
|
|
31
|
+
slideId?: string;
|
|
32
|
+
/** Deck that owns the slide — pins the revision to the right deck. */
|
|
33
|
+
deckId?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Ends the inline edit session and persists whatever is in the DOM now.
|
|
36
|
+
* Required before handing work to the agent: otherwise the still-open
|
|
37
|
+
* contentEditable serializes its stale text on the user's next click and
|
|
38
|
+
* overwrites the revision the agent just wrote.
|
|
39
|
+
*/
|
|
40
|
+
onCommitInlineEdit?: () => void;
|
|
25
41
|
}
|
|
26
42
|
|
|
27
43
|
interface Position {
|
|
@@ -44,32 +60,89 @@ const COLORS = [
|
|
|
44
60
|
"#EF4444",
|
|
45
61
|
];
|
|
46
62
|
|
|
63
|
+
/** Shown above the input so the user can confirm what the agent will rewrite. */
|
|
64
|
+
const AI_TARGET_PREVIEW_LIMIT = 160;
|
|
65
|
+
|
|
66
|
+
const AI_SEND_BUTTON_CLASS =
|
|
67
|
+
// guard:allow-raw-color — same accent as the link Apply button below.
|
|
68
|
+
"rounded p-1.5 text-[#609FF8] hover:bg-accent disabled:pointer-events-none disabled:opacity-40";
|
|
69
|
+
|
|
70
|
+
export function buildReviseSelectionPrompt({
|
|
71
|
+
selectedText,
|
|
72
|
+
instruction,
|
|
73
|
+
slideId,
|
|
74
|
+
deckId,
|
|
75
|
+
}: {
|
|
76
|
+
selectedText: string;
|
|
77
|
+
instruction: string;
|
|
78
|
+
slideId?: string;
|
|
79
|
+
deckId?: string;
|
|
80
|
+
}): string {
|
|
81
|
+
// The deck is named explicitly rather than left to "the current slide": the
|
|
82
|
+
// request is queued, and if the user opens another deck before the agent
|
|
83
|
+
// runs, an implicit target would pair this slide id with the wrong deck.
|
|
84
|
+
const target = [
|
|
85
|
+
deckId ? `Deck id: \`${deckId}\`` : null,
|
|
86
|
+
slideId ? `Slide id: \`${slideId}\`` : null,
|
|
87
|
+
].filter((line) => line !== null);
|
|
88
|
+
|
|
89
|
+
return [
|
|
90
|
+
`Revise this exact text:`,
|
|
91
|
+
``,
|
|
92
|
+
`"""`,
|
|
93
|
+
selectedText,
|
|
94
|
+
`"""`,
|
|
95
|
+
``,
|
|
96
|
+
`How to revise it: ${instruction}`,
|
|
97
|
+
...(target.length > 0 ? [``, ...target] : []),
|
|
98
|
+
``,
|
|
99
|
+
`Read that slide first with \`view-screen\`, then make one bounded \`update-slide --fullContent\` edit that replaces only the quoted text. Leave the surrounding HTML, inline styles, and layout untouched, and keep the replacement close to the original length so the slide still fits its canvas.`,
|
|
100
|
+
]
|
|
101
|
+
.filter((line) => line !== null)
|
|
102
|
+
.join("\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
47
105
|
/**
|
|
48
106
|
* Floating formatting toolbar for contentEditable text blocks. Shows on
|
|
49
107
|
* non-empty selection inside the editing element and applies inline
|
|
50
108
|
* formatting (bold, italic, underline, strike, link, color) directly to
|
|
51
109
|
* the DOM. Designed to work with the in-place per-block editing in
|
|
52
110
|
* SlideEditor — it never mutates anything outside the editing element.
|
|
111
|
+
*
|
|
112
|
+
* The "Revise with AI" action is the exception: it does not touch the DOM.
|
|
113
|
+
* It hands the selected text plus the user's instruction to the agent, which
|
|
114
|
+
* rewrites the slide through `update-slide`.
|
|
53
115
|
*/
|
|
54
|
-
export function BlockBubbleMenu({
|
|
116
|
+
export function BlockBubbleMenu({
|
|
117
|
+
editingEl,
|
|
118
|
+
slideId,
|
|
119
|
+
deckId,
|
|
120
|
+
onCommitInlineEdit,
|
|
121
|
+
}: BlockBubbleMenuProps) {
|
|
55
122
|
const t = useT();
|
|
56
123
|
const [pos, setPos] = useState<Position | null>(null);
|
|
57
124
|
const [showColors, setShowColors] = useState(false);
|
|
58
125
|
const [showLinkInput, setShowLinkInput] = useState(false);
|
|
59
126
|
const [linkValue, setLinkValue] = useState("");
|
|
127
|
+
const [showAiInput, setShowAiInput] = useState(false);
|
|
128
|
+
const [aiInstruction, setAiInstruction] = useState("");
|
|
129
|
+
const [aiTargetText, setAiTargetText] = useState("");
|
|
130
|
+
const [aiSending, setAiSending] = useState(false);
|
|
60
131
|
const savedRangeRef = useRef<Range | null>(null);
|
|
61
132
|
// True while a popup/input has the user's focus — keeps the menu pinned
|
|
62
133
|
// even when the contentEditable selection collapses behind the scenes.
|
|
63
134
|
const interactingRef = useRef(false);
|
|
64
135
|
useEffect(() => {
|
|
65
|
-
interactingRef.current = showColors || showLinkInput;
|
|
66
|
-
}, [showColors, showLinkInput]);
|
|
136
|
+
interactingRef.current = showColors || showLinkInput || showAiInput;
|
|
137
|
+
}, [showColors, showLinkInput, showAiInput]);
|
|
67
138
|
|
|
68
139
|
// Hide menu when the editing element changes
|
|
69
140
|
useEffect(() => {
|
|
70
141
|
setPos(null);
|
|
71
142
|
setShowColors(false);
|
|
72
143
|
setShowLinkInput(false);
|
|
144
|
+
setShowAiInput(false);
|
|
145
|
+
setAiInstruction("");
|
|
73
146
|
}, [editingEl]);
|
|
74
147
|
|
|
75
148
|
// Track selection and position the menu
|
|
@@ -158,6 +231,61 @@ export function BlockBubbleMenu({ editingEl }: BlockBubbleMenuProps) {
|
|
|
158
231
|
setLinkValue("");
|
|
159
232
|
};
|
|
160
233
|
|
|
234
|
+
const openAiInput = () => {
|
|
235
|
+
if (showAiInput) {
|
|
236
|
+
setShowAiInput(false);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// Snapshot the text now: opening the input moves focus out of the
|
|
240
|
+
// contentEditable and the live selection collapses.
|
|
241
|
+
const selected = savedRangeRef.current?.toString().trim() ?? "";
|
|
242
|
+
if (!selected) return;
|
|
243
|
+
setAiTargetText(selected);
|
|
244
|
+
setAiInstruction("");
|
|
245
|
+
interactingRef.current = true;
|
|
246
|
+
setShowAiInput(true);
|
|
247
|
+
setShowColors(false);
|
|
248
|
+
setShowLinkInput(false);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const submitAiRevision = async () => {
|
|
252
|
+
const instruction = aiInstruction.trim();
|
|
253
|
+
if (!instruction || !aiTargetText || aiSending) return;
|
|
254
|
+
|
|
255
|
+
// Close the inline edit first. The block is still a live contentEditable
|
|
256
|
+
// session; leaving it open means the next click away serializes the old
|
|
257
|
+
// text over whatever the agent writes.
|
|
258
|
+
onCommitInlineEdit?.();
|
|
259
|
+
|
|
260
|
+
setAiSending(true);
|
|
261
|
+
try {
|
|
262
|
+
const delivery = await sendToAgentChatAndConfirm({
|
|
263
|
+
message: buildReviseSelectionPrompt({
|
|
264
|
+
selectedText: aiTargetText,
|
|
265
|
+
instruction,
|
|
266
|
+
slideId,
|
|
267
|
+
deckId,
|
|
268
|
+
}),
|
|
269
|
+
submit: true,
|
|
270
|
+
chatTarget: "local",
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (!delivery.delivered) {
|
|
274
|
+
// Keep the typed instruction so the user can retry without retyping.
|
|
275
|
+
toast.error(t("raw.sendToAgent"), {
|
|
276
|
+
description: delivery.reason ?? "The agent did not receive this.",
|
|
277
|
+
});
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
toast.success(t("raw.sentToAgent"), { description: instruction });
|
|
282
|
+
setShowAiInput(false);
|
|
283
|
+
setAiInstruction("");
|
|
284
|
+
} finally {
|
|
285
|
+
setAiSending(false);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
161
289
|
return createPortal(
|
|
162
290
|
<div
|
|
163
291
|
data-block-bubble-menu="true"
|
|
@@ -168,6 +296,13 @@ export function BlockBubbleMenu({ editingEl }: BlockBubbleMenuProps) {
|
|
|
168
296
|
e.preventDefault();
|
|
169
297
|
}}
|
|
170
298
|
>
|
|
299
|
+
<ToolbarButton
|
|
300
|
+
icon={IconPencilStar}
|
|
301
|
+
tooltip="Revise with AI"
|
|
302
|
+
onClick={openAiInput}
|
|
303
|
+
active={showAiInput}
|
|
304
|
+
/>
|
|
305
|
+
<div className="w-px h-4 bg-border mx-0.5" />
|
|
171
306
|
<ToolbarButton
|
|
172
307
|
icon={IconBold}
|
|
173
308
|
tooltip={`Bold (${shortcutLabel("cmd+b")})`}
|
|
@@ -199,6 +334,7 @@ export function BlockBubbleMenu({ editingEl }: BlockBubbleMenuProps) {
|
|
|
199
334
|
if (!showColors) interactingRef.current = true;
|
|
200
335
|
setShowColors((v) => !v);
|
|
201
336
|
setShowLinkInput(false);
|
|
337
|
+
setShowAiInput(false);
|
|
202
338
|
}}
|
|
203
339
|
active={showColors}
|
|
204
340
|
/>
|
|
@@ -229,9 +365,64 @@ export function BlockBubbleMenu({ editingEl }: BlockBubbleMenuProps) {
|
|
|
229
365
|
if (!showLinkInput) interactingRef.current = true;
|
|
230
366
|
setShowLinkInput((v) => !v);
|
|
231
367
|
setShowColors(false);
|
|
368
|
+
setShowAiInput(false);
|
|
232
369
|
}}
|
|
233
370
|
active={showLinkInput}
|
|
234
371
|
/>
|
|
372
|
+
{showAiInput && (
|
|
373
|
+
<div
|
|
374
|
+
data-ai-revise-input="true"
|
|
375
|
+
// Own mousedown handler: the toolbar above blocks the default to keep
|
|
376
|
+
// the contentEditable focused, which would also stop this input from
|
|
377
|
+
// ever receiving a caret.
|
|
378
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
379
|
+
className="absolute top-full left-1/2 -translate-x-1/2 mt-1 w-80 p-2 rounded-lg bg-popover border border-border shadow-2xl shadow-black/60"
|
|
380
|
+
>
|
|
381
|
+
<p className="mb-1.5 line-clamp-2 text-[11px] leading-snug text-muted-foreground">
|
|
382
|
+
{aiTargetText.length > AI_TARGET_PREVIEW_LIMIT
|
|
383
|
+
? `${aiTargetText.slice(0, AI_TARGET_PREVIEW_LIMIT)}…`
|
|
384
|
+
: aiTargetText}
|
|
385
|
+
</p>
|
|
386
|
+
<div className="flex items-end gap-1">
|
|
387
|
+
<textarea
|
|
388
|
+
value={aiInstruction}
|
|
389
|
+
onChange={(e) => setAiInstruction(e.target.value)}
|
|
390
|
+
onKeyDown={(e) => {
|
|
391
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
392
|
+
e.preventDefault();
|
|
393
|
+
void submitAiRevision();
|
|
394
|
+
} else if (e.key === "Escape") {
|
|
395
|
+
e.preventDefault();
|
|
396
|
+
setShowAiInput(false);
|
|
397
|
+
}
|
|
398
|
+
}}
|
|
399
|
+
placeholder={t("raw.tellAgentDo")}
|
|
400
|
+
rows={2}
|
|
401
|
+
disabled={aiSending}
|
|
402
|
+
className="flex-1 resize-none rounded border border-border bg-muted px-2 py-1.5 text-xs text-foreground outline-none focus:border-ring disabled:opacity-60"
|
|
403
|
+
autoFocus
|
|
404
|
+
/>
|
|
405
|
+
<Tooltip>
|
|
406
|
+
<TooltipTrigger asChild>
|
|
407
|
+
<button
|
|
408
|
+
type="button"
|
|
409
|
+
onClick={() => void submitAiRevision()}
|
|
410
|
+
disabled={!aiInstruction.trim() || aiSending}
|
|
411
|
+
aria-label={t("raw.sendToAgent")}
|
|
412
|
+
className={AI_SEND_BUTTON_CLASS}
|
|
413
|
+
>
|
|
414
|
+
{aiSending ? (
|
|
415
|
+
<IconLoader2 className="w-3.5 h-3.5 animate-spin" />
|
|
416
|
+
) : (
|
|
417
|
+
<IconArrowUp className="w-3.5 h-3.5" />
|
|
418
|
+
)}
|
|
419
|
+
</button>
|
|
420
|
+
</TooltipTrigger>
|
|
421
|
+
<TooltipContent>{t("raw.sendToAgent")}</TooltipContent>
|
|
422
|
+
</Tooltip>
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
)}
|
|
235
426
|
{showLinkInput && (
|
|
236
427
|
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-1 flex items-center gap-1 p-1 rounded-lg bg-popover border border-border shadow-2xl shadow-black/60">
|
|
237
428
|
<input
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
TooltipTrigger,
|
|
30
30
|
} from "@/components/ui/tooltip";
|
|
31
31
|
import type { Slide } from "@/context/DeckContext";
|
|
32
|
-
import type
|
|
32
|
+
import { getAspectRatioDims, type AspectRatio } from "@/lib/aspect-ratios";
|
|
33
33
|
import { TAB_ID } from "@/lib/tab-id";
|
|
34
34
|
|
|
35
35
|
import type { DesignSystemData } from "../../../shared/api";
|
|
@@ -174,6 +174,8 @@ function SortableSlideThumb({
|
|
|
174
174
|
opacity: isDragging ? 0.5 : 1,
|
|
175
175
|
};
|
|
176
176
|
|
|
177
|
+
const thumbDims = getAspectRatioDims(aspectRatio);
|
|
178
|
+
|
|
177
179
|
return (
|
|
178
180
|
<div ref={setNodeRef} style={style}>
|
|
179
181
|
<button
|
|
@@ -213,6 +215,11 @@ function SortableSlideThumb({
|
|
|
213
215
|
|
|
214
216
|
{/* Thumbnail */}
|
|
215
217
|
<div className="flex-1 min-w-0">
|
|
218
|
+
{/* Each thumbnail paints a full-resolution slide canvas scaled down by
|
|
219
|
+
* transform, so a long rail keeps dozens of large layers live and the
|
|
220
|
+
* browser drops frames or paints stale tiles. `content-visibility`
|
|
221
|
+
* lets it skip everything below the fold; `aspect-ratio` keeps the row
|
|
222
|
+
* the right height while its contents are skipped. */}
|
|
216
223
|
<div
|
|
217
224
|
className="w-full overflow-hidden rounded border"
|
|
218
225
|
style={{
|
|
@@ -220,6 +227,8 @@ function SortableSlideThumb({
|
|
|
220
227
|
presenceUsers.length > 0
|
|
221
228
|
? presenceUsers[0].color + "66"
|
|
222
229
|
: "rgba(255,255,255,0.06)",
|
|
230
|
+
aspectRatio: `${thumbDims.width} / ${thumbDims.height}`,
|
|
231
|
+
contentVisibility: "auto",
|
|
223
232
|
}}
|
|
224
233
|
>
|
|
225
234
|
<SlideRenderer
|