@lokvis/ui-react 0.2.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AssetPanel.d.ts.map +1 -1
- package/dist/components/AssetPanel.js +10 -54
- package/dist/components/AssetPanel.js.map +1 -1
- package/dist/components/Canvas.d.ts.map +1 -1
- package/dist/components/Canvas.js +4 -4
- package/dist/components/Canvas.js.map +1 -1
- package/dist/components/CompareSlider.d.ts +4 -3
- package/dist/components/CompareSlider.d.ts.map +1 -1
- package/dist/components/CompareSlider.js +5 -4
- package/dist/components/CompareSlider.js.map +1 -1
- package/dist/components/DownloadPanel.d.ts.map +1 -1
- package/dist/components/DownloadPanel.js +1 -8
- package/dist/components/DownloadPanel.js.map +1 -1
- package/dist/components/ErrorBoundary.d.ts +40 -0
- package/dist/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/ErrorBoundary.js +52 -0
- package/dist/components/ErrorBoundary.js.map +1 -0
- package/dist/components/ExifPanel.js +1 -1
- package/dist/components/ExifPanel.js.map +1 -1
- package/dist/components/GlobalDropzone.d.ts.map +1 -1
- package/dist/components/GlobalDropzone.js +10 -3
- package/dist/components/GlobalDropzone.js.map +1 -1
- package/dist/components/Inspector.d.ts.map +1 -1
- package/dist/components/Inspector.js +10 -7
- package/dist/components/Inspector.js.map +1 -1
- package/dist/components/ParamForm.d.ts.map +1 -1
- package/dist/components/ParamForm.js +8 -5
- package/dist/components/ParamForm.js.map +1 -1
- package/dist/components/PipelineBar.d.ts.map +1 -1
- package/dist/components/PipelineBar.js +17 -26
- package/dist/components/PipelineBar.js.map +1 -1
- package/dist/components/StatusBar.d.ts.map +1 -1
- package/dist/components/StatusBar.js +3 -12
- package/dist/components/StatusBar.js.map +1 -1
- package/dist/components/StatusDot.d.ts +12 -0
- package/dist/components/StatusDot.d.ts.map +1 -0
- package/dist/components/StatusDot.js +15 -0
- package/dist/components/StatusDot.js.map +1 -0
- package/dist/components/ThemeToggle.js +1 -1
- package/dist/components/ThemeToggle.js.map +1 -1
- package/dist/components/ToolRunner.d.ts +53 -0
- package/dist/components/ToolRunner.d.ts.map +1 -0
- package/dist/components/ToolRunner.js +142 -0
- package/dist/components/ToolRunner.js.map +1 -0
- package/dist/components/Toolbar.js +2 -2
- package/dist/components/Toolbar.js.map +1 -1
- package/dist/components/WorkflowEditor.d.ts.map +1 -1
- package/dist/components/WorkflowEditor.js +31 -40
- package/dist/components/WorkflowEditor.js.map +1 -1
- package/dist/components/WorkflowTemplates.d.ts.map +1 -1
- package/dist/components/WorkflowTemplates.js +15 -5
- package/dist/components/WorkflowTemplates.js.map +1 -1
- package/dist/components/Workspace.d.ts +23 -1
- package/dist/components/Workspace.d.ts.map +1 -1
- package/dist/components/Workspace.js +60 -13
- package/dist/components/Workspace.js.map +1 -1
- package/dist/hooks/useCustomPresets.d.ts +98 -0
- package/dist/hooks/useCustomPresets.d.ts.map +1 -0
- package/dist/hooks/useCustomPresets.js +210 -0
- package/dist/hooks/useCustomPresets.js.map +1 -0
- package/dist/hooks/useLokvis.d.ts +19 -1
- package/dist/hooks/useLokvis.d.ts.map +1 -1
- package/dist/hooks/useLokvis.js +30 -9
- package/dist/hooks/useLokvis.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/store/assets-slice.d.ts +8 -1
- package/dist/store/assets-slice.d.ts.map +1 -1
- package/dist/store/assets-slice.js +101 -54
- package/dist/store/assets-slice.js.map +1 -1
- package/dist/store/capabilities-slice.d.ts +1 -1
- package/dist/store/capabilities-slice.d.ts.map +1 -1
- package/dist/store/capabilities-slice.js +8 -1
- package/dist/store/capabilities-slice.js.map +1 -1
- package/dist/store/index.d.ts +1 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +1 -1
- package/dist/store/index.js.map +1 -1
- package/dist/store/runtime-slice.d.ts.map +1 -1
- package/dist/store/runtime-slice.js +22 -16
- package/dist/store/runtime-slice.js.map +1 -1
- package/dist/store/subscribe-utils.d.ts +45 -0
- package/dist/store/subscribe-utils.d.ts.map +1 -0
- package/dist/store/subscribe-utils.js +69 -0
- package/dist/store/subscribe-utils.js.map +1 -0
- package/dist/store/types.d.ts +19 -11
- package/dist/store/types.d.ts.map +1 -1
- package/dist/store/types.js +0 -10
- package/dist/store/types.js.map +1 -1
- package/dist/store/workflow-slice.d.ts +1 -1
- package/dist/store/workflow-slice.d.ts.map +1 -1
- package/dist/store/workflow-slice.js +19 -51
- package/dist/store/workflow-slice.js.map +1 -1
- package/dist/utils.d.ts +24 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +31 -0
- package/dist/utils.js.map +1 -0
- package/package.json +7 -6
- package/src/components/AssetPanel.tsx +18 -58
- package/src/components/Canvas.tsx +11 -7
- package/src/components/CompareSlider.tsx +13 -10
- package/src/components/DownloadPanel.tsx +1 -7
- package/src/components/ErrorBoundary.tsx +101 -0
- package/src/components/ExifPanel.tsx +1 -1
- package/src/components/GlobalDropzone.tsx +8 -3
- package/src/components/Inspector.tsx +24 -16
- package/src/components/ParamForm.tsx +12 -9
- package/src/components/PipelineBar.tsx +20 -27
- package/src/components/StatusBar.tsx +3 -10
- package/src/components/StatusDot.tsx +27 -0
- package/src/components/ThemeToggle.tsx +1 -1
- package/src/components/ToolRunner.tsx +352 -0
- package/src/components/Toolbar.tsx +2 -2
- package/src/components/WorkflowEditor.tsx +50 -43
- package/src/components/WorkflowTemplates.tsx +27 -4
- package/src/components/Workspace.tsx +97 -10
- package/src/hooks/useCustomPresets.ts +284 -0
- package/src/hooks/useLokvis.ts +43 -10
- package/src/index.ts +3 -0
- package/src/store/assets-slice.ts +106 -52
- package/src/store/capabilities-slice.ts +10 -2
- package/src/store/index.ts +1 -1
- package/src/store/runtime-slice.ts +20 -15
- package/src/store/subscribe-utils.ts +82 -0
- package/src/store/types.ts +19 -12
- package/src/store/workflow-slice.ts +19 -53
- package/src/utils.ts +41 -0
- package/dist/store.d.ts +0 -76
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js +0 -217
- package/dist/store.js.map +0 -1
package/dist/store/types.js
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 工作流最大节点数(M1 MVP 约束)。
|
|
3
|
-
*
|
|
4
|
-
* UI 层独立定义此常量而非 import runtime 包的 MAX_WORKFLOW_STEPS,
|
|
5
|
-
* 因为 ui-react → runtime 的方向依赖违反五层架构约束(UI → Workflow → Runtime)。
|
|
6
|
-
* 两层各自维护同值常量,通过测试 + 文档约定保持同步
|
|
7
|
-
* (runtime 测试断言 MAX_WORKFLOW_STEPS === 5,UI 层此常量也为 5)。
|
|
8
|
-
* 调整上限时需同步修改两处 + 相关注释。
|
|
9
|
-
*/
|
|
10
|
-
export const MAX_WORKFLOW_STEPS = 5;
|
|
11
1
|
/** 生成节点 ID */
|
|
12
2
|
export function genNodeId() {
|
|
13
3
|
return `node_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
package/dist/store/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"AAiKA,cAAc;AACd,MAAM,UAAU,SAAS;IACvB,OAAO,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AACrF,CAAC"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - 工作流执行(run):构建线性 Workflow → 调用 runtime.run → 更新节点状态
|
|
7
7
|
* - 清空(clearWorkflow)
|
|
8
8
|
*
|
|
9
|
-
* run() 内部依赖 buildLinearWorkflow(
|
|
9
|
+
* run() 内部依赖 buildLinearWorkflow(@lokvis/workflow)把节点序列构建为 Workflow 定义。
|
|
10
10
|
*/
|
|
11
11
|
import type { StateCreator } from 'zustand';
|
|
12
12
|
import type { WorkspaceStore, WorkspaceState, WorkspaceActions } from './types.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-slice.d.ts","sourceRoot":"","sources":["../../src/store/workflow-slice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"workflow-slice.d.ts","sourceRoot":"","sources":["../../src/store/workflow-slice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI5C,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAInF,MAAM,WAAW,aACf,SAAQ,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,gBAAgB,GAAG,eAAe,GAAG,kBAAkB,GAAG,cAAc,CAAC,EAC9G,IAAI,CACF,gBAAgB,EACd,SAAS,GACT,kBAAkB,GAClB,YAAY,GACZ,YAAY,GACZ,eAAe,GACf,UAAU,GACV,cAAc,GACd,KAAK,GACL,WAAW,GACX,sBAAsB,GACtB,eAAe,GACf,cAAc,GACd,cAAc,CACjB;CAAG;AAER,eAAO,MAAM,mBAAmB,EAAE,YAAY,CAC5C,cAAc,EACd;CAAE,EACF;CAAE,EACF,aAAa,CAoQb,CAAC"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MAX_WORKFLOW_STEPS } from '@lokvis/schema';
|
|
2
|
+
import { buildLinearWorkflow } from '@lokvis/workflow';
|
|
3
|
+
import { subscribeAll } from './subscribe-utils.js';
|
|
4
|
+
import { genNodeId } from './types.js';
|
|
2
5
|
export const createWorkflowSlice = (set, get) => ({
|
|
3
6
|
nodes: [],
|
|
4
7
|
selectedNodeId: null,
|
|
@@ -6,7 +9,7 @@ export const createWorkflowSlice = (set, get) => ({
|
|
|
6
9
|
selectedOutputId: null,
|
|
7
10
|
currentRunId: null,
|
|
8
11
|
addNode(capability) {
|
|
9
|
-
// W10.1/W10.3: 节点数上限(
|
|
12
|
+
// W10.1/W10.3: 节点数上限(MAX_WORKFLOW_STEPS,单一源 @lokvis/schema)
|
|
10
13
|
if (get().nodes.length >= MAX_WORKFLOW_STEPS) {
|
|
11
14
|
get().setError(`工作流最多 ${MAX_WORKFLOW_STEPS} 个节点(M1 MVP 限制),请先删除不需要的节点`);
|
|
12
15
|
return;
|
|
@@ -119,7 +122,7 @@ export const createWorkflowSlice = (set, get) => ({
|
|
|
119
122
|
// 等 run() 的 await runtime.run() 返回后,由 run() 统一归位。
|
|
120
123
|
},
|
|
121
124
|
async run() {
|
|
122
|
-
const { runtime, nodes, selectedAssetId, running } = get();
|
|
125
|
+
const { runtime, nodes, selectedAssetId, running, assets } = get();
|
|
123
126
|
if (!runtime)
|
|
124
127
|
throw new Error('Runtime not initialized');
|
|
125
128
|
if (nodes.length === 0)
|
|
@@ -144,19 +147,21 @@ export const createWorkflowSlice = (set, get) => ({
|
|
|
144
147
|
//
|
|
145
148
|
// 修复:订阅 node:started / node:finished / node:failed 事件实时更新节点状态,
|
|
146
149
|
// 这样 completed/failed/cancelled 三种情况下节点状态都精确
|
|
147
|
-
const
|
|
148
|
-
get().setNodeStatus(e.nodeId, 'running')
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const errMsg = e.error instanceof Error ? e.error.message : String(e.error);
|
|
155
|
-
get().setNodeStatus(e.nodeId, 'failed', errMsg);
|
|
150
|
+
const offAll = subscribeAll(runtime.eventBus, {
|
|
151
|
+
'node:started': (e) => get().setNodeStatus(e.nodeId, 'running'),
|
|
152
|
+
'node:finished': (e) => get().setNodeStatus(e.nodeId, 'success', undefined, e.duration),
|
|
153
|
+
'node:failed': (e) => {
|
|
154
|
+
const errMsg = e.error instanceof Error ? e.error.message : String(e.error);
|
|
155
|
+
get().setNodeStatus(e.nodeId, 'failed', errMsg);
|
|
156
|
+
},
|
|
156
157
|
});
|
|
157
158
|
try {
|
|
159
|
+
// 从选中资产推导输入类型(去硬编码 'image')
|
|
160
|
+
const selectedAsset = assets.find((a) => a.id === selectedAssetId);
|
|
161
|
+
if (!selectedAsset)
|
|
162
|
+
throw new Error('Selected asset not found');
|
|
158
163
|
// 构建 Workflow
|
|
159
|
-
const workflow = buildLinearWorkflow(nodes);
|
|
164
|
+
const workflow = buildLinearWorkflow(nodes, selectedAsset.type);
|
|
160
165
|
// W11.6: 记录当前 workflowId 供 cancelRun() 使用
|
|
161
166
|
set({ currentRunId: workflow.id });
|
|
162
167
|
const input = await runtime.getAsset(selectedAssetId);
|
|
@@ -227,9 +232,7 @@ export const createWorkflowSlice = (set, get) => ({
|
|
|
227
232
|
finally {
|
|
228
233
|
// W11.6: 清除 currentRunId(成功/失败/取消都应清除)
|
|
229
234
|
set({ currentRunId: null });
|
|
230
|
-
|
|
231
|
-
offFinished();
|
|
232
|
-
offNodeFailed();
|
|
235
|
+
offAll();
|
|
233
236
|
}
|
|
234
237
|
},
|
|
235
238
|
clearWorkflow() {
|
|
@@ -242,39 +245,4 @@ export const createWorkflowSlice = (set, get) => ({
|
|
|
242
245
|
set({ lastOutputIds: [], selectedOutputId: null });
|
|
243
246
|
},
|
|
244
247
|
});
|
|
245
|
-
/** 把工作台节点序列构建为线性 Workflow */
|
|
246
|
-
function buildLinearWorkflow(nodes) {
|
|
247
|
-
if (nodes.length === 0) {
|
|
248
|
-
throw new Error('Workflow is empty');
|
|
249
|
-
}
|
|
250
|
-
const workflowNodes = nodes.map((n) => ({
|
|
251
|
-
id: n.id,
|
|
252
|
-
type: 'transform',
|
|
253
|
-
capability: n.capability,
|
|
254
|
-
params: n.params,
|
|
255
|
-
}));
|
|
256
|
-
const edges = [];
|
|
257
|
-
for (let i = 0; i < workflowNodes.length - 1; i++) {
|
|
258
|
-
const fromNode = workflowNodes[i];
|
|
259
|
-
const toNode = workflowNodes[i + 1];
|
|
260
|
-
if (!fromNode || !toNode)
|
|
261
|
-
continue;
|
|
262
|
-
edges.push({ from: fromNode.id, to: toNode.id });
|
|
263
|
-
}
|
|
264
|
-
return {
|
|
265
|
-
id: `wf_${Date.now().toString(36)}`,
|
|
266
|
-
name: 'Workspace Workflow',
|
|
267
|
-
version: '1',
|
|
268
|
-
description: 'Workspace linear workflow',
|
|
269
|
-
author: { id: 'local', name: 'Local User' },
|
|
270
|
-
category: 'image',
|
|
271
|
-
tags: [],
|
|
272
|
-
nodes: workflowNodes,
|
|
273
|
-
edges,
|
|
274
|
-
inputs: { type: 'image', multiple: true },
|
|
275
|
-
outputs: { type: 'image' },
|
|
276
|
-
createdAt: Date.now(),
|
|
277
|
-
updatedAt: Date.now(),
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
248
|
//# sourceMappingURL=workflow-slice.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-slice.js","sourceRoot":"","sources":["../../src/store/workflow-slice.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"workflow-slice.js","sourceRoot":"","sources":["../../src/store/workflow-slice.ts"],"names":[],"mappings":"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"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ui-react 共享工具函数
|
|
3
|
+
*
|
|
4
|
+
* 抽取自多个组件的重复逻辑(formatBytes / filterCapabilities),
|
|
5
|
+
* 避免散落在各组件中的同名实现 drift。
|
|
6
|
+
*/
|
|
7
|
+
import type { Capability } from '@lokvis/schema';
|
|
8
|
+
/**
|
|
9
|
+
* 字节数格式化为人类可读字符串(B / KB / MB / GB)。
|
|
10
|
+
*
|
|
11
|
+
* 统一使用 1024 进制,保留合理小数位:
|
|
12
|
+
* - B:整数
|
|
13
|
+
* - KB:1 位小数
|
|
14
|
+
* - MB:1 位小数
|
|
15
|
+
* - GB:2 位小数
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatBytes(bytes: number): string;
|
|
18
|
+
/**
|
|
19
|
+
* 按搜索关键词过滤能力列表(匹配 name 或 description,大小写不敏感)。
|
|
20
|
+
*
|
|
21
|
+
* 用于 Inspector 能力列表与 WorkflowEditor InsertConnector 搜索。
|
|
22
|
+
*/
|
|
23
|
+
export declare function filterCapabilities(capabilities: Capability[], query: string): Capability[];
|
|
24
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,UAAU,EAAE,EAC1B,KAAK,EAAE,MAAM,GACZ,UAAU,EAAE,CAOd"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 字节数格式化为人类可读字符串(B / KB / MB / GB)。
|
|
3
|
+
*
|
|
4
|
+
* 统一使用 1024 进制,保留合理小数位:
|
|
5
|
+
* - B:整数
|
|
6
|
+
* - KB:1 位小数
|
|
7
|
+
* - MB:1 位小数
|
|
8
|
+
* - GB:2 位小数
|
|
9
|
+
*/
|
|
10
|
+
export function formatBytes(bytes) {
|
|
11
|
+
if (bytes < 0)
|
|
12
|
+
return '0 B';
|
|
13
|
+
if (bytes < 1024)
|
|
14
|
+
return `${bytes} B`;
|
|
15
|
+
if (bytes < 1024 * 1024)
|
|
16
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
17
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
18
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
19
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 按搜索关键词过滤能力列表(匹配 name 或 description,大小写不敏感)。
|
|
23
|
+
*
|
|
24
|
+
* 用于 Inspector 能力列表与 WorkflowEditor InsertConnector 搜索。
|
|
25
|
+
*/
|
|
26
|
+
export function filterCapabilities(capabilities, query) {
|
|
27
|
+
const q = query.toLowerCase();
|
|
28
|
+
return capabilities.filter((c) => c.name.toLowerCase().includes(q) ||
|
|
29
|
+
c.description.toLowerCase().includes(q));
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAQA;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5B,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,IAAI,CAAC;IACtC,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAClE,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAClF,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,YAA0B,EAC1B,KAAa;IAEb,MAAM,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAC9B,OAAO,YAAY,CAAC,MAAM,CACxB,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAChC,CAAC,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAC1C,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lokvis/ui-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Lokvis Workspace UI - React components for Image/Video/PDF Workspace",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,11 +31,12 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"zustand": "^5.0.0",
|
|
34
|
-
"@lokvis/schema": "0.
|
|
35
|
-
"@lokvis/
|
|
36
|
-
"@lokvis/
|
|
37
|
-
"@lokvis/
|
|
38
|
-
"@lokvis/
|
|
34
|
+
"@lokvis/schema": "0.4.1",
|
|
35
|
+
"@lokvis/runtime": "0.4.1",
|
|
36
|
+
"@lokvis/sdk": "0.4.1",
|
|
37
|
+
"@lokvis/ui-core": "0.4.1",
|
|
38
|
+
"@lokvis/workflow": "0.4.1",
|
|
39
|
+
"@lokvis/capability": "0.4.1"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"@types/react": "^19.0.0",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import * as React from 'react';
|
|
9
9
|
import type { AssetType } from '@lokvis/schema';
|
|
10
|
-
import { Icon } from '@lokvis/ui-core';
|
|
10
|
+
import { FOCUS_RING, Icon, Input } from '@lokvis/ui-core';
|
|
11
11
|
import { useWorkspaceStore } from '../store/index.js';
|
|
12
12
|
|
|
13
13
|
export interface AssetPanelProps {
|
|
@@ -32,15 +32,12 @@ export function AssetPanel({ className = '' }: AssetPanelProps) {
|
|
|
32
32
|
const importFiles = useWorkspaceStore((s) => s.importFiles);
|
|
33
33
|
const selectAsset = useWorkspaceStore((s) => s.selectAsset);
|
|
34
34
|
const removeAsset = useWorkspaceStore((s) => s.removeAsset);
|
|
35
|
-
const
|
|
36
|
-
const runtime = useWorkspaceStore((s) => s.runtime);
|
|
35
|
+
const ensureThumbnails = useWorkspaceStore((s) => s.ensureThumbnails);
|
|
37
36
|
|
|
38
37
|
const [dragging, setDragging] = React.useState(false);
|
|
39
38
|
// W6.5 筛选:类型 + 关键词(纯 UI 状态,不入 store)
|
|
40
39
|
const [filterType, setFilterType] = React.useState<'all' | AssetType>('all');
|
|
41
40
|
const [searchQuery, setSearchQuery] = React.useState('');
|
|
42
|
-
// B2 修复:跟踪 in-flight exportAsset,避免同一 asset 跨 effect 重跑并发发起
|
|
43
|
-
const inflightRef = React.useRef<Set<string>>(new Set());
|
|
44
41
|
|
|
45
42
|
async function handleFiles(files: FileList | File[] | null) {
|
|
46
43
|
if (!files) return;
|
|
@@ -48,45 +45,11 @@ export function AssetPanel({ className = '' }: AssetPanelProps) {
|
|
|
48
45
|
if (arr.length > 0) await importFiles(arr);
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// - cancelled flag:effect 重跑/卸载时,await 完成的 exportAsset 不再创建
|
|
54
|
-
// ObjectURL 也不 setThumbnail,避免覆盖更新值与孤儿 URL
|
|
55
|
-
// - inflightRef:同一 asset 在上一轮 effect 还在 await 时,新一轮 effect
|
|
56
|
-
// 不再重复发起(批量导入 assets 频繁变化场景)
|
|
48
|
+
// TD-5.1:缩略图生成统一在 store.ensureThumbnails 中管理(创建/替换/释放闭环)。
|
|
49
|
+
// 组件只需在 assets 变化时触发一次,inflight 去重 + 资产存在性检查在 store 内完成。
|
|
57
50
|
React.useEffect(() => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const inflight = inflightRef.current;
|
|
61
|
-
for (const asset of assets) {
|
|
62
|
-
if (thumbnails[asset.id]) continue;
|
|
63
|
-
if (asset.type !== 'image') continue;
|
|
64
|
-
if (inflight.has(asset.id)) continue;
|
|
65
|
-
inflight.add(asset.id);
|
|
66
|
-
(async () => {
|
|
67
|
-
try {
|
|
68
|
-
const blob = await runtime.exportAsset(asset.id);
|
|
69
|
-
// effect 已过期或资产已删除:不创建 ObjectURL(零泄漏)
|
|
70
|
-
if (cancelled) return;
|
|
71
|
-
const url = URL.createObjectURL(blob);
|
|
72
|
-
if (cancelled) {
|
|
73
|
-
// 双重检查:await 与 createObjectURL 之间可能被取消
|
|
74
|
-
URL.revokeObjectURL(url);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
setThumbnail(asset.id, url);
|
|
78
|
-
} catch {
|
|
79
|
-
// ignore export 失败
|
|
80
|
-
} finally {
|
|
81
|
-
inflight.delete(asset.id);
|
|
82
|
-
}
|
|
83
|
-
})();
|
|
84
|
-
}
|
|
85
|
-
return () => {
|
|
86
|
-
cancelled = true;
|
|
87
|
-
};
|
|
88
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
89
|
-
}, [assets, runtime]);
|
|
51
|
+
ensureThumbnails();
|
|
52
|
+
}, [assets, ensureThumbnails]);
|
|
90
53
|
|
|
91
54
|
// 资产中实际存在的类型(仅展示有意义的 chip,避免空类型噪音)
|
|
92
55
|
const availableTypes = React.useMemo(() => {
|
|
@@ -127,7 +90,7 @@ export function AssetPanel({ className = '' }: AssetPanelProps) {
|
|
|
127
90
|
}}
|
|
128
91
|
>
|
|
129
92
|
{/* Header */}
|
|
130
|
-
<div className="flex items-center justify-between px-3 h-
|
|
93
|
+
<div className="flex items-center justify-between px-3 h-[var(--lokvis-panel-header-h)] shrink-0 border-b border-[var(--lokvis-border)]">
|
|
131
94
|
<span className="text-[11px] font-semibold uppercase tracking-wider text-[var(--lokvis-fg-subtle)]">
|
|
132
95
|
Assets
|
|
133
96
|
</span>
|
|
@@ -143,7 +106,7 @@ export function AssetPanel({ className = '' }: AssetPanelProps) {
|
|
|
143
106
|
className={`m-2 block cursor-pointer rounded-lg border border-dashed px-2 py-3 text-center transition-all ${
|
|
144
107
|
dragging
|
|
145
108
|
? 'border-[var(--lokvis-primary)] bg-[var(--lokvis-primary)]/10 text-[var(--lokvis-primary)]'
|
|
146
|
-
: 'border-[var(--lokvis-border)] text-[var(--lokvis-fg-subtle)] hover:border-[var(--lokvis-primary)]/50 hover:bg-[var(--lokvis-primary)]/
|
|
109
|
+
: 'border-[var(--lokvis-border)] text-[var(--lokvis-fg-subtle)] hover:border-[var(--lokvis-primary)]/50 hover:bg-[var(--lokvis-primary)]/5 hover:text-[var(--lokvis-primary)]'
|
|
147
110
|
}`}
|
|
148
111
|
>
|
|
149
112
|
<input
|
|
@@ -200,24 +163,21 @@ export function AssetPanel({ className = '' }: AssetPanelProps) {
|
|
|
200
163
|
))}
|
|
201
164
|
</div>
|
|
202
165
|
{/* 搜索框:m4 文案改为与实际过滤行为一致(按 format/mimeType) */}
|
|
203
|
-
<
|
|
204
|
-
|
|
205
|
-
size={11}
|
|
206
|
-
className="absolute left-1.5 top-1/2 -translate-y-1/2 text-[var(--lokvis-fg-subtle)] pointer-events-none"
|
|
207
|
-
>
|
|
208
|
-
<circle cx="10" cy="10" r="6" />
|
|
209
|
-
<path d="m20 20-5-5" />
|
|
210
|
-
</Icon>
|
|
211
|
-
<input
|
|
212
|
-
type="text"
|
|
166
|
+
<Input
|
|
167
|
+
size="sm"
|
|
213
168
|
value={searchQuery}
|
|
214
169
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
215
170
|
placeholder="Filter by type..."
|
|
216
171
|
aria-label="Filter assets by format"
|
|
217
|
-
className="
|
|
172
|
+
className="bg-[var(--lokvis-surface-muted)] hover:bg-[var(--lokvis-border)]/50"
|
|
173
|
+
leadingIcon={
|
|
174
|
+
<Icon size={11}>
|
|
175
|
+
<circle cx="10" cy="10" r="6" />
|
|
176
|
+
<path d="m20 20-5-5" />
|
|
177
|
+
</Icon>
|
|
178
|
+
}
|
|
218
179
|
/>
|
|
219
180
|
</div>
|
|
220
|
-
</div>
|
|
221
181
|
)}
|
|
222
182
|
|
|
223
183
|
{/* Asset list */}
|
|
@@ -250,7 +210,7 @@ export function AssetPanel({ className = '' }: AssetPanelProps) {
|
|
|
250
210
|
selectAsset(asset.id);
|
|
251
211
|
}
|
|
252
212
|
}}
|
|
253
|
-
className={`group flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-xs transition-all
|
|
213
|
+
className={`group flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-xs transition-all ${FOCUS_RING} ${
|
|
254
214
|
selected
|
|
255
215
|
? 'bg-[var(--lokvis-primary)]/10 ring-1 ring-[var(--lokvis-primary)]/40'
|
|
256
216
|
: 'hover:bg-[var(--lokvis-surface)]'
|
|
@@ -86,14 +86,18 @@ export function Canvas({ className = '', enableCompare = true }: CanvasProps) {
|
|
|
86
86
|
<div
|
|
87
87
|
className="absolute inset-0 opacity-[0.35]"
|
|
88
88
|
style={{
|
|
89
|
-
backgroundImage: 'radial-gradient(circle,
|
|
89
|
+
backgroundImage: 'radial-gradient(circle, var(--lokvis-border) 1px, transparent 1px)',
|
|
90
90
|
backgroundSize: '20px 20px',
|
|
91
91
|
}}
|
|
92
92
|
/>
|
|
93
93
|
|
|
94
94
|
{/* W9.4 Compare 模式切换按钮(右上) */}
|
|
95
95
|
{canCompare && (
|
|
96
|
-
<div
|
|
96
|
+
<div
|
|
97
|
+
role="group"
|
|
98
|
+
aria-label="对比模式切换"
|
|
99
|
+
className="absolute top-2 right-2 z-20 flex items-center gap-1 rounded-md bg-[var(--lokvis-surface)]/90 p-0.5 shadow-[var(--lokvis-elevation-1)] backdrop-blur-sm"
|
|
100
|
+
>
|
|
97
101
|
<button
|
|
98
102
|
type="button"
|
|
99
103
|
onClick={() => setCompareMode(false)}
|
|
@@ -122,7 +126,7 @@ export function Canvas({ className = '', enableCompare = true }: CanvasProps) {
|
|
|
122
126
|
)}
|
|
123
127
|
|
|
124
128
|
{dragOver && (
|
|
125
|
-
<div className="absolute inset-4 z-10 rounded-xl border-2 border-dashed border-[var(--lokvis-primary)] bg-[var(--lokvis-primary)]/10
|
|
129
|
+
<div className="absolute inset-4 z-10 rounded-xl border-2 border-dashed border-[var(--lokvis-primary)] bg-[var(--lokvis-primary)]/10 flex items-center justify-center">
|
|
126
130
|
<div className="text-center">
|
|
127
131
|
<Icon size={32} className="mx-auto text-[var(--lokvis-primary)]" strokeWidth={1.5}><path d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5" /></Icon>
|
|
128
132
|
<p className="mt-2 text-sm font-medium text-[var(--lokvis-primary)]">Drop files to import</p>
|
|
@@ -141,10 +145,10 @@ export function Canvas({ className = '', enableCompare = true }: CanvasProps) {
|
|
|
141
145
|
<img
|
|
142
146
|
src={preview}
|
|
143
147
|
alt={selected.metadata.format}
|
|
144
|
-
className="max-h-[calc(100%-4rem)] max-w-[calc(100%-4rem)] rounded-lg object-contain shadow-
|
|
148
|
+
className="max-h-[calc(100%-4rem)] max-w-[calc(100%-4rem)] rounded-lg object-contain shadow-[var(--lokvis-elevation-3)] ring-1 ring-[var(--lokvis-border)]/50"
|
|
145
149
|
/>
|
|
146
150
|
{/* Metadata overlay */}
|
|
147
|
-
<div className="absolute bottom-2 left-2 rounded-lg bg-[var(--lokvis-surface)]/90 px-2.5 py-1 text-[11px] text-[var(--lokvis-fg-muted)] shadow-
|
|
151
|
+
<div className="absolute bottom-2 left-2 rounded-lg bg-[var(--lokvis-surface)]/90 px-2.5 py-1 text-[11px] text-[var(--lokvis-fg-muted)] shadow-[var(--lokvis-elevation-1)] backdrop-blur-sm">
|
|
148
152
|
<div className="flex items-center gap-2">
|
|
149
153
|
<span className="font-medium">{selected.metadata.format.toUpperCase()}</span>
|
|
150
154
|
{selected.metadata.dimensions && (
|
|
@@ -166,7 +170,7 @@ export function Canvas({ className = '', enableCompare = true }: CanvasProps) {
|
|
|
166
170
|
)
|
|
167
171
|
) : (
|
|
168
172
|
<div className="relative flex flex-col items-center gap-5 px-6 text-center">
|
|
169
|
-
<div className="flex h-20 w-20 items-center justify-center rounded-2xl bg-[var(--lokvis-surface)] shadow-
|
|
173
|
+
<div className="flex h-20 w-20 items-center justify-center rounded-2xl bg-[var(--lokvis-surface)] shadow-[var(--lokvis-elevation-1)] ring-1 ring-[var(--lokvis-border)]/80">
|
|
170
174
|
<Icon size={36} className="text-[var(--lokvis-fg-subtle)]" strokeWidth={1.5}>
|
|
171
175
|
<path d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
|
|
172
176
|
</Icon>
|
|
@@ -180,7 +184,7 @@ export function Canvas({ className = '', enableCompare = true }: CanvasProps) {
|
|
|
180
184
|
</p>
|
|
181
185
|
</div>
|
|
182
186
|
{assets.length === 0 && (
|
|
183
|
-
<label className="mt-2 inline-flex items-center gap-1.5 rounded-lg bg-[var(--lokvis-primary-hover)] px-4 py-2 text-xs font-medium text-[var(--lokvis-primary-fg)] shadow-
|
|
187
|
+
<label className="mt-2 inline-flex items-center gap-1.5 rounded-lg bg-[var(--lokvis-primary-hover)] px-4 py-2 text-xs font-medium text-[var(--lokvis-primary-fg)] shadow-[var(--lokvis-elevation-1)] cursor-pointer transition-colors hover:bg-[var(--lokvis-primary)]">
|
|
184
188
|
<Icon size={14}><path d="M12 4.5v15m7.5-7.5h-15" /></Icon>
|
|
185
189
|
Import Files
|
|
186
190
|
<input
|
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
* - 键盘:← → 调整 5% 步长(聚焦分隔条时)
|
|
11
11
|
*
|
|
12
12
|
* 数据来源:
|
|
13
|
-
* - before: `store.selectedAssetId` 对应的 thumbnails(由
|
|
13
|
+
* - before: `store.selectedAssetId` 对应的 thumbnails(由 store.ensureThumbnails 生成)
|
|
14
14
|
* - after: `store.selectedOutputId` 对应的 thumbnails
|
|
15
15
|
* - 任一缺失则不渲染(返回 null)
|
|
16
16
|
*
|
|
17
|
-
* 注:本组件只负责"展示对比",不负责生成缩略图。缩略图由
|
|
18
|
-
*
|
|
17
|
+
* 注:本组件只负责"展示对比",不负责生成缩略图。缩略图由 store.ensureThumbnails
|
|
18
|
+
* 统一生成(TD-5.1:创建/替换/释放在 store 内闭环)并存入 store.thumbnails,
|
|
19
|
+
* 本组件按 assetId 取用。
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
import * as React from 'react';
|
|
@@ -112,7 +113,9 @@ export function CompareSlider({ className = '' }: CompareSliderProps) {
|
|
|
112
113
|
return (
|
|
113
114
|
<div
|
|
114
115
|
ref={containerRef}
|
|
115
|
-
|
|
116
|
+
role="group"
|
|
117
|
+
aria-label="前后对比滑块"
|
|
118
|
+
className={`relative select-none overflow-hidden rounded-lg ring-1 ring-[var(--lokvis-border)]/50 ${className}`}
|
|
116
119
|
onMouseDown={handleMouseDown}
|
|
117
120
|
onTouchStart={handleTouchStart}
|
|
118
121
|
>
|
|
@@ -139,10 +142,10 @@ export function CompareSlider({ className = '' }: CompareSliderProps) {
|
|
|
139
142
|
</div>
|
|
140
143
|
|
|
141
144
|
{/* before/after 标签 */}
|
|
142
|
-
<span className="absolute top-2 left-2 rounded bg-
|
|
145
|
+
<span className="absolute top-2 left-2 rounded bg-[var(--lokvis-scrim)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--lokvis-primary-fg)]">
|
|
143
146
|
Before
|
|
144
147
|
</span>
|
|
145
|
-
<span className="absolute top-2 right-2 rounded bg-
|
|
148
|
+
<span className="absolute top-2 right-2 rounded bg-[var(--lokvis-scrim)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--lokvis-primary-fg)]">
|
|
146
149
|
After
|
|
147
150
|
</span>
|
|
148
151
|
|
|
@@ -158,8 +161,8 @@ export function CompareSlider({ className = '' }: CompareSliderProps) {
|
|
|
158
161
|
tabIndex={0}
|
|
159
162
|
onKeyDown={handleKeyDown}
|
|
160
163
|
>
|
|
161
|
-
<div className="absolute inset-y-0 left-1/2 w-0.5 -translate-x-1/2 bg-[var(--lokvis-surface)] shadow-
|
|
162
|
-
<div className="absolute top-1/2 left-1/2 flex h-8 w-8 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-[var(--lokvis-surface)] shadow-
|
|
164
|
+
<div className="absolute inset-y-0 left-1/2 w-0.5 -translate-x-1/2 bg-[var(--lokvis-surface)] shadow-[var(--lokvis-elevation-1)]" />
|
|
165
|
+
<div className="absolute top-1/2 left-1/2 flex h-8 w-8 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-[var(--lokvis-surface)] shadow-[var(--lokvis-elevation-2)] ring-1 ring-[var(--lokvis-border)]">
|
|
163
166
|
<Icon size={14} className="text-[var(--lokvis-fg-muted)]">
|
|
164
167
|
<path d="M9 5l-5 7 5 7M15 5l5 7-5 7" />
|
|
165
168
|
</Icon>
|
|
@@ -170,12 +173,12 @@ export function CompareSlider({ className = '' }: CompareSliderProps) {
|
|
|
170
173
|
{(beforeAsset || afterAsset) && (
|
|
171
174
|
<div className="absolute bottom-2 inset-x-2 flex justify-between text-[10px] text-[var(--lokvis-primary-fg)]">
|
|
172
175
|
{beforeAsset?.metadata.dimensions && (
|
|
173
|
-
<span className="rounded bg-
|
|
176
|
+
<span className="rounded bg-[var(--lokvis-scrim)] px-1.5 py-0.5">
|
|
174
177
|
{beforeAsset.metadata.dimensions.width}×{beforeAsset.metadata.dimensions.height}
|
|
175
178
|
</span>
|
|
176
179
|
)}
|
|
177
180
|
{afterAsset?.metadata.dimensions && (
|
|
178
|
-
<span className="rounded bg-
|
|
181
|
+
<span className="rounded bg-[var(--lokvis-scrim)] px-1.5 py-0.5">
|
|
179
182
|
{afterAsset.metadata.dimensions.width}×{afterAsset.metadata.dimensions.height}
|
|
180
183
|
</span>
|
|
181
184
|
)}
|
|
@@ -16,18 +16,12 @@
|
|
|
16
16
|
import * as React from 'react';
|
|
17
17
|
import { Icon } from '@lokvis/ui-core';
|
|
18
18
|
import { useWorkspaceStore } from '../store/index.js';
|
|
19
|
+
import { formatBytes } from '../utils.js';
|
|
19
20
|
|
|
20
21
|
export interface DownloadPanelProps {
|
|
21
22
|
className?: string;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
/** 格式化字节数 */
|
|
25
|
-
function formatBytes(bytes: number): string {
|
|
26
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
27
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
28
|
-
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
25
|
/**
|
|
32
26
|
* 批量下载时每项之间的延迟毫秒数。
|
|
33
27
|
*
|