@kywk/open-slide-core 2.3.1 → 2.3.3
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/dist/{build-CZ6Qtrng.js → build-46dO14h1.js} +1 -1
- package/dist/cli/bin.js +4 -4
- package/dist/{config-CpmGqpQi.d.ts → config-DJ8ZOe7C.d.ts} +6 -2
- package/dist/{config-B5LArqpp.js → config-KyYvWqq-.js} +2061 -355
- package/dist/{dev-CCgdakbZ.js → dev-C0n-o0-c.js} +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +73 -32
- package/dist/locale/index.d.ts +1 -1
- package/dist/locale/index.js +1 -1
- package/dist/{preview-AwAqvG17.js → preview-CDWAGSfB.js} +1 -1
- package/dist/{types-CvAqFqsY.d.ts → types-tdaXSyah.d.ts} +111 -0
- package/dist/{update-C_RlnKu4.js → update-CqZqQRQk.js} +128 -7
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +1 -1
- package/dist/{zh-tw-B7_Oqv2q.js → zh-tw-DXcqQTrA.js} +540 -0
- package/package.json +10 -3
- package/src/agent/prompt.ts +141 -0
- package/src/agent/provider.ts +123 -0
- package/src/agent/types.ts +151 -0
- package/src/app/components/agent/agent-dock.tsx +35 -0
- package/src/app/components/agent/agent-markdown.tsx +45 -0
- package/src/app/components/agent/agent-panel.tsx +692 -0
- package/src/app/components/agent/agent-presence.tsx +74 -0
- package/src/app/components/agent/composer.tsx +120 -0
- package/src/app/components/agent/connect-view.tsx +184 -0
- package/src/app/components/agent/history-view.tsx +135 -0
- package/src/app/components/agent/provider-logo.tsx +34 -0
- package/src/app/components/agent/transcript.tsx +609 -0
- package/src/app/components/command/slide-command-menu.tsx +10 -0
- package/src/app/components/inline-svg.tsx +52 -21
- package/src/app/components/inspector/comment-layer.tsx +20 -1
- package/src/app/components/inspector/image-crop-dialog.tsx +1 -5
- package/src/app/components/inspector/inspector-panel.tsx +1 -1
- package/src/app/components/mermaid.tsx +42 -11
- package/src/app/components/panel/editor-sidebar.tsx +85 -16
- package/src/app/components/panel/panel-shell.tsx +2 -2
- package/src/app/components/panel/resize-handle.tsx +129 -0
- package/src/app/components/player.tsx +105 -23
- package/src/app/components/present/curtain.tsx +52 -0
- package/src/app/components/present/last-slide-hint.tsx +36 -0
- package/src/app/components/slide-canvas.tsx +1 -1
- package/src/app/components/thumbnail-rail.tsx +1 -1
- package/src/app/lib/agent/use-agent.ts +543 -0
- package/src/app/lib/use-auto-grow.ts +15 -0
- package/src/app/lib/utils.ts +4 -0
- package/src/app/routes/home.tsx +51 -7
- package/src/app/routes/slide.tsx +172 -116
- package/src/app/styles.css +170 -0
- package/src/editing/placeholder-id.ts +1 -0
- package/src/locale/en.ts +107 -0
- package/src/locale/es.ts +109 -0
- package/src/locale/ja.ts +100 -0
- package/src/locale/types.ts +110 -0
- package/src/locale/zh-cn.ts +97 -0
- package/src/locale/zh-tw.ts +97 -0
package/src/app/routes/slide.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
MoreHorizontal,
|
|
15
15
|
Play,
|
|
16
16
|
Presentation,
|
|
17
|
+
Sparkles,
|
|
17
18
|
Terminal,
|
|
18
19
|
} from 'lucide-react';
|
|
19
20
|
import {
|
|
@@ -21,12 +22,16 @@ import {
|
|
|
21
22
|
type RefObject,
|
|
22
23
|
useCallback,
|
|
23
24
|
useEffect,
|
|
25
|
+
useLayoutEffect,
|
|
24
26
|
useMemo,
|
|
25
27
|
useRef,
|
|
26
28
|
useState,
|
|
27
29
|
} from 'react';
|
|
28
30
|
import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
|
29
31
|
import { toast } from 'sonner';
|
|
32
|
+
import { AgentToggleButton } from '@/components/agent/agent-dock';
|
|
33
|
+
import { AgentPanel } from '@/components/agent/agent-panel';
|
|
34
|
+
import { AgentPresence } from '@/components/agent/agent-presence';
|
|
30
35
|
import { AssetView } from '@/components/asset-view';
|
|
31
36
|
import { HistoryProvider } from '@/components/history-provider';
|
|
32
37
|
import { CommentLayer } from '@/components/inspector/comment-layer';
|
|
@@ -40,6 +45,7 @@ import {
|
|
|
40
45
|
} from '@/components/inspector/inspector-provider';
|
|
41
46
|
import { SaveBar } from '@/components/inspector/save-bar';
|
|
42
47
|
import { EditorSidebar } from '@/components/panel/editor-sidebar';
|
|
48
|
+
import { ResizeHandle, useStoredWidth } from '@/components/panel/resize-handle';
|
|
43
49
|
import { DesignProvider } from '@/components/style-panel/design-provider';
|
|
44
50
|
import { DesignToggleButton } from '@/components/style-panel/style-panel';
|
|
45
51
|
import { Button, buttonVariants } from '@/components/ui/button';
|
|
@@ -57,6 +63,7 @@ import {
|
|
|
57
63
|
} from '@/components/ui/dropdown-menu';
|
|
58
64
|
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
59
65
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
66
|
+
import { setDockOpen, useAgentActivity, useAgentDock } from '@/lib/agent/use-agent';
|
|
60
67
|
import { useFolders } from '@/lib/folders';
|
|
61
68
|
import {
|
|
62
69
|
hasModifier,
|
|
@@ -74,11 +81,18 @@ import { format, useLocale } from '@/lib/use-locale';
|
|
|
74
81
|
import { useMediaQuery } from '@/lib/use-media-query';
|
|
75
82
|
import { useWheelPageNavigation } from '@/lib/use-wheel-page-navigation';
|
|
76
83
|
import { cn } from '@/lib/utils';
|
|
84
|
+
import type { AgentTurnContext } from '../../agent/types';
|
|
77
85
|
import { SlideCommandMenu } from '../components/command/slide-command-menu';
|
|
78
86
|
import { PdfProgressToast, PptxProgressToast } from '../components/export-progress-toast';
|
|
79
87
|
import { NotesDrawer } from '../components/notes-drawer';
|
|
80
88
|
import { OverviewGrid } from '../components/overview-grid';
|
|
81
89
|
import { openPresenterWindow, Player } from '../components/player';
|
|
90
|
+
import {
|
|
91
|
+
afterPaint,
|
|
92
|
+
CURTAIN_FADE_MS,
|
|
93
|
+
PresentCurtain,
|
|
94
|
+
whenFullscreenSettled,
|
|
95
|
+
} from '../components/present/curtain';
|
|
82
96
|
import { SlideCanvas } from '../components/slide-canvas';
|
|
83
97
|
import { isDeckWarmed, markDeckWarmed, SlidePreloadLayer } from '../components/slide-preload-layer';
|
|
84
98
|
import { SlideTransitionLayer } from '../components/slide-transition-layer';
|
|
@@ -132,6 +146,13 @@ export function Slide() {
|
|
|
132
146
|
const [designOpen, setDesignOpen] = useState(false);
|
|
133
147
|
const [overviewOpen, setOverviewOpen] = useState(false);
|
|
134
148
|
const [commandOpen, setCommandOpen] = useState(false);
|
|
149
|
+
const { open: agentOpen, toggle: toggleAgent } = useAgentDock();
|
|
150
|
+
useLayoutEffect(() => {
|
|
151
|
+
if (agentOpen) setDesignOpen(false);
|
|
152
|
+
}, [agentOpen]);
|
|
153
|
+
useLayoutEffect(() => {
|
|
154
|
+
if (designOpen) setDockOpen(false);
|
|
155
|
+
}, [designOpen]);
|
|
135
156
|
const [, setWarmedTick] = useState(0);
|
|
136
157
|
const handleAssetsWarmed = useCallback(() => {
|
|
137
158
|
markDeckWarmed(slideId);
|
|
@@ -151,6 +172,55 @@ export function Slide() {
|
|
|
151
172
|
const narrowToolbar = useMediaQuery('(max-width: 639.98px)');
|
|
152
173
|
const prefersReducedMotion = usePrefersReducedMotion();
|
|
153
174
|
|
|
175
|
+
// 'entering' darkens the editor before the Player mounts; 'leaving' keeps
|
|
176
|
+
// the editor dark after the Player unmounts, then fades it back in.
|
|
177
|
+
const [curtain, setCurtain] = useState<'entering' | 'leaving' | null>(null);
|
|
178
|
+
const curtainTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
179
|
+
useEffect(
|
|
180
|
+
() => () => {
|
|
181
|
+
if (curtainTimerRef.current) clearTimeout(curtainTimerRef.current);
|
|
182
|
+
},
|
|
183
|
+
[],
|
|
184
|
+
);
|
|
185
|
+
const present = useCallback(
|
|
186
|
+
(mode: 'window' | 'fullscreen') => {
|
|
187
|
+
if (prefersReducedMotion) {
|
|
188
|
+
setPlayMode(mode);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (curtainTimerRef.current) return;
|
|
192
|
+
setCurtain('entering');
|
|
193
|
+
// Fullscreen is requested when the Player mounts; the delay stays well
|
|
194
|
+
// inside the browser's transient user-activation window.
|
|
195
|
+
curtainTimerRef.current = setTimeout(() => {
|
|
196
|
+
curtainTimerRef.current = null;
|
|
197
|
+
setCurtain(null);
|
|
198
|
+
setPlayMode(mode);
|
|
199
|
+
}, CURTAIN_FADE_MS);
|
|
200
|
+
},
|
|
201
|
+
[prefersReducedMotion],
|
|
202
|
+
);
|
|
203
|
+
const exitPresent = useCallback(() => {
|
|
204
|
+
setPlayMode(null);
|
|
205
|
+
if (!prefersReducedMotion) setCurtain('leaving');
|
|
206
|
+
}, [prefersReducedMotion]);
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (curtain !== 'leaving' || playMode) return;
|
|
209
|
+
let cancelPaint = () => {};
|
|
210
|
+
const reveal = () => {
|
|
211
|
+
cancelPaint = afterPaint(() => setCurtain(null));
|
|
212
|
+
};
|
|
213
|
+
if (!document.fullscreenElement) {
|
|
214
|
+
reveal();
|
|
215
|
+
return () => cancelPaint();
|
|
216
|
+
}
|
|
217
|
+
const cancelWait = whenFullscreenSettled(reveal);
|
|
218
|
+
return () => {
|
|
219
|
+
cancelWait();
|
|
220
|
+
cancelPaint();
|
|
221
|
+
};
|
|
222
|
+
}, [curtain, playMode]);
|
|
223
|
+
|
|
154
224
|
const modulePages = useMemo(() => slide?.default ?? [], [slide]);
|
|
155
225
|
const [pages, setPages] = useState<typeof modulePages>(modulePages);
|
|
156
226
|
useEffect(() => {
|
|
@@ -195,6 +265,16 @@ export function Slide() {
|
|
|
195
265
|
[pageCount, setSearchParams],
|
|
196
266
|
);
|
|
197
267
|
|
|
268
|
+
// While an agent writes this deck page by page, jump to each page as it
|
|
269
|
+
// lands so the user can follow along.
|
|
270
|
+
const agentActivity = useAgentActivity(slideId);
|
|
271
|
+
const agentWriting = agentActivity !== null;
|
|
272
|
+
const seenPageCount = useRef(pageCount);
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
if (agentWriting && pageCount > seenPageCount.current) goTo(pageCount - 1);
|
|
275
|
+
seenPageCount.current = pageCount;
|
|
276
|
+
}, [agentWriting, pageCount, goTo]);
|
|
277
|
+
|
|
198
278
|
const reorderPage = useCallback(
|
|
199
279
|
async (from: number, to: number) => {
|
|
200
280
|
if (from === to) return;
|
|
@@ -310,7 +390,7 @@ export function Slide() {
|
|
|
310
390
|
// When showSlideUi is false the read-only <Player> is rendered and owns
|
|
311
391
|
// keyboard navigation (including step-aware advance/retreat). Attaching this
|
|
312
392
|
// page-nav handler too would race it and skip <Steps> reveals, so bail out.
|
|
313
|
-
if (playMode || !showSlideUi) return;
|
|
393
|
+
if (playMode || curtain || !showSlideUi) return;
|
|
314
394
|
const onKey = (e: KeyboardEvent) => {
|
|
315
395
|
if (
|
|
316
396
|
isTypingTarget(e.target) ||
|
|
@@ -343,19 +423,21 @@ export function Slide() {
|
|
|
343
423
|
return;
|
|
344
424
|
}
|
|
345
425
|
if (e.key === 'f' || e.key === 'F') {
|
|
346
|
-
|
|
426
|
+
present('fullscreen');
|
|
347
427
|
} else if (e.key === 'Enter') {
|
|
348
|
-
|
|
428
|
+
present('window');
|
|
349
429
|
} else if (e.key === 'p' || e.key === 'P') {
|
|
350
430
|
if (slideId) openPresenterWindow(slideId);
|
|
351
|
-
|
|
431
|
+
present('window');
|
|
352
432
|
} else if (import.meta.env.DEV && (e.key === 'd' || e.key === 'D')) {
|
|
353
433
|
setDesignOpen((v) => !v);
|
|
434
|
+
} else if (import.meta.env.DEV && (e.key === 'a' || e.key === 'A')) {
|
|
435
|
+
toggleAgent();
|
|
354
436
|
}
|
|
355
437
|
};
|
|
356
438
|
window.addEventListener('keydown', onKey);
|
|
357
439
|
return () => window.removeEventListener('keydown', onKey);
|
|
358
|
-
}, [index, goTo, playMode, slideId, overviewOpen]);
|
|
440
|
+
}, [index, goTo, playMode, curtain, slideId, overviewOpen, toggleAgent, present]);
|
|
359
441
|
|
|
360
442
|
if (error) {
|
|
361
443
|
return (
|
|
@@ -396,8 +478,9 @@ export function Slide() {
|
|
|
396
478
|
transition={presented.slide.transition}
|
|
397
479
|
index={presented.index}
|
|
398
480
|
onIndexChange={presentReady ? goTo : noop}
|
|
399
|
-
onExit={
|
|
481
|
+
onExit={exitPresent}
|
|
400
482
|
controls
|
|
483
|
+
curtain={!prefersReducedMotion}
|
|
401
484
|
slideId={presented.slideId}
|
|
402
485
|
onSwitchSlide={switchPresentedSlide}
|
|
403
486
|
fullscreen={playMode === 'fullscreen'}
|
|
@@ -619,8 +702,11 @@ export function Slide() {
|
|
|
619
702
|
<InspectorProvider
|
|
620
703
|
slideId={slideId}
|
|
621
704
|
pageIndex={index}
|
|
622
|
-
panelHidden={designOpen}
|
|
623
|
-
onPanelOpen={() =>
|
|
705
|
+
panelHidden={designOpen || agentOpen}
|
|
706
|
+
onPanelOpen={() => {
|
|
707
|
+
setDesignOpen(false);
|
|
708
|
+
setDockOpen(false);
|
|
709
|
+
}}
|
|
624
710
|
>
|
|
625
711
|
<SelectionReporter />
|
|
626
712
|
<div className="flex h-dvh flex-col overflow-hidden bg-sidebar text-foreground">
|
|
@@ -739,6 +825,12 @@ export function Slide() {
|
|
|
739
825
|
/>
|
|
740
826
|
)}
|
|
741
827
|
<DropdownMenuGroup>
|
|
828
|
+
{narrowToolbar && import.meta.env.DEV && (
|
|
829
|
+
<DropdownMenuItem onClick={toggleAgent}>
|
|
830
|
+
<Sparkles />
|
|
831
|
+
{t.agent.openTitle}
|
|
832
|
+
</DropdownMenuItem>
|
|
833
|
+
)}
|
|
742
834
|
<DropdownMenuItem onClick={() => setCommandOpen(true)}>
|
|
743
835
|
<Terminal />
|
|
744
836
|
{t.commandMenu.trigger}
|
|
@@ -753,6 +845,7 @@ export function Slide() {
|
|
|
753
845
|
</DropdownMenuContent>
|
|
754
846
|
</DropdownMenu>
|
|
755
847
|
)}
|
|
848
|
+
{import.meta.env.DEV && view === 'slides' && !narrowToolbar && <AgentToggleButton />}
|
|
756
849
|
{view === 'slides' && !narrowToolbar && (
|
|
757
850
|
<DesignToggleButton active={designOpen} onToggle={() => setDesignOpen((v) => !v)} />
|
|
758
851
|
)}
|
|
@@ -764,7 +857,7 @@ export function Slide() {
|
|
|
764
857
|
size="sm"
|
|
765
858
|
variant="brand"
|
|
766
859
|
aria-label={t.slide.present}
|
|
767
|
-
onClick={() =>
|
|
860
|
+
onClick={() => present(isMobile ? 'window' : 'fullscreen')}
|
|
768
861
|
className="rounded-r-none px-2.5 lg:px-3"
|
|
769
862
|
>
|
|
770
863
|
<Play className="size-3.5 fill-current" />
|
|
@@ -784,12 +877,12 @@ export function Slide() {
|
|
|
784
877
|
</DropdownMenuTrigger>
|
|
785
878
|
<DropdownMenuContent align="end" className="min-w-[200px]">
|
|
786
879
|
<DropdownMenuGroup>
|
|
787
|
-
<DropdownMenuItem onClick={() =>
|
|
880
|
+
<DropdownMenuItem onClick={() => present('window')}>
|
|
788
881
|
<Play />
|
|
789
882
|
{t.slide.presentInWindow}
|
|
790
883
|
<DropdownMenuShortcut>↵</DropdownMenuShortcut>
|
|
791
884
|
</DropdownMenuItem>
|
|
792
|
-
<DropdownMenuItem onClick={() =>
|
|
885
|
+
<DropdownMenuItem onClick={() => present('fullscreen')}>
|
|
793
886
|
<Maximize />
|
|
794
887
|
{t.slide.presentFullscreen}
|
|
795
888
|
<DropdownMenuShortcut>F</DropdownMenuShortcut>
|
|
@@ -797,7 +890,7 @@ export function Slide() {
|
|
|
797
890
|
<DropdownMenuItem
|
|
798
891
|
onClick={() => {
|
|
799
892
|
if (slideId) openPresenterWindow(slideId);
|
|
800
|
-
|
|
893
|
+
present('window');
|
|
801
894
|
}}
|
|
802
895
|
>
|
|
803
896
|
<MonitorSpeaker />
|
|
@@ -834,6 +927,7 @@ export function Slide() {
|
|
|
834
927
|
ref={slideViewportRef}
|
|
835
928
|
data-inspector-root
|
|
836
929
|
data-slide-id={slideId}
|
|
930
|
+
data-agent-provider={agentActivity?.provider}
|
|
837
931
|
className="relative min-h-0 min-w-0 flex-1 bg-background p-2 md:mx-2 md:mb-2 md:rounded-[10px] md:p-10 md:shadow-edge md:ring-1 md:ring-foreground/[0.06]"
|
|
838
932
|
>
|
|
839
933
|
<SlideViewportNavigation
|
|
@@ -856,6 +950,7 @@ export function Slide() {
|
|
|
856
950
|
<InlineEditLayer />
|
|
857
951
|
<SaveBar />
|
|
858
952
|
{import.meta.env.DEV && <CommentLayer />}
|
|
953
|
+
{agentActivity && <AgentPresence activity={agentActivity} revision={pages} />}
|
|
859
954
|
</main>
|
|
860
955
|
{/* Mobile-only horizontal rail. Sits below the canvas and
|
|
861
956
|
pads its bottom for the iOS home indicator / Safari URL bar. */}
|
|
@@ -875,6 +970,17 @@ export function Slide() {
|
|
|
875
970
|
<EditorSidebar
|
|
876
971
|
designOpen={designOpen}
|
|
877
972
|
onCloseDesign={() => setDesignOpen(false)}
|
|
973
|
+
agentOpen={agentOpen}
|
|
974
|
+
agent={
|
|
975
|
+
import.meta.env.DEV && (
|
|
976
|
+
<SlideAgentDock
|
|
977
|
+
slideId={slideId}
|
|
978
|
+
slideTitle={title}
|
|
979
|
+
pageNumber={index + 1}
|
|
980
|
+
totalPages={pageCount}
|
|
981
|
+
/>
|
|
982
|
+
)
|
|
983
|
+
}
|
|
878
984
|
/>
|
|
879
985
|
</div>
|
|
880
986
|
{import.meta.env.DEV && (
|
|
@@ -907,15 +1013,16 @@ export function Slide() {
|
|
|
907
1013
|
currentIndex={index}
|
|
908
1014
|
exporting={exporting}
|
|
909
1015
|
handlers={{
|
|
910
|
-
onPresentWindow: () =>
|
|
911
|
-
onPresentFullscreen: () =>
|
|
1016
|
+
onPresentWindow: () => present('window'),
|
|
1017
|
+
onPresentFullscreen: () => present('fullscreen'),
|
|
912
1018
|
onPresenterView: () => {
|
|
913
1019
|
if (slideId) openPresenterWindow(slideId);
|
|
914
|
-
|
|
1020
|
+
present('window');
|
|
915
1021
|
},
|
|
916
1022
|
onCopyLink: copyLink,
|
|
917
1023
|
onOverview: () => setOverviewOpen(true),
|
|
918
1024
|
onToggleDesignPanel: () => setDesignOpen((v) => !v),
|
|
1025
|
+
onToggleAgentPanel: toggleAgent,
|
|
919
1026
|
onExportHtml: exportHtml,
|
|
920
1027
|
onExportPdf: exportPdf,
|
|
921
1028
|
onExportPptx: exportPptx,
|
|
@@ -925,6 +1032,7 @@ export function Slide() {
|
|
|
925
1032
|
/>
|
|
926
1033
|
)}
|
|
927
1034
|
</div>
|
|
1035
|
+
<PresentCurtain opaque={curtain !== null} className="fixed z-[1000]" />
|
|
928
1036
|
</InspectorProvider>
|
|
929
1037
|
</HistoryProvider>
|
|
930
1038
|
);
|
|
@@ -935,14 +1043,6 @@ const DEFAULT_RAIL_WIDTH = 264;
|
|
|
935
1043
|
const MIN_RAIL_WIDTH = 200;
|
|
936
1044
|
const MAX_RAIL_WIDTH = 480;
|
|
937
1045
|
|
|
938
|
-
function readStoredRailWidth(): number {
|
|
939
|
-
if (typeof window === 'undefined') return DEFAULT_RAIL_WIDTH;
|
|
940
|
-
const raw = window.localStorage.getItem(RAIL_WIDTH_STORAGE_KEY);
|
|
941
|
-
const parsed = raw == null ? Number.NaN : Number(raw);
|
|
942
|
-
if (!Number.isFinite(parsed)) return DEFAULT_RAIL_WIDTH;
|
|
943
|
-
return Math.min(MAX_RAIL_WIDTH, Math.max(MIN_RAIL_WIDTH, parsed));
|
|
944
|
-
}
|
|
945
|
-
|
|
946
1046
|
function ResizableRail(props: {
|
|
947
1047
|
pages: SlideModule['default'];
|
|
948
1048
|
design?: SlideModule['design'];
|
|
@@ -954,103 +1054,26 @@ function ResizableRail(props: {
|
|
|
954
1054
|
onOverview?: () => void;
|
|
955
1055
|
}) {
|
|
956
1056
|
const t = useLocale();
|
|
957
|
-
const [width, setWidth] =
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
window.localStorage.setItem(RAIL_WIDTH_STORAGE_KEY, String(width));
|
|
964
|
-
}, [width]);
|
|
965
|
-
|
|
966
|
-
useEffect(() => {
|
|
967
|
-
if (!resizing) return;
|
|
968
|
-
const prev = {
|
|
969
|
-
cursor: document.body.style.cursor,
|
|
970
|
-
userSelect: document.body.style.userSelect,
|
|
971
|
-
};
|
|
972
|
-
document.body.style.cursor = 'col-resize';
|
|
973
|
-
document.body.style.userSelect = 'none';
|
|
974
|
-
return () => {
|
|
975
|
-
document.body.style.cursor = prev.cursor;
|
|
976
|
-
document.body.style.userSelect = prev.userSelect;
|
|
977
|
-
};
|
|
978
|
-
}, [resizing]);
|
|
979
|
-
|
|
980
|
-
const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
|
|
981
|
-
e.preventDefault();
|
|
982
|
-
e.currentTarget.setPointerCapture(e.pointerId);
|
|
983
|
-
dragRef.current = { startX: e.clientX, startWidth: width };
|
|
984
|
-
setResizing(true);
|
|
985
|
-
};
|
|
986
|
-
|
|
987
|
-
const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
|
|
988
|
-
if (!dragRef.current) return;
|
|
989
|
-
const delta = e.clientX - dragRef.current.startX;
|
|
990
|
-
const next = Math.min(
|
|
991
|
-
MAX_RAIL_WIDTH,
|
|
992
|
-
Math.max(MIN_RAIL_WIDTH, dragRef.current.startWidth + delta),
|
|
993
|
-
);
|
|
994
|
-
setWidth(next);
|
|
995
|
-
};
|
|
996
|
-
|
|
997
|
-
const onPointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
|
|
998
|
-
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
|
999
|
-
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
1000
|
-
}
|
|
1001
|
-
dragRef.current = null;
|
|
1002
|
-
setResizing(false);
|
|
1003
|
-
};
|
|
1004
|
-
|
|
1005
|
-
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
1006
|
-
const step = e.shiftKey ? 32 : 8;
|
|
1007
|
-
if (e.key === 'ArrowLeft') {
|
|
1008
|
-
e.preventDefault();
|
|
1009
|
-
e.stopPropagation();
|
|
1010
|
-
setWidth((w) => Math.max(MIN_RAIL_WIDTH, w - step));
|
|
1011
|
-
} else if (e.key === 'ArrowRight') {
|
|
1012
|
-
e.preventDefault();
|
|
1013
|
-
e.stopPropagation();
|
|
1014
|
-
setWidth((w) => Math.min(MAX_RAIL_WIDTH, w + step));
|
|
1015
|
-
} else if (e.key === 'Home') {
|
|
1016
|
-
e.preventDefault();
|
|
1017
|
-
e.stopPropagation();
|
|
1018
|
-
setWidth(DEFAULT_RAIL_WIDTH);
|
|
1019
|
-
}
|
|
1020
|
-
};
|
|
1057
|
+
const [width, setWidth] = useStoredWidth(
|
|
1058
|
+
RAIL_WIDTH_STORAGE_KEY,
|
|
1059
|
+
DEFAULT_RAIL_WIDTH,
|
|
1060
|
+
MIN_RAIL_WIDTH,
|
|
1061
|
+
MAX_RAIL_WIDTH,
|
|
1062
|
+
);
|
|
1021
1063
|
|
|
1022
1064
|
return (
|
|
1023
1065
|
<div className="relative hidden shrink-0 md:block" style={{ width }}>
|
|
1024
1066
|
<ThumbnailRail width={width} {...props} />
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
onPointerMove={onPointerMove}
|
|
1036
|
-
onPointerUp={onPointerUp}
|
|
1037
|
-
onPointerCancel={onPointerUp}
|
|
1038
|
-
onKeyDown={onKeyDown}
|
|
1039
|
-
onDoubleClick={() => setWidth(DEFAULT_RAIL_WIDTH)}
|
|
1040
|
-
className={cn(
|
|
1041
|
-
'group/resize absolute inset-y-0 right-0 z-20 w-1.5 translate-x-1/2 cursor-col-resize touch-none outline-none',
|
|
1042
|
-
'focus-visible:bg-brand/20',
|
|
1043
|
-
)}
|
|
1044
|
-
>
|
|
1045
|
-
<span
|
|
1046
|
-
aria-hidden
|
|
1047
|
-
className={cn(
|
|
1048
|
-
'pointer-events-none absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-brand opacity-0 transition-opacity duration-150',
|
|
1049
|
-
'group-hover/resize:opacity-100 group-focus-visible/resize:opacity-100',
|
|
1050
|
-
resizing && 'opacity-100',
|
|
1051
|
-
)}
|
|
1052
|
-
/>
|
|
1053
|
-
</div>
|
|
1067
|
+
<ResizeHandle
|
|
1068
|
+
width={width}
|
|
1069
|
+
min={MIN_RAIL_WIDTH}
|
|
1070
|
+
max={MAX_RAIL_WIDTH}
|
|
1071
|
+
defaultWidth={DEFAULT_RAIL_WIDTH}
|
|
1072
|
+
edge="right"
|
|
1073
|
+
label={t.thumbnailRail.resizeRail}
|
|
1074
|
+
onResize={setWidth}
|
|
1075
|
+
className="right-0 translate-x-1/2"
|
|
1076
|
+
/>
|
|
1054
1077
|
</div>
|
|
1055
1078
|
);
|
|
1056
1079
|
}
|
|
@@ -1096,6 +1119,39 @@ function EditorMenuItems({
|
|
|
1096
1119
|
);
|
|
1097
1120
|
}
|
|
1098
1121
|
|
|
1122
|
+
function SlideAgentDock({
|
|
1123
|
+
slideId,
|
|
1124
|
+
slideTitle,
|
|
1125
|
+
pageNumber,
|
|
1126
|
+
totalPages,
|
|
1127
|
+
}: {
|
|
1128
|
+
slideId: string;
|
|
1129
|
+
slideTitle: string;
|
|
1130
|
+
pageNumber: number;
|
|
1131
|
+
totalPages: number;
|
|
1132
|
+
}) {
|
|
1133
|
+
const { selected, comments } = useInspector();
|
|
1134
|
+
const context = useMemo<AgentTurnContext>(
|
|
1135
|
+
() => ({
|
|
1136
|
+
slideId,
|
|
1137
|
+
slideTitle,
|
|
1138
|
+
pageNumber,
|
|
1139
|
+
totalPages,
|
|
1140
|
+
pendingComments: comments.length,
|
|
1141
|
+
selection: selected
|
|
1142
|
+
? {
|
|
1143
|
+
line: selected.line,
|
|
1144
|
+
column: selected.column,
|
|
1145
|
+
tagName: selected.anchor.tagName.toLowerCase(),
|
|
1146
|
+
text: (selected.anchor.textContent ?? '').replace(/\s+/g, ' ').trim().slice(0, 120),
|
|
1147
|
+
}
|
|
1148
|
+
: null,
|
|
1149
|
+
}),
|
|
1150
|
+
[slideId, slideTitle, pageNumber, totalPages, comments.length, selected],
|
|
1151
|
+
);
|
|
1152
|
+
return <AgentPanel context={context} onClose={() => setDockOpen(false)} />;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1099
1155
|
function AgentConnectedBadge() {
|
|
1100
1156
|
const t = useLocale();
|
|
1101
1157
|
const connected = useAgentSocketConnected();
|
package/src/app/styles.css
CHANGED
|
@@ -383,3 +383,173 @@
|
|
|
383
383
|
background-clip: content-box;
|
|
384
384
|
}
|
|
385
385
|
}
|
|
386
|
+
|
|
387
|
+
/* Agent presence: ring, write sweep and shimmer shown while an agent edits a deck. */
|
|
388
|
+
@property --agent-angle {
|
|
389
|
+
syntax: "<angle>";
|
|
390
|
+
inherits: false;
|
|
391
|
+
initial-value: 0deg;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
[data-agent-provider] {
|
|
395
|
+
--agent-a: oklch(0.68 0.14 42);
|
|
396
|
+
--agent-b: oklch(0.82 0.12 75);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
[data-agent-provider="codex"] {
|
|
400
|
+
--agent-a: oklch(0.64 0.13 170);
|
|
401
|
+
--agent-b: oklch(0.7 0.14 240);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
@keyframes agent-spin {
|
|
405
|
+
to {
|
|
406
|
+
--agent-angle: 360deg;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
@keyframes agent-fade-in {
|
|
411
|
+
from {
|
|
412
|
+
opacity: 0;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.agent-ring {
|
|
417
|
+
pointer-events: none;
|
|
418
|
+
position: absolute;
|
|
419
|
+
inset: 0;
|
|
420
|
+
border-radius: inherit;
|
|
421
|
+
padding: 1.5px;
|
|
422
|
+
background: conic-gradient(
|
|
423
|
+
from var(--agent-angle),
|
|
424
|
+
transparent 0deg,
|
|
425
|
+
var(--agent-a) 70deg,
|
|
426
|
+
var(--agent-b) 140deg,
|
|
427
|
+
transparent 210deg,
|
|
428
|
+
transparent 360deg
|
|
429
|
+
);
|
|
430
|
+
mask:
|
|
431
|
+
linear-gradient(#000 0 0) content-box exclude,
|
|
432
|
+
linear-gradient(#000 0 0);
|
|
433
|
+
animation:
|
|
434
|
+
agent-spin 3.6s linear infinite,
|
|
435
|
+
agent-fade-in 400ms var(--ease-swift) both;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.agent-glow {
|
|
439
|
+
pointer-events: none;
|
|
440
|
+
position: absolute;
|
|
441
|
+
inset: 0;
|
|
442
|
+
border-radius: inherit;
|
|
443
|
+
box-shadow:
|
|
444
|
+
inset 0 0 0 1px color-mix(in oklch, var(--agent-a) 35%, transparent),
|
|
445
|
+
inset 0 0 48px -12px color-mix(in oklch, var(--agent-a) 45%, transparent);
|
|
446
|
+
animation: agent-fade-in 400ms var(--ease-swift) both;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
@keyframes agent-sweep {
|
|
450
|
+
from {
|
|
451
|
+
background-position: 120% 0;
|
|
452
|
+
opacity: 1;
|
|
453
|
+
}
|
|
454
|
+
to {
|
|
455
|
+
background-position: -20% 0;
|
|
456
|
+
opacity: 0;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.agent-sweep {
|
|
461
|
+
pointer-events: none;
|
|
462
|
+
position: absolute;
|
|
463
|
+
inset: 0;
|
|
464
|
+
border-radius: inherit;
|
|
465
|
+
background: linear-gradient(
|
|
466
|
+
105deg,
|
|
467
|
+
transparent 35%,
|
|
468
|
+
color-mix(in oklch, var(--agent-b) 22%, transparent) 50%,
|
|
469
|
+
transparent 65%
|
|
470
|
+
);
|
|
471
|
+
background-size: 250% 100%;
|
|
472
|
+
animation: agent-sweep 900ms var(--ease-swift) both;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
@keyframes agent-shimmer {
|
|
476
|
+
from {
|
|
477
|
+
background-position: 100% 0;
|
|
478
|
+
}
|
|
479
|
+
to {
|
|
480
|
+
background-position: 0% 0;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.agent-shimmer {
|
|
485
|
+
background: linear-gradient(
|
|
486
|
+
90deg,
|
|
487
|
+
var(--muted-foreground) 35%,
|
|
488
|
+
var(--foreground) 50%,
|
|
489
|
+
var(--muted-foreground) 65%
|
|
490
|
+
);
|
|
491
|
+
background-size: 300% 100%;
|
|
492
|
+
background-clip: text;
|
|
493
|
+
color: transparent;
|
|
494
|
+
animation: agent-shimmer 1.8s linear infinite;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
@keyframes agent-progress {
|
|
498
|
+
from {
|
|
499
|
+
transform: translateX(-100%);
|
|
500
|
+
}
|
|
501
|
+
to {
|
|
502
|
+
transform: translateX(250%);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.agent-progress {
|
|
507
|
+
pointer-events: none;
|
|
508
|
+
position: absolute;
|
|
509
|
+
inset-inline: 0;
|
|
510
|
+
top: 0;
|
|
511
|
+
height: 1.5px;
|
|
512
|
+
overflow: hidden;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.agent-progress::after {
|
|
516
|
+
content: "";
|
|
517
|
+
position: absolute;
|
|
518
|
+
inset-block: 0;
|
|
519
|
+
width: 40%;
|
|
520
|
+
background: linear-gradient(90deg, transparent, var(--agent-a), var(--agent-b), transparent);
|
|
521
|
+
animation: agent-progress 1.6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
@media (prefers-reduced-motion: reduce) {
|
|
525
|
+
.agent-ring {
|
|
526
|
+
animation: agent-fade-in 160ms ease-out both;
|
|
527
|
+
--agent-angle: 120deg;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.agent-sweep {
|
|
531
|
+
display: none;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.agent-shimmer {
|
|
535
|
+
animation: none;
|
|
536
|
+
color: var(--foreground);
|
|
537
|
+
background: none;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.agent-progress::after {
|
|
541
|
+
animation: none;
|
|
542
|
+
width: 100%;
|
|
543
|
+
opacity: 0.6;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.sidebar-swap {
|
|
548
|
+
animation: agent-fade-in 160ms var(--ease-swift, ease-out);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
@media (prefers-reduced-motion: reduce) {
|
|
552
|
+
.sidebar-swap {
|
|
553
|
+
animation: none;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const UNTITLED_SLIDE_ID_RE = /^untitled(-\d+)?$/;
|