@agimon-ai/doompi-author 0.0.1-alpha.10
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/LICENSE +21 -0
- package/README.md +41 -0
- package/dist/_virtual/_rolldown/runtime.cjs +1 -0
- package/dist/api-contracts.cjs +1 -0
- package/dist/api-contracts.d.cts +2 -0
- package/dist/api-contracts.d.mts +2 -0
- package/dist/api-contracts.mjs +1 -0
- package/dist/authorFacade.cjs +1 -0
- package/dist/authorFacade.d.cts +3 -0
- package/dist/authorFacade.d.mts +3 -0
- package/dist/authorFacade.mjs +1 -0
- package/dist/constants/author.cjs +4 -0
- package/dist/constants/author.cjs.map +1 -0
- package/dist/constants/author.mjs +4 -0
- package/dist/constants/author.mjs.map +1 -0
- package/dist/controllers/authorApi.cjs +2 -0
- package/dist/controllers/authorApi.cjs.map +1 -0
- package/dist/controllers/authorApi.mjs +2 -0
- package/dist/controllers/authorApi.mjs.map +1 -0
- package/dist/controllers/authorBridgeApi.cjs +2 -0
- package/dist/controllers/authorBridgeApi.cjs.map +1 -0
- package/dist/controllers/authorBridgeApi.mjs +2 -0
- package/dist/controllers/authorBridgeApi.mjs.map +1 -0
- package/dist/controllers/authorBridgeMethod.cjs +2 -0
- package/dist/controllers/authorBridgeMethod.cjs.map +1 -0
- package/dist/controllers/authorBridgeMethod.mjs +2 -0
- package/dist/controllers/authorBridgeMethod.mjs.map +1 -0
- package/dist/controllers/authorDocumentApi.cjs +2 -0
- package/dist/controllers/authorDocumentApi.cjs.map +1 -0
- package/dist/controllers/authorDocumentApi.mjs +2 -0
- package/dist/controllers/authorDocumentApi.mjs.map +1 -0
- package/dist/controllers/authorPiMode.cjs +2 -0
- package/dist/controllers/authorPiMode.cjs.map +1 -0
- package/dist/controllers/authorPiMode.mjs +2 -0
- package/dist/controllers/authorPiMode.mjs.map +1 -0
- package/dist/controllers/doomAuthorCommand.cjs +2 -0
- package/dist/controllers/doomAuthorCommand.cjs.map +1 -0
- package/dist/controllers/doomAuthorCommand.d.cts +5 -0
- package/dist/controllers/doomAuthorCommand.d.cts.map +1 -0
- package/dist/controllers/doomAuthorCommand.d.mts +5 -0
- package/dist/controllers/doomAuthorCommand.d.mts.map +1 -0
- package/dist/controllers/doomAuthorCommand.mjs +2 -0
- package/dist/controllers/doomAuthorCommand.mjs.map +1 -0
- package/dist/controllers/webAuthorChannel.cjs +2 -0
- package/dist/controllers/webAuthorChannel.cjs.map +1 -0
- package/dist/controllers/webAuthorChannel.mjs +2 -0
- package/dist/controllers/webAuthorChannel.mjs.map +1 -0
- package/dist/extensions/pi.cjs +2 -0
- package/dist/extensions/pi.cjs.map +1 -0
- package/dist/extensions/pi.d.cts +7 -0
- package/dist/extensions/pi.d.cts.map +1 -0
- package/dist/extensions/pi.d.mts +7 -0
- package/dist/extensions/pi.d.mts.map +1 -0
- package/dist/extensions/pi.mjs +2 -0
- package/dist/extensions/pi.mjs.map +1 -0
- package/dist/extensions/server.cjs +2 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.cts +5 -0
- package/dist/extensions/server.d.cts.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +2 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +20 -0
- package/dist/index.d.mts +20 -0
- package/dist/index.mjs +1 -0
- package/dist/models/authorBridgeState.cjs +2 -0
- package/dist/models/authorBridgeState.cjs.map +1 -0
- package/dist/models/authorBridgeState.d.cts +31 -0
- package/dist/models/authorBridgeState.d.cts.map +1 -0
- package/dist/models/authorBridgeState.d.mts +31 -0
- package/dist/models/authorBridgeState.d.mts.map +1 -0
- package/dist/models/authorBridgeState.mjs +2 -0
- package/dist/models/authorBridgeState.mjs.map +1 -0
- package/dist/models/authorMode.cjs +2 -0
- package/dist/models/authorMode.cjs.map +1 -0
- package/dist/models/authorMode.d.cts +9 -0
- package/dist/models/authorMode.d.cts.map +1 -0
- package/dist/models/authorMode.d.mts +9 -0
- package/dist/models/authorMode.d.mts.map +1 -0
- package/dist/models/authorMode.mjs +2 -0
- package/dist/models/authorMode.mjs.map +1 -0
- package/dist/schemas/apiContracts.cjs +4 -0
- package/dist/schemas/apiContracts.cjs.map +1 -0
- package/dist/schemas/apiContracts.d.cts +53 -0
- package/dist/schemas/apiContracts.d.cts.map +1 -0
- package/dist/schemas/apiContracts.d.mts +53 -0
- package/dist/schemas/apiContracts.d.mts.map +1 -0
- package/dist/schemas/apiContracts.mjs +4 -0
- package/dist/schemas/apiContracts.mjs.map +1 -0
- package/dist/schemas/authorFacade.cjs +2 -0
- package/dist/schemas/authorFacade.cjs.map +1 -0
- package/dist/schemas/authorFacade.d.cts +112 -0
- package/dist/schemas/authorFacade.d.cts.map +1 -0
- package/dist/schemas/authorFacade.d.mts +112 -0
- package/dist/schemas/authorFacade.d.mts.map +1 -0
- package/dist/schemas/authorFacade.mjs +2 -0
- package/dist/schemas/authorFacade.mjs.map +1 -0
- package/dist/schemas/authorTools.cjs +2 -0
- package/dist/schemas/authorTools.cjs.map +1 -0
- package/dist/schemas/authorTools.d.cts +12 -0
- package/dist/schemas/authorTools.d.cts.map +1 -0
- package/dist/schemas/authorTools.d.mts +12 -0
- package/dist/schemas/authorTools.d.mts.map +1 -0
- package/dist/schemas/authorTools.mjs +2 -0
- package/dist/schemas/authorTools.mjs.map +1 -0
- package/dist/services/authorCatalog/index.cjs +2 -0
- package/dist/services/authorCatalog/index.cjs.map +1 -0
- package/dist/services/authorCatalog/index.d.cts +6 -0
- package/dist/services/authorCatalog/index.d.cts.map +1 -0
- package/dist/services/authorCatalog/index.d.mts +6 -0
- package/dist/services/authorCatalog/index.d.mts.map +1 -0
- package/dist/services/authorCatalog/index.mjs +2 -0
- package/dist/services/authorCatalog/index.mjs.map +1 -0
- package/dist/services/authorCatalog/monitor.cjs +2 -0
- package/dist/services/authorCatalog/monitor.cjs.map +1 -0
- package/dist/services/authorCatalog/monitor.d.cts +20 -0
- package/dist/services/authorCatalog/monitor.d.cts.map +1 -0
- package/dist/services/authorCatalog/monitor.d.mts +20 -0
- package/dist/services/authorCatalog/monitor.d.mts.map +1 -0
- package/dist/services/authorCatalog/monitor.mjs +2 -0
- package/dist/services/authorCatalog/monitor.mjs.map +1 -0
- package/dist/services/authorCatalog/type.d.cts +10 -0
- package/dist/services/authorCatalog/type.d.cts.map +1 -0
- package/dist/services/authorCatalog/type.d.mts +10 -0
- package/dist/services/authorCatalog/type.d.mts.map +1 -0
- package/dist/services/authorPrompt/index.cjs +2 -0
- package/dist/services/authorPrompt/index.cjs.map +1 -0
- package/dist/services/authorPrompt/index.mjs +2 -0
- package/dist/services/authorPrompt/index.mjs.map +1 -0
- package/dist/services/structuredDocuments/common.cjs +2 -0
- package/dist/services/structuredDocuments/common.cjs.map +1 -0
- package/dist/services/structuredDocuments/common.mjs +2 -0
- package/dist/services/structuredDocuments/common.mjs.map +1 -0
- package/dist/services/structuredDocuments/csv.cjs +7 -0
- package/dist/services/structuredDocuments/csv.cjs.map +1 -0
- package/dist/services/structuredDocuments/csv.d.cts +7 -0
- package/dist/services/structuredDocuments/csv.d.cts.map +1 -0
- package/dist/services/structuredDocuments/csv.d.mts +7 -0
- package/dist/services/structuredDocuments/csv.d.mts.map +1 -0
- package/dist/services/structuredDocuments/csv.mjs +7 -0
- package/dist/services/structuredDocuments/csv.mjs.map +1 -0
- package/dist/services/structuredDocuments/document.cjs +2 -0
- package/dist/services/structuredDocuments/document.cjs.map +1 -0
- package/dist/services/structuredDocuments/document.mjs +2 -0
- package/dist/services/structuredDocuments/document.mjs.map +1 -0
- package/dist/services/structuredDocuments/index.cjs +2 -0
- package/dist/services/structuredDocuments/index.cjs.map +1 -0
- package/dist/services/structuredDocuments/index.d.cts +13 -0
- package/dist/services/structuredDocuments/index.d.cts.map +1 -0
- package/dist/services/structuredDocuments/index.d.mts +13 -0
- package/dist/services/structuredDocuments/index.d.mts.map +1 -0
- package/dist/services/structuredDocuments/index.mjs +2 -0
- package/dist/services/structuredDocuments/index.mjs.map +1 -0
- package/dist/services/structuredDocuments/markdownSlides.cjs +6 -0
- package/dist/services/structuredDocuments/markdownSlides.cjs.map +1 -0
- package/dist/services/structuredDocuments/markdownSlides.d.cts +7 -0
- package/dist/services/structuredDocuments/markdownSlides.d.cts.map +1 -0
- package/dist/services/structuredDocuments/markdownSlides.d.mts +7 -0
- package/dist/services/structuredDocuments/markdownSlides.d.mts.map +1 -0
- package/dist/services/structuredDocuments/markdownSlides.mjs +6 -0
- package/dist/services/structuredDocuments/markdownSlides.mjs.map +1 -0
- package/dist/services/structuredDocuments/ooxmlArchive.cjs +2 -0
- package/dist/services/structuredDocuments/ooxmlArchive.cjs.map +1 -0
- package/dist/services/structuredDocuments/ooxmlArchive.d.cts +13 -0
- package/dist/services/structuredDocuments/ooxmlArchive.d.cts.map +1 -0
- package/dist/services/structuredDocuments/ooxmlArchive.d.mts +13 -0
- package/dist/services/structuredDocuments/ooxmlArchive.d.mts.map +1 -0
- package/dist/services/structuredDocuments/ooxmlArchive.mjs +2 -0
- package/dist/services/structuredDocuments/ooxmlArchive.mjs.map +1 -0
- package/dist/services/structuredDocuments/pptx.cjs +2 -0
- package/dist/services/structuredDocuments/pptx.cjs.map +1 -0
- package/dist/services/structuredDocuments/pptx.d.cts +7 -0
- package/dist/services/structuredDocuments/pptx.d.cts.map +1 -0
- package/dist/services/structuredDocuments/pptx.d.mts +7 -0
- package/dist/services/structuredDocuments/pptx.d.mts.map +1 -0
- package/dist/services/structuredDocuments/pptx.mjs +2 -0
- package/dist/services/structuredDocuments/pptx.mjs.map +1 -0
- package/dist/services/structuredDocuments/xlsx.cjs +2 -0
- package/dist/services/structuredDocuments/xlsx.cjs.map +1 -0
- package/dist/services/structuredDocuments/xlsx.d.cts +7 -0
- package/dist/services/structuredDocuments/xlsx.d.cts.map +1 -0
- package/dist/services/structuredDocuments/xlsx.d.mts +7 -0
- package/dist/services/structuredDocuments/xlsx.d.mts.map +1 -0
- package/dist/services/structuredDocuments/xlsx.mjs +2 -0
- package/dist/services/structuredDocuments/xlsx.mjs.map +1 -0
- package/dist/services/structuredDocuments/xmlText.cjs +2 -0
- package/dist/services/structuredDocuments/xmlText.cjs.map +1 -0
- package/dist/services/structuredDocuments/xmlText.mjs +2 -0
- package/dist/services/structuredDocuments/xmlText.mjs.map +1 -0
- package/dist/tools/authorTools.cjs +2 -0
- package/dist/tools/authorTools.cjs.map +1 -0
- package/dist/tools/authorTools.d.cts +6 -0
- package/dist/tools/authorTools.d.cts.map +1 -0
- package/dist/tools/authorTools.d.mts +6 -0
- package/dist/tools/authorTools.d.mts.map +1 -0
- package/dist/tools/authorTools.mjs +2 -0
- package/dist/tools/authorTools.mjs.map +1 -0
- package/dist/types/author.cjs +2 -0
- package/dist/types/author.cjs.map +1 -0
- package/dist/types/author.d.cts +39 -0
- package/dist/types/author.d.cts.map +1 -0
- package/dist/types/author.d.mts +39 -0
- package/dist/types/author.d.mts.map +1 -0
- package/dist/types/author.mjs +2 -0
- package/dist/types/author.mjs.map +1 -0
- package/dist/types/authorApi.cjs +2 -0
- package/dist/types/authorApi.cjs.map +1 -0
- package/dist/types/authorApi.d.cts +23 -0
- package/dist/types/authorApi.d.cts.map +1 -0
- package/dist/types/authorApi.d.mts +23 -0
- package/dist/types/authorApi.d.mts.map +1 -0
- package/dist/types/authorApi.mjs +2 -0
- package/dist/types/authorApi.mjs.map +1 -0
- package/dist/types/extension.d.cts +16 -0
- package/dist/types/extension.d.cts.map +1 -0
- package/dist/types/extension.d.mts +16 -0
- package/dist/types/extension.d.mts.map +1 -0
- package/dist/types/structuredDocuments.d.cts +60 -0
- package/dist/types/structuredDocuments.d.cts.map +1 -0
- package/dist/types/structuredDocuments.d.mts +60 -0
- package/dist/types/structuredDocuments.d.mts.map +1 -0
- package/dist/types/webAuthor.cjs +2 -0
- package/dist/types/webAuthor.cjs.map +1 -0
- package/dist/types/webAuthor.d.cts +67 -0
- package/dist/types/webAuthor.d.cts.map +1 -0
- package/dist/types/webAuthor.d.mts +67 -0
- package/dist/types/webAuthor.d.mts.map +1 -0
- package/dist/types/webAuthor.mjs +2 -0
- package/dist/types/webAuthor.mjs.map +1 -0
- package/llms.txt +11 -0
- package/package.json +147 -0
- package/src/extensions/web.ts +71 -0
- package/src/prompts/doompi-use-author/SKILL.md +17 -0
- package/src/types/author.ts +45 -0
- package/src/types/structuredDocuments.ts +63 -0
- package/src/types/webAuthor.ts +85 -0
- package/src/web/api/authorBroker.ts +14 -0
- package/src/web/api/authorBrowserBridge.ts +229 -0
- package/src/web/api/authorFiles.ts +221 -0
- package/src/web/api/authorMedia.ts +19 -0
- package/src/web/api/authorRuntime.ts +165 -0
- package/src/web/components/AuthorDocumentPanel.tsx +233 -0
- package/src/web/components/AuthorGridOverlay.tsx +82 -0
- package/src/web/components/AuthorMediaPreview.tsx +42 -0
- package/src/web/components/AuthorMediaView.tsx +357 -0
- package/src/web/components/AuthorPanel.tsx +102 -0
- package/src/web/components/AuthorRegionDrafts.tsx +103 -0
- package/src/web/components/AuthorRequestLog.tsx +179 -0
- package/src/web/components/AuthorStructuredView.tsx +122 -0
- package/src/web/components/AuthorTextView.tsx +98 -0
- package/src/web/components/AuthorToolPalette.tsx +104 -0
- package/src/web/components/AuthorVideoControls.tsx +111 -0
- package/src/web/components/DescribeAuthorToolsToolCard.tsx +51 -0
- package/src/web/components/OpenAuthoringFileToolCard.tsx +67 -0
- package/src/web/components/UseAuthorToolsToolCard.tsx +51 -0
- package/src/web/lib/authorGrid.ts +125 -0
- package/src/web/lib/authorRegions.ts +30 -0
- package/src/web/lib/authorViewportTypes.ts +202 -0
- package/src/web/lib/describeAuthorToolsToolRender.ts +20 -0
- package/src/web/lib/useAuthorToolsToolRender.ts +20 -0
- package/src/web/stores/authorCapture.ts +323 -0
- package/src/web/stores/authorGridTools.ts +316 -0
- package/src/web/stores/authorProfiles.ts +130 -0
- package/src/web/stores/authorRequestLifecycle.ts +83 -0
- package/src/web/stores/authorStore.ts +61 -0
- package/src/web/stores/authorWorkspaceStore.ts +526 -0
- package/src/web/tsconfig.json +22 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { AuthorNativeAnchor, AuthorRequestRecord } from '../lib/authorViewportTypes';
|
|
2
|
+
|
|
3
|
+
const STATUS_LABELS: Record<AuthorRequestRecord['status'], string> = {
|
|
4
|
+
REQUESTED: 'Request queued',
|
|
5
|
+
CHANGING: 'Agent working',
|
|
6
|
+
CHANGED: 'Change ready',
|
|
7
|
+
COMPLETE: 'Request completed',
|
|
8
|
+
FAILED: 'Request failed',
|
|
9
|
+
CANCELLED: 'Request cancelled',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const STATUS_STYLES: Record<AuthorRequestRecord['status'], string> = {
|
|
13
|
+
REQUESTED: 'border-doom-yellow/35 bg-doom-yellow/5 text-doom-yellow',
|
|
14
|
+
CHANGING: 'border-doom-magenta/45 bg-doom-magenta/10 text-doom-yellow',
|
|
15
|
+
CHANGED: 'border-doom-cyan/40 bg-doom-cyan/5 text-doom-cyan',
|
|
16
|
+
COMPLETE: 'border-doom-green/40 bg-doom-green/5 text-doom-green',
|
|
17
|
+
FAILED: 'border-doom-red/45 bg-doom-red/10 text-doom-red',
|
|
18
|
+
CANCELLED: 'border-doom-border bg-doom-panel text-doom-dim',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function requestInstruction(requestText: string): string {
|
|
22
|
+
const referencedContext = requestText.search(/(?:^|\s+)Referenced context "/u);
|
|
23
|
+
return requestText
|
|
24
|
+
.slice(0, referencedContext < 0 ? undefined : referencedContext)
|
|
25
|
+
.replace(/^Please address the attached feedback\.\s*Request ID: \S+\s*/u, '')
|
|
26
|
+
.trim();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function documentName(path: string): string {
|
|
30
|
+
return path.replaceAll('\\', '/').split('/').at(-1) || path;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function anchorLabel(anchor: AuthorNativeAnchor): string {
|
|
34
|
+
switch (anchor.kind) {
|
|
35
|
+
case 'text-range':
|
|
36
|
+
return anchor.startLine === anchor.endLine
|
|
37
|
+
? `Line ${anchor.startLine}`
|
|
38
|
+
: `Lines ${anchor.startLine}–${anchor.endLine}`;
|
|
39
|
+
case 'cell':
|
|
40
|
+
return [anchor.sheet, anchor.location].filter(Boolean).join(' · ');
|
|
41
|
+
case 'slide-element':
|
|
42
|
+
return `Slide ${anchor.slide} · ${anchor.location}`;
|
|
43
|
+
case 'image-rect':
|
|
44
|
+
return 'Image region';
|
|
45
|
+
case 'pdf-page-rect':
|
|
46
|
+
return `Page ${anchor.page}`;
|
|
47
|
+
case 'video-time-rect': {
|
|
48
|
+
const minutes = Math.floor(anchor.timeSeconds / 60);
|
|
49
|
+
const seconds = Math.floor(anchor.timeSeconds % 60)
|
|
50
|
+
.toString()
|
|
51
|
+
.padStart(2, '0');
|
|
52
|
+
return `Video ${minutes}:${seconds}`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function progressLabel(request: AuthorRequestRecord): string {
|
|
58
|
+
const total = request.regions.length;
|
|
59
|
+
const pending = request.pendingRegions?.length;
|
|
60
|
+
if (request.status === 'CHANGING' && request.currentOperation === 'Agent is working on this request')
|
|
61
|
+
return 'Processing request';
|
|
62
|
+
if (request.status === 'CHANGING' && pending !== undefined) return `${total - pending} of ${total} regions applied`;
|
|
63
|
+
if (request.status === 'CHANGED') return 'Changes applied · awaiting save';
|
|
64
|
+
if (request.status === 'COMPLETE') return 'Agent finished processing this request';
|
|
65
|
+
if (request.status === 'FAILED') return 'Stopped with an error';
|
|
66
|
+
if (request.status === 'CANCELLED') return 'Stopped before completion';
|
|
67
|
+
return `${total} region${total === 1 ? '' : 's'} queued`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function RequestRecord({ request, prominent = false }: { request: AuthorRequestRecord; prominent?: boolean }) {
|
|
71
|
+
const instruction = requestInstruction(request.requestText);
|
|
72
|
+
return (
|
|
73
|
+
<article data-testid={prominent ? 'author-active-request' : undefined} className="space-y-3">
|
|
74
|
+
<header className="border-b border-doom-border-soft pb-3">
|
|
75
|
+
<p className="text-2xs font-bold tracking-widest text-doom-faint">DOCUMENT CONTEXT</p>
|
|
76
|
+
<p className="mt-2 flex items-center gap-2 truncate text-base font-bold text-doom-hi">
|
|
77
|
+
<span aria-hidden className="text-doom-magenta">
|
|
78
|
+
↗
|
|
79
|
+
</span>
|
|
80
|
+
{documentName(request.documentPath)}
|
|
81
|
+
</p>
|
|
82
|
+
</header>
|
|
83
|
+
|
|
84
|
+
<section aria-label="Request locations" className="rounded border border-doom-border bg-doom-panel p-3">
|
|
85
|
+
<ol className="space-y-3">
|
|
86
|
+
{request.regions.map((region, index) => (
|
|
87
|
+
<li key={region.id} className="space-y-2">
|
|
88
|
+
<p className="font-mono text-2xs font-bold uppercase tracking-wider text-doom-yellow">
|
|
89
|
+
WHERE · {request.regions.length > 1 ? `${index + 1} · ` : ''}
|
|
90
|
+
{documentName(request.documentPath)} / {anchorLabel(region.anchor)} · rev {request.revision}
|
|
91
|
+
</p>
|
|
92
|
+
{region.quote ? (
|
|
93
|
+
<blockquote className="line-clamp-3 text-sm italic leading-relaxed text-doom-text">
|
|
94
|
+
“{region.quote}”
|
|
95
|
+
</blockquote>
|
|
96
|
+
) : null}
|
|
97
|
+
<p className="text-xs leading-relaxed text-doom-dim">{region.comment}</p>
|
|
98
|
+
</li>
|
|
99
|
+
))}
|
|
100
|
+
</ol>
|
|
101
|
+
</section>
|
|
102
|
+
|
|
103
|
+
<section
|
|
104
|
+
data-testid={prominent ? 'author-operation-card' : undefined}
|
|
105
|
+
className={`rounded border p-3 ${STATUS_STYLES[request.status]}`}
|
|
106
|
+
>
|
|
107
|
+
<p className="font-mono text-xs font-bold tracking-wider">
|
|
108
|
+
●{' '}
|
|
109
|
+
{request.status === 'CHANGING'
|
|
110
|
+
? 'WORKING'
|
|
111
|
+
: request.status === 'COMPLETE'
|
|
112
|
+
? 'COMPLETED'
|
|
113
|
+
: request.status === 'FAILED'
|
|
114
|
+
? 'ERROR'
|
|
115
|
+
: request.status}
|
|
116
|
+
</p>
|
|
117
|
+
<p className="mt-2 text-sm leading-relaxed text-doom-hi">
|
|
118
|
+
{request.currentOperation || STATUS_LABELS[request.status]}
|
|
119
|
+
</p>
|
|
120
|
+
{request.error ? <p className="mt-2 text-xs leading-relaxed text-doom-red">{request.error}</p> : null}
|
|
121
|
+
{request.before !== undefined || request.after !== undefined ? (
|
|
122
|
+
<details className="mt-2 border-t border-current/20 pt-2 text-xs text-doom-dim">
|
|
123
|
+
<summary className="cursor-pointer select-none">change preview</summary>
|
|
124
|
+
{request.before !== undefined ? (
|
|
125
|
+
<pre className="mt-2 max-h-28 overflow-auto whitespace-pre-wrap break-words font-mono">
|
|
126
|
+
before: {request.before}
|
|
127
|
+
</pre>
|
|
128
|
+
) : null}
|
|
129
|
+
{request.after !== undefined ? (
|
|
130
|
+
<pre className="mt-2 max-h-28 overflow-auto whitespace-pre-wrap break-words font-mono">
|
|
131
|
+
after: {request.after}
|
|
132
|
+
</pre>
|
|
133
|
+
) : null}
|
|
134
|
+
</details>
|
|
135
|
+
) : null}
|
|
136
|
+
</section>
|
|
137
|
+
|
|
138
|
+
<section className="space-y-2">
|
|
139
|
+
<p className="text-2xs font-bold tracking-widest text-doom-faint">REQUEST · HOW TO CHANGE</p>
|
|
140
|
+
<div className="rounded border border-doom-border bg-doom-deep p-3">
|
|
141
|
+
<p className="whitespace-pre-wrap text-sm leading-relaxed text-doom-text">
|
|
142
|
+
{instruction || 'No additional instruction.'}
|
|
143
|
+
</p>
|
|
144
|
+
</div>
|
|
145
|
+
</section>
|
|
146
|
+
|
|
147
|
+
<footer className="font-mono text-2xs text-doom-dim">{progressLabel(request)}</footer>
|
|
148
|
+
</article>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function AuthorRequestLog({ requests }: { requests: readonly AuthorRequestRecord[] }) {
|
|
153
|
+
const latest = requests.at(-1);
|
|
154
|
+
const earlier = requests.slice(0, -1).toReversed();
|
|
155
|
+
return (
|
|
156
|
+
<section data-testid="author-request-history" className="space-y-3">
|
|
157
|
+
<h3 className="text-2xs font-bold tracking-widest text-doom-faint">AUTHORING</h3>
|
|
158
|
+
{latest === undefined ? (
|
|
159
|
+
<p className="text-sm leading-relaxed text-doom-dim">No requests or changes yet.</p>
|
|
160
|
+
) : (
|
|
161
|
+
<RequestRecord request={latest} prominent />
|
|
162
|
+
)}
|
|
163
|
+
{earlier.length > 0 ? (
|
|
164
|
+
<details className="border-t border-doom-border-soft pt-3" data-testid="author-earlier-requests">
|
|
165
|
+
<summary className="cursor-pointer select-none text-2xs font-bold tracking-wider text-doom-faint">
|
|
166
|
+
EARLIER REQUESTS ({earlier.length})
|
|
167
|
+
</summary>
|
|
168
|
+
<ol className="mt-3 space-y-5">
|
|
169
|
+
{earlier.map((request) => (
|
|
170
|
+
<li key={request.id}>
|
|
171
|
+
<RequestRecord request={request} />
|
|
172
|
+
</li>
|
|
173
|
+
))}
|
|
174
|
+
</ol>
|
|
175
|
+
</details>
|
|
176
|
+
) : null}
|
|
177
|
+
</section>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Button } from '@agimon-ai/doompi-web-components';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
import type { DocumentFragment } from '../../types/structuredDocuments';
|
|
5
|
+
import { registerAuthorGridResolver } from '../lib/authorGrid';
|
|
6
|
+
import type { AuthorDisplayedRegion } from '../lib/authorViewportTypes';
|
|
7
|
+
import {
|
|
8
|
+
reviseAuthorFragment,
|
|
9
|
+
setAuthorRegionCandidate,
|
|
10
|
+
type AuthorWorkspaceDocument,
|
|
11
|
+
} from '../stores/authorWorkspaceStore';
|
|
12
|
+
|
|
13
|
+
export function AuthorStructuredView({
|
|
14
|
+
sessionId,
|
|
15
|
+
document,
|
|
16
|
+
displayedRegions,
|
|
17
|
+
}: {
|
|
18
|
+
sessionId: string;
|
|
19
|
+
document: AuthorWorkspaceDocument;
|
|
20
|
+
displayedRegions: readonly AuthorDisplayedRegion[];
|
|
21
|
+
}) {
|
|
22
|
+
useEffect(
|
|
23
|
+
() =>
|
|
24
|
+
registerAuthorGridResolver(sessionId, (cell, geometry) => {
|
|
25
|
+
const { originX = 0, originY = 0, width, height } = geometry.viewport;
|
|
26
|
+
const element = window.document.elementFromPoint(
|
|
27
|
+
originX + cell.center.x * width,
|
|
28
|
+
originY + cell.center.y * height,
|
|
29
|
+
);
|
|
30
|
+
const fragmentId = element?.closest<HTMLElement>('[data-author-fragment]')?.dataset.authorFragment;
|
|
31
|
+
const fragment = document.fragments?.find((candidate) => candidate.id === fragmentId);
|
|
32
|
+
if (fragment === undefined || fragment.readOnly === true) return undefined;
|
|
33
|
+
if (document.kind === 'csv' || document.kind === 'xlsx') {
|
|
34
|
+
return {
|
|
35
|
+
anchor: { kind: 'cell', fragmentId: fragment.id, location: fragment.location },
|
|
36
|
+
quote: fragment.text,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const slide = Number(fragment.location.match(/(?:slide\s*|slides\/slide)(\d+)/u)?.[1]);
|
|
40
|
+
if (!Number.isSafeInteger(slide)) return undefined;
|
|
41
|
+
return {
|
|
42
|
+
anchor: { kind: 'slide-element', fragmentId: fragment.id, location: fragment.location, slide },
|
|
43
|
+
quote: fragment.text,
|
|
44
|
+
};
|
|
45
|
+
}),
|
|
46
|
+
[document.fragments, document.kind, document.version, sessionId],
|
|
47
|
+
);
|
|
48
|
+
const displayedByFragment = new Map(
|
|
49
|
+
displayedRegions.flatMap(({ ordinal, region }) =>
|
|
50
|
+
region.anchor.kind === 'cell' || region.anchor.kind === 'slide-element'
|
|
51
|
+
? [[region.anchor.fragmentId, ordinal] as const]
|
|
52
|
+
: [],
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
const cells = document.kind === 'csv' || document.kind === 'xlsx';
|
|
56
|
+
const groups = new Map<string, DocumentFragment[]>();
|
|
57
|
+
for (const fragment of document.fragments ?? []) {
|
|
58
|
+
const key = cells
|
|
59
|
+
? document.kind === 'csv'
|
|
60
|
+
? fragment.location.split(',')[0]!
|
|
61
|
+
: fragment.location.replace(/![A-Z]+(\d+)$/, '!row $1')
|
|
62
|
+
: fragment.location;
|
|
63
|
+
const group = groups.get(key) ?? [];
|
|
64
|
+
group.push(fragment);
|
|
65
|
+
groups.set(key, group);
|
|
66
|
+
}
|
|
67
|
+
return (
|
|
68
|
+
<div data-testid="author-structured" className="min-h-0 flex-1 space-y-3 overflow-auto p-4">
|
|
69
|
+
{!cells ? <p className="text-xs text-doom-faint">Slide text view, not a layout-faithful rendering.</p> : null}
|
|
70
|
+
{[...groups].map(([location, fragments]) => (
|
|
71
|
+
<section key={location} className="space-y-1">
|
|
72
|
+
<h3 className="text-xs text-doom-faint">{location}</h3>
|
|
73
|
+
<div className={cells ? 'flex gap-1' : 'space-y-2'}>
|
|
74
|
+
{fragments.map((fragment) => (
|
|
75
|
+
<div
|
|
76
|
+
key={fragment.id}
|
|
77
|
+
data-author-fragment={fragment.id}
|
|
78
|
+
data-author-region={displayedByFragment.get(fragment.id)}
|
|
79
|
+
className={`${cells ? 'w-40 shrink-0' : ''} relative rounded ${displayedByFragment.has(fragment.id) ? 'ring-1 ring-doom-yellow' : ''}`}
|
|
80
|
+
>
|
|
81
|
+
{displayedByFragment.has(fragment.id) ? (
|
|
82
|
+
<span className="absolute -left-1 -top-1 z-10 flex h-4 min-w-4 items-center justify-center rounded-full bg-doom-yellow px-1 text-2xs font-bold text-doom-deep">
|
|
83
|
+
{displayedByFragment.get(fragment.id)}
|
|
84
|
+
</span>
|
|
85
|
+
) : null}
|
|
86
|
+
<Button
|
|
87
|
+
size="xs"
|
|
88
|
+
variant="ghost"
|
|
89
|
+
onClick={(event) => {
|
|
90
|
+
const bounds = event.currentTarget.getBoundingClientRect();
|
|
91
|
+
const slide = Number(fragment.location.match(/(?:slide\s*|slides\/slide)(\d+)/)?.[1]);
|
|
92
|
+
if (!cells && !Number.isSafeInteger(slide)) return;
|
|
93
|
+
setAuthorRegionCandidate(sessionId, {
|
|
94
|
+
documentPath: document.path,
|
|
95
|
+
revision: document.version,
|
|
96
|
+
sourceSha256: document.sourceSha256,
|
|
97
|
+
quote: fragment.text,
|
|
98
|
+
anchor: cells
|
|
99
|
+
? { kind: 'cell', fragmentId: fragment.id, location: fragment.location }
|
|
100
|
+
: { kind: 'slide-element', fragmentId: fragment.id, location: fragment.location, slide },
|
|
101
|
+
viewport: { width: bounds.width, height: bounds.height },
|
|
102
|
+
createdAt: Date.now(),
|
|
103
|
+
});
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
mark {fragment.location}
|
|
107
|
+
</Button>
|
|
108
|
+
<textarea
|
|
109
|
+
aria-label={fragment.location}
|
|
110
|
+
value={fragment.text}
|
|
111
|
+
readOnly={fragment.readOnly === true}
|
|
112
|
+
className="w-full rounded border border-doom-border bg-doom-deep p-2 text-sm text-doom-text"
|
|
113
|
+
onChange={(event) => reviseAuthorFragment(sessionId, document.path, fragment.id, event.target.value)}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
</section>
|
|
119
|
+
))}
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { CodeEditor, Markdown, type CodeEditorController } from '@agimon-ai/doompi-web-components';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
|
|
4
|
+
import { registerAuthorGridResolver } from '../lib/authorGrid';
|
|
5
|
+
import type { AuthorDisplayedRegion } from '../lib/authorViewportTypes';
|
|
6
|
+
import {
|
|
7
|
+
authorDocument,
|
|
8
|
+
reviseAuthorDocument,
|
|
9
|
+
setAuthorRegionCandidate,
|
|
10
|
+
type AuthorWorkspaceDocument,
|
|
11
|
+
} from '../stores/authorWorkspaceStore';
|
|
12
|
+
|
|
13
|
+
export function AuthorTextView({
|
|
14
|
+
sessionId,
|
|
15
|
+
document,
|
|
16
|
+
preview,
|
|
17
|
+
displayedRegions,
|
|
18
|
+
}: {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
document: AuthorWorkspaceDocument;
|
|
21
|
+
preview: boolean;
|
|
22
|
+
displayedRegions: readonly AuthorDisplayedRegion[];
|
|
23
|
+
}) {
|
|
24
|
+
const host = useRef<HTMLDivElement>(null);
|
|
25
|
+
const editor = useRef<CodeEditorController>(null);
|
|
26
|
+
useEffect(
|
|
27
|
+
() =>
|
|
28
|
+
registerAuthorGridResolver(sessionId, (cell, geometry) => {
|
|
29
|
+
if (preview || editor.current === null) return undefined;
|
|
30
|
+
const { originX = 0, originY = 0, width, height } = geometry.viewport;
|
|
31
|
+
const range = editor.current.resolveViewportRegion({
|
|
32
|
+
left: originX + cell.rect.x * width,
|
|
33
|
+
top: originY + cell.rect.y * height,
|
|
34
|
+
right: originX + (cell.rect.x + cell.rect.width) * width,
|
|
35
|
+
bottom: originY + (cell.rect.y + cell.rect.height) * height,
|
|
36
|
+
});
|
|
37
|
+
if (range === null || range.from === range.to) return undefined;
|
|
38
|
+
return {
|
|
39
|
+
anchor: {
|
|
40
|
+
kind: 'text-range',
|
|
41
|
+
startOffset: range.from,
|
|
42
|
+
endOffset: range.to,
|
|
43
|
+
startLine: range.startLine,
|
|
44
|
+
endLine: range.endLine,
|
|
45
|
+
},
|
|
46
|
+
quote: range.text,
|
|
47
|
+
};
|
|
48
|
+
}),
|
|
49
|
+
[preview, sessionId],
|
|
50
|
+
);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
editor.current?.setMarkedRanges(
|
|
53
|
+
displayedRegions.flatMap(({ ordinal, region }) =>
|
|
54
|
+
region.anchor.kind === 'text-range'
|
|
55
|
+
? [{ from: region.anchor.startOffset, to: region.anchor.endOffset, label: String(ordinal) }]
|
|
56
|
+
: [],
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
}, [displayedRegions]);
|
|
60
|
+
return (
|
|
61
|
+
<div ref={host} className="flex min-h-0 flex-1 flex-col">
|
|
62
|
+
{preview ? (
|
|
63
|
+
<div data-testid="author-markdown" className="min-h-0 flex-1 overflow-auto p-4 text-sm text-doom-text">
|
|
64
|
+
<Markdown text={document.content ?? ''} />
|
|
65
|
+
</div>
|
|
66
|
+
) : (
|
|
67
|
+
<CodeEditor
|
|
68
|
+
value={document.content ?? ''}
|
|
69
|
+
path={document.path}
|
|
70
|
+
data-testid="author-editor"
|
|
71
|
+
controllerRef={editor}
|
|
72
|
+
className="min-h-0 flex-1"
|
|
73
|
+
onChange={(content) => reviseAuthorDocument(sessionId, document.path, content)}
|
|
74
|
+
onSelect={(range) => {
|
|
75
|
+
const current = authorDocument(sessionId, document.path);
|
|
76
|
+
const bounds = host.current?.getBoundingClientRect();
|
|
77
|
+
if (!current || !bounds || range.from === range.to) return;
|
|
78
|
+
setAuthorRegionCandidate(sessionId, {
|
|
79
|
+
documentPath: current.path,
|
|
80
|
+
revision: current.version,
|
|
81
|
+
sourceSha256: current.sourceSha256,
|
|
82
|
+
quote: range.text,
|
|
83
|
+
anchor: {
|
|
84
|
+
kind: 'text-range',
|
|
85
|
+
startOffset: range.from,
|
|
86
|
+
endOffset: range.to,
|
|
87
|
+
startLine: range.startLine,
|
|
88
|
+
endLine: range.endLine,
|
|
89
|
+
},
|
|
90
|
+
viewport: { width: bounds.width, height: bounds.height },
|
|
91
|
+
createdAt: Date.now(),
|
|
92
|
+
});
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Button } from '@agimon-ai/doompi-web-components';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import type { AuthorDocumentKind, AuthorToolMode } from '../lib/authorViewportTypes';
|
|
5
|
+
import {
|
|
6
|
+
authorDocument,
|
|
7
|
+
authorSessionWorkspace,
|
|
8
|
+
reviseAuthorDocument,
|
|
9
|
+
setAuthorRegionCandidate,
|
|
10
|
+
setAuthorToolMode,
|
|
11
|
+
} from '../stores/authorWorkspaceStore';
|
|
12
|
+
|
|
13
|
+
export function AuthorToolPalette({
|
|
14
|
+
sessionId,
|
|
15
|
+
kind,
|
|
16
|
+
activeTool,
|
|
17
|
+
}: {
|
|
18
|
+
sessionId: string;
|
|
19
|
+
kind: AuthorDocumentKind;
|
|
20
|
+
activeTool: AuthorToolMode;
|
|
21
|
+
}) {
|
|
22
|
+
const [error, setError] = useState<string>();
|
|
23
|
+
const format = (style: 'Bold' | 'Heading' | 'Link' | 'List') => {
|
|
24
|
+
const session = authorSessionWorkspace(sessionId);
|
|
25
|
+
const selected = session.candidate;
|
|
26
|
+
const document = selected && authorDocument(sessionId, selected.documentPath);
|
|
27
|
+
if (
|
|
28
|
+
!selected ||
|
|
29
|
+
!document ||
|
|
30
|
+
selected.anchor.kind !== 'text-range' ||
|
|
31
|
+
selected.revision !== document.version ||
|
|
32
|
+
selected.sourceSha256 !== document.sourceSha256
|
|
33
|
+
) {
|
|
34
|
+
setError('Select text in the document first.');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const { startOffset, endOffset } = selected.anchor;
|
|
38
|
+
const content = document.content ?? '';
|
|
39
|
+
const text = content.slice(startOffset, endOffset);
|
|
40
|
+
const replacement =
|
|
41
|
+
style === 'Bold'
|
|
42
|
+
? `**${text}**`
|
|
43
|
+
: style === 'Link'
|
|
44
|
+
? `[${text}](https://)`
|
|
45
|
+
: text
|
|
46
|
+
.split('\n')
|
|
47
|
+
.map((line) => `${style === 'Heading' ? '## ' : '- '}${line}`)
|
|
48
|
+
.join('\n');
|
|
49
|
+
reviseAuthorDocument(
|
|
50
|
+
sessionId,
|
|
51
|
+
document.path,
|
|
52
|
+
content.slice(0, startOffset) + replacement + content.slice(endOffset),
|
|
53
|
+
);
|
|
54
|
+
setAuthorRegionCandidate(sessionId, undefined);
|
|
55
|
+
setError(undefined);
|
|
56
|
+
};
|
|
57
|
+
const tools = [
|
|
58
|
+
{
|
|
59
|
+
label: 'Region',
|
|
60
|
+
glyph: '⌗',
|
|
61
|
+
action: () => setAuthorToolMode(sessionId, activeTool === 'mark' ? 'select' : 'mark'),
|
|
62
|
+
active: activeTool === 'mark',
|
|
63
|
+
},
|
|
64
|
+
...(kind === 'markdown'
|
|
65
|
+
? [
|
|
66
|
+
{ label: 'Bold', glyph: 'B', action: () => format('Bold') },
|
|
67
|
+
{ label: 'Heading', glyph: 'H', action: () => format('Heading') },
|
|
68
|
+
{ label: 'Link', glyph: '↗', action: () => format('Link') },
|
|
69
|
+
{ label: 'List', glyph: '≡', action: () => format('List') },
|
|
70
|
+
]
|
|
71
|
+
: []),
|
|
72
|
+
{ label: 'Comment', glyph: '□', action: () => setAuthorToolMode(sessionId, 'mark') },
|
|
73
|
+
];
|
|
74
|
+
return (
|
|
75
|
+
<section data-testid="author-tool-palette" className="space-y-2 border-b border-doom-border-soft pb-3">
|
|
76
|
+
<div className="flex justify-between text-sm text-doom-dim">
|
|
77
|
+
<h3 className="font-bold tracking-widest">TOOLS</h3>
|
|
78
|
+
<span>focused tab</span>
|
|
79
|
+
</div>
|
|
80
|
+
<div className="grid grid-cols-3 gap-1.5">
|
|
81
|
+
{tools.map((tool) => (
|
|
82
|
+
<Button
|
|
83
|
+
key={tool.label}
|
|
84
|
+
size="md"
|
|
85
|
+
variant="outline"
|
|
86
|
+
aria-label={tool.label === 'Region' ? 'mark region' : tool.label}
|
|
87
|
+
aria-pressed={tool.active}
|
|
88
|
+
onClick={tool.action}
|
|
89
|
+
className={`min-h-11 min-w-11 gap-1.5 rounded border text-base [@media(pointer:fine)]:min-h-8 sm:text-sm ${tool.active ? 'border-doom-red bg-doom-red/10 text-doom-red hover:bg-doom-red/15' : 'border-doom-border bg-doom-panel text-doom-dim'}`}
|
|
90
|
+
>
|
|
91
|
+
<span aria-hidden="true">{tool.glyph}</span>
|
|
92
|
+
{tool.label}
|
|
93
|
+
</Button>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
<p className="text-base leading-normal text-doom-dim sm:text-sm">
|
|
97
|
+
{activeTool === 'mark'
|
|
98
|
+
? 'Drag over the document to mark a region.'
|
|
99
|
+
: 'Choose Region to mark an area for a comment.'}
|
|
100
|
+
</p>
|
|
101
|
+
{error ? <output className="block text-base text-doom-red">{error}</output> : null}
|
|
102
|
+
</section>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Button, type MediaPlaybackState } from '@agimon-ai/doompi-web-components';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
export function videoTimeLabel(seconds: number): string {
|
|
5
|
+
const milliseconds = Math.max(0, Math.round((Number.isFinite(seconds) ? seconds : 0) * 1000));
|
|
6
|
+
return `${String(Math.floor(milliseconds / 60000)).padStart(2, '0')}:${String(Math.floor(milliseconds / 1000) % 60).padStart(2, '0')}.${String(milliseconds % 1000).padStart(3, '0')}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function AuthorVideoControls({
|
|
10
|
+
playback,
|
|
11
|
+
ready,
|
|
12
|
+
locked,
|
|
13
|
+
marking,
|
|
14
|
+
onSeek,
|
|
15
|
+
onToggle,
|
|
16
|
+
onAnnotate,
|
|
17
|
+
}: {
|
|
18
|
+
playback: MediaPlaybackState;
|
|
19
|
+
ready: boolean;
|
|
20
|
+
locked: boolean;
|
|
21
|
+
marking: boolean;
|
|
22
|
+
onSeek: (seconds: number) => void;
|
|
23
|
+
onToggle: () => void;
|
|
24
|
+
onAnnotate: () => void;
|
|
25
|
+
}) {
|
|
26
|
+
const [seconds, setSeconds] = useState('');
|
|
27
|
+
const duration = playback.duration;
|
|
28
|
+
const invalid =
|
|
29
|
+
seconds.trim() === '' || !Number.isFinite(Number(seconds)) || Number(seconds) < 0 || Number(seconds) > duration;
|
|
30
|
+
const buttonClass = 'min-h-11 px-3 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm';
|
|
31
|
+
return (
|
|
32
|
+
<section
|
|
33
|
+
aria-label="Video annotation controls"
|
|
34
|
+
className="mt-2 space-y-2 rounded border border-doom-border bg-doom-panel p-2.5 text-base text-doom-text sm:text-sm"
|
|
35
|
+
>
|
|
36
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
37
|
+
<output aria-label="Playback position" className="font-mono text-base tabular-nums">
|
|
38
|
+
{videoTimeLabel(playback.currentTime)} / {videoTimeLabel(duration)}
|
|
39
|
+
</output>
|
|
40
|
+
<span className="text-doom-dim">
|
|
41
|
+
{locked
|
|
42
|
+
? 'Annotation draft: add or discard it before seeking.'
|
|
43
|
+
: marking
|
|
44
|
+
? 'Frame paused. Drag a region on the video.'
|
|
45
|
+
: 'Seek to a moment, then choose Annotate this frame.'}
|
|
46
|
+
</span>
|
|
47
|
+
</div>
|
|
48
|
+
<input
|
|
49
|
+
aria-label="Video timeline"
|
|
50
|
+
type="range"
|
|
51
|
+
min={0}
|
|
52
|
+
max={duration || 0}
|
|
53
|
+
step={0.001}
|
|
54
|
+
value={Math.min(playback.currentTime, duration)}
|
|
55
|
+
disabled={locked || duration <= 0}
|
|
56
|
+
onChange={(event) => onSeek(Number(event.target.value))}
|
|
57
|
+
className="h-11 w-full cursor-pointer accent-doom-blue [@media(pointer:fine)]:h-6"
|
|
58
|
+
/>
|
|
59
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
60
|
+
<Button className={buttonClass} disabled={locked || duration <= 0} onClick={onToggle}>
|
|
61
|
+
{playback.playing ? 'Pause' : 'Play'}
|
|
62
|
+
</Button>
|
|
63
|
+
<Button
|
|
64
|
+
className={buttonClass}
|
|
65
|
+
disabled={locked || duration <= 0}
|
|
66
|
+
onClick={() => onSeek(Math.max(0, playback.currentTime - 0.1))}
|
|
67
|
+
aria-label="Seek back 0.1 seconds"
|
|
68
|
+
>
|
|
69
|
+
−0.1s
|
|
70
|
+
</Button>
|
|
71
|
+
<Button
|
|
72
|
+
className={buttonClass}
|
|
73
|
+
disabled={locked || duration <= 0}
|
|
74
|
+
onClick={() => onSeek(Math.min(duration, playback.currentTime + 0.1))}
|
|
75
|
+
aria-label="Seek forward 0.1 seconds"
|
|
76
|
+
>
|
|
77
|
+
+0.1s
|
|
78
|
+
</Button>
|
|
79
|
+
<Button variant="primary" className={buttonClass} disabled={locked || !ready} onClick={onAnnotate}>
|
|
80
|
+
Annotate this frame
|
|
81
|
+
</Button>
|
|
82
|
+
<form
|
|
83
|
+
className="flex flex-wrap items-center gap-2 sm:ml-auto"
|
|
84
|
+
onSubmit={(event) => {
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
if (!invalid && !locked) onSeek(Number(seconds));
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
<label htmlFor="author-video-seconds" className="text-doom-dim">
|
|
90
|
+
Go to seconds
|
|
91
|
+
</label>
|
|
92
|
+
<input
|
|
93
|
+
id="author-video-seconds"
|
|
94
|
+
type="number"
|
|
95
|
+
min={0}
|
|
96
|
+
max={duration}
|
|
97
|
+
step={0.001}
|
|
98
|
+
value={seconds}
|
|
99
|
+
onChange={(event) => setSeconds(event.target.value)}
|
|
100
|
+
disabled={locked || duration <= 0}
|
|
101
|
+
placeholder="1.250"
|
|
102
|
+
className="h-11 w-24 rounded border border-doom-border bg-doom-deep px-2 text-lg [@media(pointer:fine)]:h-8 sm:text-sm"
|
|
103
|
+
/>
|
|
104
|
+
<Button type="submit" className={buttonClass} disabled={locked || duration <= 0 || invalid}>
|
|
105
|
+
Go
|
|
106
|
+
</Button>
|
|
107
|
+
</form>
|
|
108
|
+
</div>
|
|
109
|
+
</section>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ToolMessageRenderProps } from '@agimon-ai/doompi-core/web';
|
|
2
|
+
import {
|
|
3
|
+
collapseLines,
|
|
4
|
+
MessageItem,
|
|
5
|
+
MessageItemBody,
|
|
6
|
+
MessageItemHeader,
|
|
7
|
+
MessageItemStatus,
|
|
8
|
+
MessageLines,
|
|
9
|
+
toolTone,
|
|
10
|
+
} from '@agimon-ai/doompi-web-components';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
describeAuthorToolsCallSummary,
|
|
14
|
+
describeAuthorToolsCollapsedLines,
|
|
15
|
+
describeAuthorToolsResultLines,
|
|
16
|
+
} from '../lib/describeAuthorToolsToolRender';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The describe_author_tools tool's timeline item: Describe the current Author viewport capabilities. The shell owns
|
|
20
|
+
* the frame, the outcome tone, the status badge, and the expand toggle; this
|
|
21
|
+
* card supplies the header summary and the body lines, and says whether it
|
|
22
|
+
* hides any so the toggle appears only when it reveals something.
|
|
23
|
+
*/
|
|
24
|
+
export function DescribeAuthorToolsToolCard({ toolName, args, result, running, isError }: ToolMessageRenderProps) {
|
|
25
|
+
const lines = describeAuthorToolsResultLines(result);
|
|
26
|
+
const collapsed = collapseLines(lines, describeAuthorToolsCollapsedLines, false);
|
|
27
|
+
return (
|
|
28
|
+
<MessageItem tone={toolTone({ running, isError })} expandable={collapsed.hidden > 0}>
|
|
29
|
+
{({ expanded }) => {
|
|
30
|
+
const view = expanded ? collapseLines(lines, describeAuthorToolsCollapsedLines, true) : collapsed;
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<MessageItemHeader title={toolName}>
|
|
34
|
+
<span data-testid={`tool-call-${toolName}`} className="min-w-0 flex-1 truncate text-doom-text">
|
|
35
|
+
{describeAuthorToolsCallSummary(args)}
|
|
36
|
+
</span>
|
|
37
|
+
</MessageItemHeader>
|
|
38
|
+
{view.shown.length > 0 || running || isError ? (
|
|
39
|
+
<MessageItemBody data-testid={`tool-result-${toolName}`} className="flex flex-col gap-1">
|
|
40
|
+
{view.shown.length > 0 ? <MessageLines lines={view.shown} /> : null}
|
|
41
|
+
{view.hidden > 0 ? <MessageItemStatus expands>{view.hidden} more line(s)</MessageItemStatus> : null}
|
|
42
|
+
{running ? <MessageItemStatus tone="running">running</MessageItemStatus> : null}
|
|
43
|
+
{isError ? <MessageItemStatus tone="error">failed</MessageItemStatus> : null}
|
|
44
|
+
</MessageItemBody>
|
|
45
|
+
) : null}
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
}}
|
|
49
|
+
</MessageItem>
|
|
50
|
+
);
|
|
51
|
+
}
|