@larkup/workspace-app 0.2.2 → 0.2.6
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-types/components/chat/chat-workspace.d.ts.map +1 -1
- package/dist-types/components/chat/message-item.d.ts.map +1 -1
- package/dist-types/components/chat/tools/chat-media-preview.d.ts.map +1 -1
- package/dist-types/components/data/corpus-panel.d.ts +2 -1
- package/dist-types/components/data/corpus-panel.d.ts.map +1 -1
- package/dist-types/components/data/data-workspace.d.ts.map +1 -1
- package/dist-types/components/data/project-data-page.d.ts.map +1 -1
- package/dist-types/lib/chat/message-compaction.d.ts +2 -0
- package/dist-types/lib/chat/message-compaction.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/components/chat/chat-workspace.tsx +1 -16
- package/src/components/chat/message-item.tsx +5 -1
- package/src/components/chat/tools/chat-media-preview.tsx +47 -32
- package/src/components/data/corpus-panel.tsx +17 -0
- package/src/components/data/data-workspace.tsx +18 -4
- package/src/components/data/project-data-page.tsx +7 -1
- package/src/lib/chat/message-compaction.ts +25 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-workspace.d.ts","sourceRoot":"","sources":["../../../src/components/chat/chat-workspace.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chat-workspace.d.ts","sourceRoot":"","sources":["../../../src/components/chat/chat-workspace.tsx"],"names":[],"mappings":"AAkOA,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,+BAQjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-item.d.ts","sourceRoot":"","sources":["../../../src/components/chat/message-item.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEpC,OAAO,0BAA0B,CAAC;AAuBlC,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEhG,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,MAAM,EACN,WAAW,EACX,aAAa,EACb,QAAQ,EACR,sBAA8B,EAC9B,UAAU,EACV,MAAM,EACN,UAAU,GACX,EAAE;IACD,OAAO,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,8EAA8E;IAC9E,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,UAAU,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxD,yHAAyH;IACzH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,SAAS,KAAK,IAAI,CAAC;CAChF,+
|
|
1
|
+
{"version":3,"file":"message-item.d.ts","sourceRoot":"","sources":["../../../src/components/chat/message-item.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEpC,OAAO,0BAA0B,CAAC;AAuBlC,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEhG,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,MAAM,EACN,WAAW,EACX,aAAa,EACb,QAAQ,EACR,sBAA8B,EAC9B,UAAU,EACV,MAAM,EACN,UAAU,GACX,EAAE;IACD,OAAO,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,8EAA8E;IAC9E,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,UAAU,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxD,yHAAyH;IACzH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,SAAS,KAAK,IAAI,CAAC;CAChF,+BAklBA;AAopBD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA4FnD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-media-preview.d.ts","sourceRoot":"","sources":["../../../../src/components/chat/tools/chat-media-preview.tsx"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AAEH,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,EACT,OAAO,GACR,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,eAAe,CAAC;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,
|
|
1
|
+
{"version":3,"file":"chat-media-preview.d.ts","sourceRoot":"","sources":["../../../../src/components/chat/tools/chat-media-preview.tsx"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AAEH,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,EACT,OAAO,GACR,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,eAAe,CAAC;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,sCAuRA;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,GAAG,SAAS,CAyCpB;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,CACjC,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,GAAG,SAAS,CAsBpB;AAqDD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,CAqB7D"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SourceDocument, DataGroup } from '@larkup/core/types';
|
|
2
2
|
import { type VideoRuntimeScope } from '../../lib/media/video-runtime-scope';
|
|
3
|
-
export declare function CorpusPanel({ documents, groups, onChanged, isIndexing, activeVideoRuntimeScope, initialGroupId, isLoading, }: {
|
|
3
|
+
export declare function CorpusPanel({ documents, groups, onChanged, isIndexing, activeVideoRuntimeScope, initialGroupId, isLoading, error, }: {
|
|
4
4
|
documents: SourceDocument[];
|
|
5
5
|
groups?: DataGroup[];
|
|
6
6
|
onChanged: () => void | Promise<unknown>;
|
|
@@ -8,5 +8,6 @@ export declare function CorpusPanel({ documents, groups, onChanged, isIndexing,
|
|
|
8
8
|
activeVideoRuntimeScope: VideoRuntimeScope;
|
|
9
9
|
initialGroupId?: string;
|
|
10
10
|
isLoading?: boolean;
|
|
11
|
+
error?: string;
|
|
11
12
|
}): import("react").JSX.Element;
|
|
12
13
|
//# sourceMappingURL=corpus-panel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"corpus-panel.d.ts","sourceRoot":"","sources":["../../../src/components/data/corpus-panel.tsx"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAkB,cAAc,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AA+BpF,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,qCAAqC,CAAC;AAU7C,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,MAAW,EACX,SAAS,EACT,UAAU,EACV,uBAAuB,EACvB,cAAc,EACd,SAAS,
|
|
1
|
+
{"version":3,"file":"corpus-panel.d.ts","sourceRoot":"","sources":["../../../src/components/data/corpus-panel.tsx"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAkB,cAAc,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AA+BpF,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,qCAAqC,CAAC;AAU7C,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,MAAW,EACX,SAAS,EACT,UAAU,EACV,uBAAuB,EACvB,cAAc,EACd,SAAS,EACT,KAAK,GACN,EAAE;IACD,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uBAAuB,EAAE,iBAAiB,CAAC;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,+BAovBA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-workspace.d.ts","sourceRoot":"","sources":["../../../src/components/data/data-workspace.tsx"],"names":[],"mappings":"AAmEA,KAAK,QAAQ,GAAG,KAAK,GAAG,QAAQ,CAAC;AA+BjC,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,GAAE;IAAE,IAAI,CAAC,EAAE,QAAQ,CAAA;CAAO,+
|
|
1
|
+
{"version":3,"file":"data-workspace.d.ts","sourceRoot":"","sources":["../../../src/components/data/data-workspace.tsx"],"names":[],"mappings":"AAmEA,KAAK,QAAQ,GAAG,KAAK,GAAG,QAAQ,CAAC;AA+BjC,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,GAAE;IAAE,IAAI,CAAC,EAAE,QAAQ,CAAA;CAAO,+BA0hB/D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-data-page.d.ts","sourceRoot":"","sources":["../../../src/components/data/project-data-page.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"project-data-page.d.ts","sourceRoot":"","sources":["../../../src/components/data/project-data-page.tsx"],"names":[],"mappings":"AAeA,kEAAkE;AAClE,wBAAgB,eAAe,gCA4C9B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-compaction.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/message-compaction.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,CAwBjD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@larkup/workspace-app",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Canonical Larkup chat, data and settings pages with host adapters",
|
|
6
6
|
"files": [
|
|
@@ -534,7 +534,7 @@
|
|
|
534
534
|
"dependencies": {
|
|
535
535
|
"@ai-sdk/react": "4.0.136",
|
|
536
536
|
"@base-ui/react": "^1.5.0",
|
|
537
|
-
"@larkup/core": "^0.8.
|
|
537
|
+
"@larkup/core": "^0.8.2",
|
|
538
538
|
"ai": "7.0.133",
|
|
539
539
|
"class-variance-authority": "^0.7.1",
|
|
540
540
|
"clsx": "^2.1.1",
|
|
@@ -557,7 +557,7 @@
|
|
|
557
557
|
"@larkup/sandbox": "^0.1.8",
|
|
558
558
|
"next": "16.3.8",
|
|
559
559
|
"@larkup/integrations": "0.2.1",
|
|
560
|
-
"@larkup/tool-doc-editor": "0.3.
|
|
560
|
+
"@larkup/tool-doc-editor": "0.3.2",
|
|
561
561
|
"@larkup/workspace-ui": "0.2.0"
|
|
562
562
|
},
|
|
563
563
|
"peerDependencies": {
|
|
@@ -4,6 +4,7 @@ import { workspaceUrl, workspaceFetch as fetch } from '../../host';
|
|
|
4
4
|
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
|
5
5
|
import { useChat } from '@ai-sdk/react';
|
|
6
6
|
import { DefaultChatTransport } from 'ai';
|
|
7
|
+
import { compactToolResult } from '../../lib/chat/message-compaction';
|
|
7
8
|
import type { RagConfig } from '@larkup/core/types';
|
|
8
9
|
import {
|
|
9
10
|
ArrowUp,
|
|
@@ -88,22 +89,6 @@ const fetcher = async <T,>(url: string): Promise<T> => {
|
|
|
88
89
|
return payload as T;
|
|
89
90
|
};
|
|
90
91
|
|
|
91
|
-
function compactToolResult(value: any): any {
|
|
92
|
-
if (!value || typeof value !== 'object') return value;
|
|
93
|
-
if (value.type === 'json' && 'value' in value) {
|
|
94
|
-
return { ...value, value: compactToolResult(value.value) };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const compact = { ...value };
|
|
98
|
-
if (Array.isArray(compact.rows)) compact.rows = compact.rows.slice(0, 50);
|
|
99
|
-
if (Array.isArray(compact.data)) compact.data = compact.data.slice(0, 100);
|
|
100
|
-
if (typeof compact.stdout === 'string') compact.stdout = compact.stdout.slice(0, 4_000);
|
|
101
|
-
if (typeof compact.stderr === 'string') compact.stderr = compact.stderr.slice(0, 1_000);
|
|
102
|
-
if (typeof compact.fileBase64 === 'string') delete compact.fileBase64;
|
|
103
|
-
if (Array.isArray(compact.pages)) delete compact.pages;
|
|
104
|
-
return compact;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
92
|
/**
|
|
108
93
|
* Compact messages for the LLM context to prevent massive payloads (like chart data,
|
|
109
94
|
* table rows, and document base64s) from bloating the request and exceeding token limits.
|
|
@@ -519,6 +519,7 @@ export function MessageItem({
|
|
|
519
519
|
return {
|
|
520
520
|
toolCallId,
|
|
521
521
|
percent: smoothPendingToolProgress(pendingToolStartedAt.current[toolCallId], progressClock),
|
|
522
|
+
indeterminate: true,
|
|
522
523
|
label: behavior.pendingLabel || 'Working…',
|
|
523
524
|
message: behavior.pendingLabel || 'Working…',
|
|
524
525
|
};
|
|
@@ -902,6 +903,7 @@ function renderToolPart(
|
|
|
902
903
|
toolProgress?: {
|
|
903
904
|
toolCallId: string;
|
|
904
905
|
percent: number;
|
|
906
|
+
indeterminate?: boolean;
|
|
905
907
|
label: string;
|
|
906
908
|
message?: string;
|
|
907
909
|
} | null,
|
|
@@ -929,7 +931,9 @@ function renderToolPart(
|
|
|
929
931
|
// Past the point where the remaining work has a measurable size, a
|
|
930
932
|
// percentage is a claim we cannot back. Drop it and shuttle the bar
|
|
931
933
|
// instead, so the wait still reads as active rather than stalled.
|
|
932
|
-
const indeterminate = liveProgress
|
|
934
|
+
const indeterminate = liveProgress
|
|
935
|
+
? liveProgress.indeterminate === true || isIndeterminateProgress(liveProgress.percent)
|
|
936
|
+
: false;
|
|
933
937
|
|
|
934
938
|
return (
|
|
935
939
|
<div key={index} className="flex min-w-0 flex-col gap-1.5 py-3">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect,
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { Image as ImageIcon, Film, AudioLines, Maximize2, X } from 'lucide-react';
|
|
5
5
|
import { workspaceUrl } from '../../../host';
|
|
6
6
|
|
|
@@ -32,6 +32,7 @@ export function ChatMediaPreview({
|
|
|
32
32
|
}) {
|
|
33
33
|
const [expanded, setExpanded] = useState(false);
|
|
34
34
|
const [imageUnavailable, setImageUnavailable] = useState(false);
|
|
35
|
+
const [videoUnavailable, setVideoUnavailable] = useState(false);
|
|
35
36
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
36
37
|
// Media is rendered only from a server-validated presentMedia result. Do
|
|
37
38
|
// not invent a URL from an identifier emitted in model text: it could point
|
|
@@ -42,10 +43,6 @@ export function ChatMediaPreview({
|
|
|
42
43
|
assetUrl && startSecs !== undefined
|
|
43
44
|
? `${assetUrl.split('#')[0]}#t=${startSecs}${endSecs !== undefined ? `,${endSecs}` : ''}`
|
|
44
45
|
: assetUrl;
|
|
45
|
-
const providerEmbedUrl = useMemo(
|
|
46
|
-
() => (mediaType === 'video' ? getProviderEmbedUrl(sourceUrl, startSecs, endSecs) : undefined),
|
|
47
|
-
[mediaType, sourceUrl, startSecs, endSecs],
|
|
48
|
-
);
|
|
49
46
|
const hasClipEnd =
|
|
50
47
|
mediaType === 'video' &&
|
|
51
48
|
Number.isFinite(startSecs) &&
|
|
@@ -133,35 +130,53 @@ export function ChatMediaPreview({
|
|
|
133
130
|
}
|
|
134
131
|
|
|
135
132
|
if (mediaType === 'video') {
|
|
133
|
+
if (videoUnavailable) {
|
|
134
|
+
return (
|
|
135
|
+
<div className="my-2 w-full max-w-2xl overflow-hidden rounded-lg border border-border bg-card">
|
|
136
|
+
<div className="flex aspect-video items-center justify-center bg-muted/30 px-6 text-center">
|
|
137
|
+
<div>
|
|
138
|
+
<Film className="mx-auto mb-2 size-6 text-muted-foreground" />
|
|
139
|
+
<p className="text-sm font-medium text-foreground">Preview unavailable</p>
|
|
140
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
141
|
+
Open the original source to watch this cited moment.
|
|
142
|
+
</p>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
<MediaCitationFooter
|
|
146
|
+
icon={<Film className="size-3 text-muted-foreground" />}
|
|
147
|
+
fileName={fileName}
|
|
148
|
+
sourceUrl={timestampedMediaUrl(sourceUrl, startSecs, endSecs)}
|
|
149
|
+
timestamp={
|
|
150
|
+
startSecs !== undefined
|
|
151
|
+
? `${formatTimestamp(startSecs)}${
|
|
152
|
+
endSecs !== undefined ? ` – ${formatTimestamp(endSecs)}` : ''
|
|
153
|
+
}`
|
|
154
|
+
: undefined
|
|
155
|
+
}
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
136
160
|
return (
|
|
137
161
|
<div className="my-2 w-full max-w-2xl overflow-hidden rounded-lg border border-border bg-card">
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// browsers. Stop local playback at the cited evidence boundary
|
|
157
|
-
// so a supporting clip never continues into unrelated footage.
|
|
158
|
-
if (hasClipEnd && event.currentTarget.currentTime >= (endSecs as number)) {
|
|
159
|
-
event.currentTarget.pause();
|
|
160
|
-
event.currentTarget.currentTime = endSecs as number;
|
|
161
|
-
}
|
|
162
|
-
}}
|
|
163
|
-
/>
|
|
164
|
-
)}
|
|
162
|
+
<video
|
|
163
|
+
ref={videoRef}
|
|
164
|
+
src={playbackUrl}
|
|
165
|
+
controls
|
|
166
|
+
playsInline
|
|
167
|
+
preload="metadata"
|
|
168
|
+
className="max-h-90 w-full bg-black object-contain"
|
|
169
|
+
onError={() => setVideoUnavailable(true)}
|
|
170
|
+
onTimeUpdate={(event) => {
|
|
171
|
+
// Media-fragment end times are not enforced consistently by
|
|
172
|
+
// browsers. Stop local playback at the cited evidence boundary
|
|
173
|
+
// so a supporting clip never continues into unrelated footage.
|
|
174
|
+
if (hasClipEnd && event.currentTarget.currentTime >= (endSecs as number)) {
|
|
175
|
+
event.currentTarget.pause();
|
|
176
|
+
event.currentTarget.currentTime = endSecs as number;
|
|
177
|
+
}
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
165
180
|
<MediaCitationFooter
|
|
166
181
|
icon={<Film className="size-3 text-muted-foreground" />}
|
|
167
182
|
fileName={fileName}
|
|
@@ -88,6 +88,7 @@ export function CorpusPanel({
|
|
|
88
88
|
activeVideoRuntimeScope,
|
|
89
89
|
initialGroupId,
|
|
90
90
|
isLoading,
|
|
91
|
+
error,
|
|
91
92
|
}: {
|
|
92
93
|
documents: SourceDocument[];
|
|
93
94
|
groups?: DataGroup[];
|
|
@@ -96,6 +97,7 @@ export function CorpusPanel({
|
|
|
96
97
|
activeVideoRuntimeScope: VideoRuntimeScope;
|
|
97
98
|
initialGroupId?: string;
|
|
98
99
|
isLoading?: boolean;
|
|
100
|
+
error?: string;
|
|
99
101
|
}) {
|
|
100
102
|
const [active, setActive] = useState<SourceDocument | null>(null);
|
|
101
103
|
const [deleteTask, setDeleteTask] = useState<
|
|
@@ -328,6 +330,21 @@ export function CorpusPanel({
|
|
|
328
330
|
return <CorpusSkeleton />;
|
|
329
331
|
}
|
|
330
332
|
|
|
333
|
+
if (error) {
|
|
334
|
+
return (
|
|
335
|
+
<div className="flex flex-col items-center justify-center rounded-lg border border-border bg-card/50 px-6 py-14 text-center">
|
|
336
|
+
<div className="size-10 p-1.5 bg-destructive/10 border border-destructive/20 rounded-md flex items-center justify-center text-destructive">
|
|
337
|
+
<Info className="size-6" />
|
|
338
|
+
</div>
|
|
339
|
+
<p className="mt-3 text-lg font-medium text-foreground">Could not load corpus data</p>
|
|
340
|
+
<p className="mt-1 max-w-xl text-sm text-muted-foreground">{error}</p>
|
|
341
|
+
<Button variant="outline" size="sm" className="mt-4" onClick={() => void onChanged()}>
|
|
342
|
+
Retry
|
|
343
|
+
</Button>
|
|
344
|
+
</div>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
331
348
|
if (clearingAll || groupedDocuments.length === 0) {
|
|
332
349
|
return (
|
|
333
350
|
<div className="flex flex-col items-center justify-center rounded-lg px-6 py-14 text-center">
|
|
@@ -190,9 +190,13 @@ export function DataWorkspace({ view }: { view?: TopTabId } = {}) {
|
|
|
190
190
|
const completedJobs = jobs.filter((job) => job.status === 'completed');
|
|
191
191
|
const [jobsFilterTab, setJobsFilterTab] = useState<'running' | 'all'>('running');
|
|
192
192
|
|
|
193
|
-
const docsQuery = useSWR<DocsResponse>(
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
const docsQuery = useSWR<DocsResponse>(
|
|
194
|
+
serverId ? workspaceUrl('/api/documents') : null,
|
|
195
|
+
fetcher,
|
|
196
|
+
{
|
|
197
|
+
refreshInterval: hasActive ? 5000 : 0,
|
|
198
|
+
},
|
|
199
|
+
);
|
|
196
200
|
const { mutate: mutateDocuments } = docsQuery;
|
|
197
201
|
const documents = docsQuery.data?.documents ?? [];
|
|
198
202
|
const groups = docsQuery.data?.groups ?? [];
|
|
@@ -211,6 +215,9 @@ export function DataWorkspace({ view }: { view?: TopTabId } = {}) {
|
|
|
211
215
|
docsQuery.data !== undefined &&
|
|
212
216
|
!docsQuery.error &&
|
|
213
217
|
(targetGroupId === 'default' || selectedGroup !== undefined);
|
|
218
|
+
const isCorpusLoading = Boolean(
|
|
219
|
+
!serverId || docsQuery.isLoading || (docsQuery.data === undefined && !docsQuery.error),
|
|
220
|
+
);
|
|
214
221
|
|
|
215
222
|
useEffect(() => {
|
|
216
223
|
if (dataAddBlocked) setPrimaryAction(null);
|
|
@@ -613,7 +620,14 @@ export function DataWorkspace({ view }: { view?: TopTabId } = {}) {
|
|
|
613
620
|
isIndexing={indexRunning}
|
|
614
621
|
activeVideoRuntimeScope={activeVideoRuntimeScope}
|
|
615
622
|
initialGroupId={searchParams.has('groupId') ? queryGroupId : undefined}
|
|
616
|
-
isLoading={
|
|
623
|
+
isLoading={isCorpusLoading}
|
|
624
|
+
error={
|
|
625
|
+
docsQuery.error
|
|
626
|
+
? docsQuery.error instanceof Error
|
|
627
|
+
? docsQuery.error.message
|
|
628
|
+
: String(docsQuery.error)
|
|
629
|
+
: undefined
|
|
630
|
+
}
|
|
617
631
|
/>
|
|
618
632
|
</div>
|
|
619
633
|
)}
|
|
@@ -7,6 +7,7 @@ import { useRouter } from 'next/navigation';
|
|
|
7
7
|
import { Database, FolderOpen } from 'lucide-react';
|
|
8
8
|
import { WorkspaceLineTabs, WorkspacePageHeading } from '@larkup/workspace-ui';
|
|
9
9
|
import type { DataGroup } from '@larkup/core/types';
|
|
10
|
+
import { useProject } from '../projects/project-provider';
|
|
10
11
|
import { DataWorkspace } from './data-workspace';
|
|
11
12
|
import { GroupsPanel } from './groups-panel';
|
|
12
13
|
|
|
@@ -15,8 +16,13 @@ const fetcher = (url: string) => fetch(url).then((response) => response.json());
|
|
|
15
16
|
/** Simple, tab-first source management for the active Project. */
|
|
16
17
|
export function ProjectDataPage() {
|
|
17
18
|
const router = useRouter();
|
|
19
|
+
const { activeProject } = useProject();
|
|
20
|
+
const serverId = activeProject?.id;
|
|
18
21
|
const [activeView, setActiveView] = useState<'all' | 'groups'>('all');
|
|
19
|
-
const { data } = useSWR<{ groups: DataGroup[] }>(
|
|
22
|
+
const { data } = useSWR<{ groups: DataGroup[] }>(
|
|
23
|
+
serverId ? workspaceUrl('/api/groups') : null,
|
|
24
|
+
fetcher,
|
|
25
|
+
);
|
|
20
26
|
const groups = data?.groups ?? [];
|
|
21
27
|
|
|
22
28
|
return (
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function compactToolResult(value: any): any {
|
|
2
|
+
if (!value || typeof value !== 'object') return value;
|
|
3
|
+
if (value.type === 'json' && 'value' in value) {
|
|
4
|
+
return { ...value, value: compactToolResult(value.value) };
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const compact = { ...value };
|
|
8
|
+
if (Array.isArray(compact.rows)) compact.rows = compact.rows.slice(0, 50);
|
|
9
|
+
if (Array.isArray(compact.data)) compact.data = compact.data.slice(0, 100);
|
|
10
|
+
if (typeof compact.stdout === 'string') compact.stdout = compact.stdout.slice(0, 4_000);
|
|
11
|
+
if (typeof compact.stderr === 'string') compact.stderr = compact.stderr.slice(0, 1_000);
|
|
12
|
+
if (typeof compact.fileBase64 === 'string') delete compact.fileBase64;
|
|
13
|
+
if (Array.isArray(compact.pages)) {
|
|
14
|
+
compact.pages = compact.pages.slice(0, 3).flatMap((page: any) => {
|
|
15
|
+
if (!page || typeof page !== 'object' || !Number.isInteger(page.pageNumber)) return [];
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
pageNumber: page.pageNumber,
|
|
19
|
+
...(typeof page.previewUrl === 'string' ? { previewUrl: page.previewUrl } : {}),
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return compact;
|
|
25
|
+
}
|