@lokvis/ui-react 0.1.0 → 0.2.0
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/dist/components/AssetPanel.d.ts +2 -2
- package/dist/components/AssetPanel.d.ts.map +1 -1
- package/dist/components/AssetPanel.js +80 -7
- package/dist/components/AssetPanel.js.map +1 -1
- package/dist/components/Canvas.d.ts +9 -1
- package/dist/components/Canvas.d.ts.map +1 -1
- package/dist/components/Canvas.js +41 -3
- package/dist/components/Canvas.js.map +1 -1
- package/dist/components/CommandPalette.d.ts +48 -0
- package/dist/components/CommandPalette.d.ts.map +1 -0
- package/dist/components/CommandPalette.js +154 -0
- package/dist/components/CommandPalette.js.map +1 -0
- package/dist/components/CompareSlider.d.ts +25 -0
- package/dist/components/CompareSlider.d.ts.map +1 -0
- package/dist/components/CompareSlider.js +106 -0
- package/dist/components/CompareSlider.js.map +1 -0
- package/dist/components/DownloadPanel.d.ts +20 -0
- package/dist/components/DownloadPanel.d.ts.map +1 -0
- package/dist/components/DownloadPanel.js +169 -0
- package/dist/components/DownloadPanel.js.map +1 -0
- package/dist/components/ErrorBanner.d.ts +22 -0
- package/dist/components/ErrorBanner.d.ts.map +1 -0
- package/dist/components/ErrorBanner.js +47 -0
- package/dist/components/ErrorBanner.js.map +1 -0
- package/dist/components/ExifPanel.d.ts +26 -0
- package/dist/components/ExifPanel.d.ts.map +1 -0
- package/dist/components/ExifPanel.js +104 -0
- package/dist/components/ExifPanel.js.map +1 -0
- package/dist/components/GlobalDropzone.d.ts +37 -0
- package/dist/components/GlobalDropzone.d.ts.map +1 -0
- package/dist/components/GlobalDropzone.js +154 -0
- package/dist/components/GlobalDropzone.js.map +1 -0
- package/dist/components/HistoryPanel.d.ts +17 -7
- package/dist/components/HistoryPanel.d.ts.map +1 -1
- package/dist/components/HistoryPanel.js +74 -62
- package/dist/components/HistoryPanel.js.map +1 -1
- package/dist/components/Inspector.d.ts.map +1 -1
- package/dist/components/Inspector.js +3 -1
- package/dist/components/Inspector.js.map +1 -1
- package/dist/components/PipelineBar.d.ts.map +1 -1
- package/dist/components/PipelineBar.js +4 -3
- package/dist/components/PipelineBar.js.map +1 -1
- package/dist/components/ProgressBar.d.ts +18 -0
- package/dist/components/ProgressBar.d.ts.map +1 -0
- package/dist/components/ProgressBar.js +35 -0
- package/dist/components/ProgressBar.js.map +1 -0
- package/dist/components/StatusBar.d.ts +8 -2
- package/dist/components/StatusBar.d.ts.map +1 -1
- package/dist/components/StatusBar.js +59 -4
- package/dist/components/StatusBar.js.map +1 -1
- package/dist/components/ThemeToggle.d.ts +20 -0
- package/dist/components/ThemeToggle.d.ts.map +1 -0
- package/dist/components/ThemeToggle.js +60 -0
- package/dist/components/ThemeToggle.js.map +1 -0
- package/dist/components/WorkflowEditor.d.ts +24 -0
- package/dist/components/WorkflowEditor.d.ts.map +1 -0
- package/dist/components/WorkflowEditor.js +160 -0
- package/dist/components/WorkflowEditor.js.map +1 -0
- package/dist/components/WorkflowTemplates.d.ts +23 -0
- package/dist/components/WorkflowTemplates.d.ts.map +1 -0
- package/dist/components/WorkflowTemplates.js +32 -0
- package/dist/components/WorkflowTemplates.js.map +1 -0
- package/dist/components/Workspace.d.ts +48 -2
- package/dist/components/Workspace.d.ts.map +1 -1
- package/dist/components/Workspace.js +80 -5
- package/dist/components/Workspace.js.map +1 -1
- package/dist/data/workflow-templates.d.ts +42 -0
- package/dist/data/workflow-templates.d.ts.map +1 -0
- package/dist/data/workflow-templates.js +112 -0
- package/dist/data/workflow-templates.js.map +1 -0
- package/dist/hooks/useDebouncedRun.d.ts +49 -0
- package/dist/hooks/useDebouncedRun.d.ts.map +1 -0
- package/dist/hooks/useDebouncedRun.js +117 -0
- package/dist/hooks/useDebouncedRun.js.map +1 -0
- package/dist/hooks/useLokvis.js +1 -1
- package/dist/hooks/useMediaQuery.d.ts +27 -0
- package/dist/hooks/useMediaQuery.d.ts.map +1 -0
- package/dist/hooks/useMediaQuery.js +41 -0
- package/dist/hooks/useMediaQuery.js.map +1 -0
- package/dist/hooks/useShareLink.d.ts +58 -0
- package/dist/hooks/useShareLink.d.ts.map +1 -0
- package/dist/hooks/useShareLink.js +120 -0
- package/dist/hooks/useShareLink.js.map +1 -0
- package/dist/hooks/useTheme.d.ts +30 -0
- package/dist/hooks/useTheme.d.ts.map +1 -0
- package/dist/hooks/useTheme.js +105 -0
- package/dist/hooks/useTheme.js.map +1 -0
- package/dist/hooks/useWorkflowProgress.d.ts +28 -0
- package/dist/hooks/useWorkflowProgress.d.ts.map +1 -0
- package/dist/hooks/useWorkflowProgress.js +25 -0
- package/dist/hooks/useWorkflowProgress.js.map +1 -0
- package/dist/hooks/useWorkflows.d.ts +86 -0
- package/dist/hooks/useWorkflows.d.ts.map +1 -0
- package/dist/hooks/useWorkflows.js +247 -0
- package/dist/hooks/useWorkflows.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/dist/store/assets-slice.d.ts.map +1 -1
- package/dist/store/assets-slice.js +19 -0
- package/dist/store/assets-slice.js.map +1 -1
- package/dist/store/history-slice.d.ts +18 -0
- package/dist/store/history-slice.d.ts.map +1 -0
- package/dist/store/history-slice.js +42 -0
- package/dist/store/history-slice.js.map +1 -0
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +3 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/runtime-slice.d.ts +10 -2
- package/dist/store/runtime-slice.d.ts.map +1 -1
- package/dist/store/runtime-slice.js +77 -28
- package/dist/store/runtime-slice.js.map +1 -1
- package/dist/store/types.d.ts +70 -1
- package/dist/store/types.d.ts.map +1 -1
- package/dist/store/types.js +10 -0
- 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 +167 -20
- package/dist/store/workflow-slice.js.map +1 -1
- package/dist/store.d.ts +76 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +217 -0
- package/dist/store.js.map +1 -0
- package/package.json +9 -6
- package/src/components/AssetPanel.tsx +154 -12
- package/src/components/Canvas.tsx +85 -7
- package/src/components/CommandPalette.tsx +300 -0
- package/src/components/CompareSlider.tsx +186 -0
- package/src/components/DownloadPanel.tsx +252 -0
- package/src/components/ErrorBanner.tsx +95 -0
- package/src/components/ExifPanel.tsx +187 -0
- package/src/components/GlobalDropzone.tsx +220 -0
- package/src/components/HistoryPanel.tsx +231 -104
- package/src/components/Inspector.tsx +7 -2
- package/src/components/PipelineBar.tsx +13 -12
- package/src/components/ProgressBar.tsx +104 -0
- package/src/components/StatusBar.tsx +124 -5
- package/src/components/ThemeToggle.tsx +122 -0
- package/src/components/WorkflowEditor.tsx +382 -0
- package/src/components/WorkflowTemplates.tsx +101 -0
- package/src/components/Workspace.tsx +207 -9
- package/src/data/workflow-templates.ts +137 -0
- package/src/hooks/useDebouncedRun.ts +151 -0
- package/src/hooks/useLokvis.ts +1 -1
- package/src/hooks/useMediaQuery.ts +53 -0
- package/src/hooks/useShareLink.ts +158 -0
- package/src/hooks/useTheme.ts +116 -0
- package/src/hooks/useWorkflowProgress.ts +41 -0
- package/src/hooks/useWorkflows.ts +326 -0
- package/src/index.ts +32 -0
- package/src/store/assets-slice.ts +19 -0
- package/src/store/history-slice.ts +65 -0
- package/src/store/index.ts +3 -0
- package/src/store/runtime-slice.ts +89 -32
- package/src/store/types.ts +71 -1
- package/src/store/workflow-slice.ts +178 -20
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,cAAc;AACd,MAAM,WAAW,cAAc;IAC7B,iBAAiB;IACjB,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,sBAAsB;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY;IACZ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzB,cAAc;IACd,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,iBAAiB;IACjB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEnC,cAAc;IACd,YAAY,EAAE,UAAU,EAAE,CAAC;IAC3B,+BAA+B;IAC/B,aAAa,EAAE,aAAa,CAAC;IAE7B,mBAAmB;IACnB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,iBAAiB;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAE9B,gBAAgB;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,YAAY;AACZ,MAAM,WAAW,gBAAgB;IAC/B,kBAAkB;IAClB,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,aAAa;IACb,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,aAAa;IACb,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,WAAW;IACX,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,WAAW;IACX,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACrC,YAAY;IACZ,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,WAAW;IACX,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,cAAc;IACd,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,aAAa;IACb,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACpE,WAAW;IACX,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW;IACX,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACpC,aAAa;IACb,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvF,YAAY;IACZ,GAAG,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACxB,aAAa;IACb,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW;IACX,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACrC,YAAY;IACZ,aAAa,IAAI,IAAI,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,gBAAgB,CAAC;AAO/D,eAAO,MAAM,iBAAiB,6EA+L3B,CAAC"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace UI 状态管理
|
|
3
|
+
*
|
|
4
|
+
* 使用 Zustand 管理工作台状态。Runtime 永远不知道 React,
|
|
5
|
+
* 但 UI 层负责把 Runtime 事件桥接到 store。
|
|
6
|
+
*/
|
|
7
|
+
import { create } from 'zustand';
|
|
8
|
+
/** 生成节点 ID */
|
|
9
|
+
function genNodeId() {
|
|
10
|
+
return `node_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
11
|
+
}
|
|
12
|
+
export const useWorkspaceStore = create((set, get) => ({
|
|
13
|
+
runtime: null,
|
|
14
|
+
initializing: false,
|
|
15
|
+
initError: null,
|
|
16
|
+
assets: [],
|
|
17
|
+
selectedAssetId: null,
|
|
18
|
+
thumbnails: {},
|
|
19
|
+
capabilities: [],
|
|
20
|
+
capabilityMap: {},
|
|
21
|
+
nodes: [],
|
|
22
|
+
selectedNodeId: null,
|
|
23
|
+
running: false,
|
|
24
|
+
statusMessage: 'Idle',
|
|
25
|
+
error: null,
|
|
26
|
+
async init(runtime) {
|
|
27
|
+
set({ runtime, initializing: true, initError: null });
|
|
28
|
+
try {
|
|
29
|
+
await get().refreshAssets();
|
|
30
|
+
await get().refreshCapabilities();
|
|
31
|
+
set({ initializing: false, statusMessage: 'Ready' });
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
set({
|
|
35
|
+
initializing: false,
|
|
36
|
+
initError: err instanceof Error ? err.message : String(err),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
async refreshAssets() {
|
|
41
|
+
const { runtime } = get();
|
|
42
|
+
if (!runtime)
|
|
43
|
+
return;
|
|
44
|
+
const assets = await runtime.listAssets();
|
|
45
|
+
set({ assets });
|
|
46
|
+
},
|
|
47
|
+
async refreshCapabilities() {
|
|
48
|
+
const { runtime } = get();
|
|
49
|
+
if (!runtime)
|
|
50
|
+
return;
|
|
51
|
+
const capabilities = await runtime.capabilities();
|
|
52
|
+
const capabilityMap = {};
|
|
53
|
+
for (const cap of capabilities)
|
|
54
|
+
capabilityMap[cap.name] = cap;
|
|
55
|
+
set({ capabilities, capabilityMap });
|
|
56
|
+
},
|
|
57
|
+
async importFiles(files) {
|
|
58
|
+
const { runtime } = get();
|
|
59
|
+
if (!runtime)
|
|
60
|
+
return;
|
|
61
|
+
set({ statusMessage: `Importing ${files.length} file(s)...` });
|
|
62
|
+
for (const file of files) {
|
|
63
|
+
await runtime.importAsset({ kind: 'file', file });
|
|
64
|
+
}
|
|
65
|
+
await get().refreshAssets();
|
|
66
|
+
set({ statusMessage: `Imported ${files.length} file(s)` });
|
|
67
|
+
},
|
|
68
|
+
selectAsset(id) {
|
|
69
|
+
set({ selectedAssetId: id });
|
|
70
|
+
},
|
|
71
|
+
setThumbnail(id, url) {
|
|
72
|
+
set((state) => ({ thumbnails: { ...state.thumbnails, [id]: url } }));
|
|
73
|
+
},
|
|
74
|
+
async removeAsset(id) {
|
|
75
|
+
const { runtime } = get();
|
|
76
|
+
if (!runtime)
|
|
77
|
+
return;
|
|
78
|
+
await runtime.removeAsset(id);
|
|
79
|
+
await get().refreshAssets();
|
|
80
|
+
if (get().selectedAssetId === id)
|
|
81
|
+
set({ selectedAssetId: null });
|
|
82
|
+
},
|
|
83
|
+
addNode(capability) {
|
|
84
|
+
const node = {
|
|
85
|
+
id: genNodeId(),
|
|
86
|
+
capability,
|
|
87
|
+
params: {},
|
|
88
|
+
status: 'idle',
|
|
89
|
+
};
|
|
90
|
+
set((state) => ({
|
|
91
|
+
nodes: [...state.nodes, node],
|
|
92
|
+
selectedNodeId: node.id,
|
|
93
|
+
}));
|
|
94
|
+
},
|
|
95
|
+
updateNodeParams(id, params) {
|
|
96
|
+
set((state) => ({
|
|
97
|
+
nodes: state.nodes.map((n) => (n.id === id ? { ...n, params } : n)),
|
|
98
|
+
}));
|
|
99
|
+
},
|
|
100
|
+
removeNode(id) {
|
|
101
|
+
set((state) => ({
|
|
102
|
+
nodes: state.nodes.filter((n) => n.id !== id),
|
|
103
|
+
selectedNodeId: state.selectedNodeId === id ? null : state.selectedNodeId,
|
|
104
|
+
}));
|
|
105
|
+
},
|
|
106
|
+
selectNode(id) {
|
|
107
|
+
set({ selectedNodeId: id });
|
|
108
|
+
},
|
|
109
|
+
setNodeStatus(id, status, error, duration) {
|
|
110
|
+
set((state) => ({
|
|
111
|
+
nodes: state.nodes.map((n) => n.id === id ? { ...n, status, error, duration } : n),
|
|
112
|
+
}));
|
|
113
|
+
},
|
|
114
|
+
async run() {
|
|
115
|
+
const { runtime, nodes, selectedAssetId } = get();
|
|
116
|
+
if (!runtime)
|
|
117
|
+
throw new Error('Runtime not initialized');
|
|
118
|
+
if (nodes.length === 0)
|
|
119
|
+
throw new Error('Workflow is empty');
|
|
120
|
+
if (!selectedAssetId)
|
|
121
|
+
throw new Error('No asset selected');
|
|
122
|
+
set({ running: true, error: null, statusMessage: 'Running...' });
|
|
123
|
+
// 重置所有节点状态
|
|
124
|
+
set((state) => ({
|
|
125
|
+
nodes: state.nodes.map((n) => ({ ...n, status: 'pending', error: undefined })),
|
|
126
|
+
}));
|
|
127
|
+
try {
|
|
128
|
+
// 构建 Workflow
|
|
129
|
+
const workflow = buildLinearWorkflow(nodes);
|
|
130
|
+
const input = await runtime.getAsset(selectedAssetId);
|
|
131
|
+
set({ statusMessage: 'Running workflow...' });
|
|
132
|
+
const result = await runtime.run(workflow, [input]);
|
|
133
|
+
// 更新节点状态
|
|
134
|
+
if (result.status === 'completed') {
|
|
135
|
+
for (const node of nodes) {
|
|
136
|
+
get().setNodeStatus(node.id, 'success', undefined, undefined);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else if (result.status === 'failed') {
|
|
140
|
+
for (const node of nodes) {
|
|
141
|
+
if (get().nodes.find((n) => n.id === node.id)?.status === 'pending') {
|
|
142
|
+
get().setNodeStatus(node.id, 'failed', result.error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// 加载输出资产
|
|
147
|
+
const outputs = [];
|
|
148
|
+
for (const id of result.outputs) {
|
|
149
|
+
try {
|
|
150
|
+
outputs.push(await runtime.getAsset(id));
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// skip
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
set({
|
|
157
|
+
running: false,
|
|
158
|
+
statusMessage: `Workflow ${result.status} in ${result.duration}ms`,
|
|
159
|
+
});
|
|
160
|
+
if (result.status === 'failed') {
|
|
161
|
+
set({ error: result.error ?? 'Workflow failed' });
|
|
162
|
+
}
|
|
163
|
+
await get().refreshAssets();
|
|
164
|
+
return outputs;
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
168
|
+
set({
|
|
169
|
+
running: false,
|
|
170
|
+
error: message,
|
|
171
|
+
statusMessage: 'Failed',
|
|
172
|
+
});
|
|
173
|
+
throw err;
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
setStatus(message) {
|
|
177
|
+
set({ statusMessage: message });
|
|
178
|
+
},
|
|
179
|
+
setError(error) {
|
|
180
|
+
set({ error });
|
|
181
|
+
},
|
|
182
|
+
clearWorkflow() {
|
|
183
|
+
set({ nodes: [], selectedNodeId: null });
|
|
184
|
+
},
|
|
185
|
+
}));
|
|
186
|
+
/** 把工作台节点序列构建为线性 Workflow */
|
|
187
|
+
function buildLinearWorkflow(nodes) {
|
|
188
|
+
if (nodes.length === 0) {
|
|
189
|
+
throw new Error('Workflow is empty');
|
|
190
|
+
}
|
|
191
|
+
const workflowNodes = nodes.map((n) => ({
|
|
192
|
+
id: n.id,
|
|
193
|
+
type: 'transform',
|
|
194
|
+
capability: n.capability,
|
|
195
|
+
params: n.params,
|
|
196
|
+
}));
|
|
197
|
+
const edges = [];
|
|
198
|
+
for (let i = 0; i < workflowNodes.length - 1; i++) {
|
|
199
|
+
edges.push({ from: workflowNodes[i].id, to: workflowNodes[i + 1].id });
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
id: `wf_${Date.now().toString(36)}`,
|
|
203
|
+
name: 'Workspace Workflow',
|
|
204
|
+
version: '1',
|
|
205
|
+
description: 'Workspace linear workflow',
|
|
206
|
+
author: { id: 'local', name: 'Local User' },
|
|
207
|
+
category: 'image',
|
|
208
|
+
tags: [],
|
|
209
|
+
nodes: workflowNodes,
|
|
210
|
+
edges,
|
|
211
|
+
inputs: { type: 'image', multiple: true },
|
|
212
|
+
outputs: { type: 'image' },
|
|
213
|
+
createdAt: Date.now(),
|
|
214
|
+
updatedAt: Date.now(),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lokvis/ui-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Lokvis Workspace UI - React components for Image/Video/PDF Workspace",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"**/*.css"
|
|
9
|
+
],
|
|
7
10
|
"main": "./dist/index.js",
|
|
8
11
|
"module": "./dist/index.js",
|
|
9
12
|
"types": "./dist/index.d.ts",
|
|
@@ -28,11 +31,11 @@
|
|
|
28
31
|
},
|
|
29
32
|
"dependencies": {
|
|
30
33
|
"zustand": "^5.0.0",
|
|
31
|
-
"@lokvis/schema": "0.
|
|
32
|
-
"@lokvis/runtime": "0.
|
|
33
|
-
"@lokvis/sdk": "0.
|
|
34
|
-
"@lokvis/ui-core": "0.
|
|
35
|
-
"@lokvis/capability": "0.
|
|
34
|
+
"@lokvis/schema": "0.2.0",
|
|
35
|
+
"@lokvis/runtime": "0.2.0",
|
|
36
|
+
"@lokvis/sdk": "0.2.0",
|
|
37
|
+
"@lokvis/ui-core": "0.2.0",
|
|
38
|
+
"@lokvis/capability": "0.2.0"
|
|
36
39
|
},
|
|
37
40
|
"devDependencies": {
|
|
38
41
|
"@types/react": "^19.0.0",
|
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AssetPanel - 左侧资产面板
|
|
3
3
|
*
|
|
4
|
-
* 紧凑布局:section header + 上传按钮 + 资产列表。
|
|
5
|
-
*
|
|
4
|
+
* 紧凑布局:section header + 上传按钮 + 筛选 + 资产列表。
|
|
5
|
+
* 支持拖拽导入、点击选择、缩略图预览、按类型/关键词筛选、删除(W6.5)。
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import * as React from 'react';
|
|
9
|
+
import type { AssetType } from '@lokvis/schema';
|
|
10
|
+
import { Icon } from '@lokvis/ui-core';
|
|
9
11
|
import { useWorkspaceStore } from '../store/index.js';
|
|
10
12
|
|
|
11
13
|
export interface AssetPanelProps {
|
|
12
14
|
className?: string;
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
/** 类型筛选 chip 显示名 */
|
|
18
|
+
const TYPE_LABEL: Record<AssetType, string> = {
|
|
19
|
+
image: 'Image',
|
|
20
|
+
video: 'Video',
|
|
21
|
+
audio: 'Audio',
|
|
22
|
+
pdf: 'PDF',
|
|
23
|
+
text: 'Text',
|
|
24
|
+
data: 'Data',
|
|
25
|
+
unknown: 'Other',
|
|
26
|
+
};
|
|
27
|
+
|
|
15
28
|
export function AssetPanel({ className = '' }: AssetPanelProps) {
|
|
16
29
|
const assets = useWorkspaceStore((s) => s.assets);
|
|
17
30
|
const selectedAssetId = useWorkspaceStore((s) => s.selectedAssetId);
|
|
@@ -23,6 +36,11 @@ export function AssetPanel({ className = '' }: AssetPanelProps) {
|
|
|
23
36
|
const runtime = useWorkspaceStore((s) => s.runtime);
|
|
24
37
|
|
|
25
38
|
const [dragging, setDragging] = React.useState(false);
|
|
39
|
+
// W6.5 筛选:类型 + 关键词(纯 UI 状态,不入 store)
|
|
40
|
+
const [filterType, setFilterType] = React.useState<'all' | AssetType>('all');
|
|
41
|
+
const [searchQuery, setSearchQuery] = React.useState('');
|
|
42
|
+
// B2 修复:跟踪 in-flight exportAsset,避免同一 asset 跨 effect 重跑并发发起
|
|
43
|
+
const inflightRef = React.useRef<Set<string>>(new Set());
|
|
26
44
|
|
|
27
45
|
async function handleFiles(files: FileList | File[] | null) {
|
|
28
46
|
if (!files) return;
|
|
@@ -31,24 +49,69 @@ export function AssetPanel({ className = '' }: AssetPanelProps) {
|
|
|
31
49
|
}
|
|
32
50
|
|
|
33
51
|
// 自动生成缩略图
|
|
52
|
+
// B2 修复:
|
|
53
|
+
// - cancelled flag:effect 重跑/卸载时,await 完成的 exportAsset 不再创建
|
|
54
|
+
// ObjectURL 也不 setThumbnail,避免覆盖更新值与孤儿 URL
|
|
55
|
+
// - inflightRef:同一 asset 在上一轮 effect 还在 await 时,新一轮 effect
|
|
56
|
+
// 不再重复发起(批量导入 assets 频繁变化场景)
|
|
34
57
|
React.useEffect(() => {
|
|
35
58
|
if (!runtime) return;
|
|
59
|
+
let cancelled = false;
|
|
60
|
+
const inflight = inflightRef.current;
|
|
36
61
|
for (const asset of assets) {
|
|
37
62
|
if (thumbnails[asset.id]) continue;
|
|
38
63
|
if (asset.type !== 'image') continue;
|
|
64
|
+
if (inflight.has(asset.id)) continue;
|
|
65
|
+
inflight.add(asset.id);
|
|
39
66
|
(async () => {
|
|
40
67
|
try {
|
|
41
68
|
const blob = await runtime.exportAsset(asset.id);
|
|
69
|
+
// effect 已过期或资产已删除:不创建 ObjectURL(零泄漏)
|
|
70
|
+
if (cancelled) return;
|
|
42
71
|
const url = URL.createObjectURL(blob);
|
|
72
|
+
if (cancelled) {
|
|
73
|
+
// 双重检查:await 与 createObjectURL 之间可能被取消
|
|
74
|
+
URL.revokeObjectURL(url);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
43
77
|
setThumbnail(asset.id, url);
|
|
44
78
|
} catch {
|
|
45
|
-
// ignore
|
|
79
|
+
// ignore export 失败
|
|
80
|
+
} finally {
|
|
81
|
+
inflight.delete(asset.id);
|
|
46
82
|
}
|
|
47
83
|
})();
|
|
48
84
|
}
|
|
85
|
+
return () => {
|
|
86
|
+
cancelled = true;
|
|
87
|
+
};
|
|
49
88
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
50
89
|
}, [assets, runtime]);
|
|
51
90
|
|
|
91
|
+
// 资产中实际存在的类型(仅展示有意义的 chip,避免空类型噪音)
|
|
92
|
+
const availableTypes = React.useMemo(() => {
|
|
93
|
+
const set = new Set<AssetType>();
|
|
94
|
+
for (const a of assets) set.add(a.type);
|
|
95
|
+
return Array.from(set);
|
|
96
|
+
}, [assets]);
|
|
97
|
+
|
|
98
|
+
// 筛选后的资产列表
|
|
99
|
+
const filteredAssets = React.useMemo(() => {
|
|
100
|
+
let result = assets;
|
|
101
|
+
if (filterType !== 'all') {
|
|
102
|
+
result = result.filter((a) => a.type === filterType);
|
|
103
|
+
}
|
|
104
|
+
const q = searchQuery.trim().toLowerCase();
|
|
105
|
+
if (q) {
|
|
106
|
+
result = result.filter(
|
|
107
|
+
(a) =>
|
|
108
|
+
a.metadata.format.toLowerCase().includes(q) ||
|
|
109
|
+
a.metadata.mimeType.toLowerCase().includes(q)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}, [assets, filterType, searchQuery]);
|
|
114
|
+
|
|
52
115
|
return (
|
|
53
116
|
<aside
|
|
54
117
|
className={`flex w-56 shrink-0 flex-col border-r border-zinc-200 dark:border-zinc-800 ${className}`}
|
|
@@ -68,7 +131,11 @@ export function AssetPanel({ className = '' }: AssetPanelProps) {
|
|
|
68
131
|
<span className="text-[11px] font-semibold uppercase tracking-wider text-zinc-400">
|
|
69
132
|
Assets
|
|
70
133
|
</span>
|
|
71
|
-
<span className="text-[11px] tabular-nums text-zinc-400">
|
|
134
|
+
<span className="text-[11px] tabular-nums text-zinc-400">
|
|
135
|
+
{filterType === 'all' && !searchQuery
|
|
136
|
+
? assets.length
|
|
137
|
+
: `${filteredAssets.length}/${assets.length}`}
|
|
138
|
+
</span>
|
|
72
139
|
</div>
|
|
73
140
|
|
|
74
141
|
{/* Upload zone */}
|
|
@@ -85,30 +152,105 @@ export function AssetPanel({ className = '' }: AssetPanelProps) {
|
|
|
85
152
|
className="hidden"
|
|
86
153
|
onChange={(e) => handleFiles(e.target.files)}
|
|
87
154
|
/>
|
|
88
|
-
<
|
|
89
|
-
<path
|
|
90
|
-
</
|
|
155
|
+
<Icon size={16} className="mx-auto">
|
|
156
|
+
<path d="M12 4.5v15m7.5-7.5h-15" />
|
|
157
|
+
</Icon>
|
|
91
158
|
<span className="mt-1 block text-[10px] font-medium">
|
|
92
159
|
{dragging ? 'Drop here' : 'Add files'}
|
|
93
160
|
</span>
|
|
94
161
|
</label>
|
|
95
162
|
|
|
163
|
+
{/* W6.5 筛选栏:仅当有资产时显示 */}
|
|
164
|
+
{assets.length > 0 && (
|
|
165
|
+
<div className="px-2 pb-2 space-y-1.5">
|
|
166
|
+
{/* 类型 chips:m10 radiogroup 语义,屏幕阅读器识别为单选组 */}
|
|
167
|
+
<div
|
|
168
|
+
role="radiogroup"
|
|
169
|
+
aria-label="Filter by type"
|
|
170
|
+
className="flex flex-wrap gap-1"
|
|
171
|
+
>
|
|
172
|
+
<button
|
|
173
|
+
type="button"
|
|
174
|
+
role="radio"
|
|
175
|
+
aria-checked={filterType === 'all'}
|
|
176
|
+
onClick={() => setFilterType('all')}
|
|
177
|
+
className={`rounded px-1.5 py-0.5 text-[10px] font-medium transition-colors ${
|
|
178
|
+
filterType === 'all'
|
|
179
|
+
? 'bg-indigo-100 text-indigo-700 dark:bg-indigo-950/50 dark:text-indigo-300'
|
|
180
|
+
: 'bg-zinc-100 text-zinc-500 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-400 dark:hover:bg-zinc-700'
|
|
181
|
+
}`}
|
|
182
|
+
>
|
|
183
|
+
All
|
|
184
|
+
</button>
|
|
185
|
+
{availableTypes.map((t) => (
|
|
186
|
+
<button
|
|
187
|
+
key={t}
|
|
188
|
+
type="button"
|
|
189
|
+
role="radio"
|
|
190
|
+
aria-checked={filterType === t}
|
|
191
|
+
onClick={() => setFilterType(t)}
|
|
192
|
+
className={`rounded px-1.5 py-0.5 text-[10px] font-medium transition-colors ${
|
|
193
|
+
filterType === t
|
|
194
|
+
? 'bg-indigo-100 text-indigo-700 dark:bg-indigo-950/50 dark:text-indigo-300'
|
|
195
|
+
: 'bg-zinc-100 text-zinc-500 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-400 dark:hover:bg-zinc-700'
|
|
196
|
+
}`}
|
|
197
|
+
>
|
|
198
|
+
{TYPE_LABEL[t]}
|
|
199
|
+
</button>
|
|
200
|
+
))}
|
|
201
|
+
</div>
|
|
202
|
+
{/* 搜索框:m4 文案改为与实际过滤行为一致(按 format/mimeType) */}
|
|
203
|
+
<div className="relative">
|
|
204
|
+
<Icon
|
|
205
|
+
size={11}
|
|
206
|
+
className="absolute left-1.5 top-1/2 -translate-y-1/2 text-zinc-400 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"
|
|
213
|
+
value={searchQuery}
|
|
214
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
215
|
+
placeholder="Filter by type..."
|
|
216
|
+
aria-label="Filter assets by format"
|
|
217
|
+
className="w-full rounded bg-zinc-100 py-1 pl-6 pr-1.5 text-[10px] text-zinc-700 placeholder:text-zinc-400 focus:outline-none focus:ring-1 focus:ring-indigo-300 dark:bg-zinc-800 dark:text-zinc-200 dark:placeholder:text-zinc-500"
|
|
218
|
+
/>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
|
|
96
223
|
{/* Asset list */}
|
|
97
224
|
<div className="flex-1 overflow-y-auto px-2 pb-2">
|
|
98
225
|
{assets.length === 0 ? (
|
|
99
226
|
<p className="px-1 py-4 text-center text-[10px] text-zinc-400">
|
|
100
227
|
No assets imported
|
|
101
228
|
</p>
|
|
229
|
+
) : filteredAssets.length === 0 ? (
|
|
230
|
+
<p className="px-1 py-4 text-center text-[10px] text-zinc-400">
|
|
231
|
+
No assets match filter
|
|
232
|
+
</p>
|
|
102
233
|
) : (
|
|
103
234
|
<ul className="space-y-1">
|
|
104
|
-
{
|
|
235
|
+
{filteredAssets.map((asset) => {
|
|
105
236
|
const selected = asset.id === selectedAssetId;
|
|
106
237
|
const thumb = thumbnails[asset.id];
|
|
107
238
|
return (
|
|
108
239
|
<li
|
|
109
240
|
key={asset.id}
|
|
241
|
+
// M3: 键盘可达 —— tabIndex + role + onKeyDown(Enter/Space 选中)
|
|
242
|
+
tabIndex={0}
|
|
243
|
+
role="button"
|
|
244
|
+
aria-pressed={selected}
|
|
245
|
+
aria-label={`Select asset ${asset.metadata.format}`}
|
|
110
246
|
onClick={() => selectAsset(asset.id)}
|
|
111
|
-
|
|
247
|
+
onKeyDown={(e) => {
|
|
248
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
249
|
+
e.preventDefault();
|
|
250
|
+
selectAsset(asset.id);
|
|
251
|
+
}
|
|
252
|
+
}}
|
|
253
|
+
className={`group flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-xs transition-all focus:outline-none focus:ring-2 focus:ring-indigo-300 ${
|
|
112
254
|
selected
|
|
113
255
|
? 'bg-indigo-50 ring-1 ring-indigo-200 dark:bg-indigo-950/30 dark:ring-indigo-800'
|
|
114
256
|
: 'hover:bg-zinc-50 dark:hover:bg-zinc-800'
|
|
@@ -135,10 +277,10 @@ export function AssetPanel({ className = '' }: AssetPanelProps) {
|
|
|
135
277
|
e.stopPropagation();
|
|
136
278
|
void removeAsset(asset.id);
|
|
137
279
|
}}
|
|
138
|
-
className="shrink-0 rounded p-0.5 text-zinc-300 opacity-0 transition-all hover:bg-red-50 hover:text-red-500 group-hover:opacity-100 dark:text-zinc-600 dark:hover:bg-red-950/30"
|
|
139
|
-
aria-label=
|
|
280
|
+
className="shrink-0 rounded p-0.5 text-zinc-300 opacity-0 transition-all hover:bg-red-50 hover:text-red-500 focus:opacity-100 group-hover:opacity-100 dark:text-zinc-600 dark:hover:bg-red-950/30"
|
|
281
|
+
aria-label={`Remove asset ${asset.metadata.format}`}
|
|
140
282
|
>
|
|
141
|
-
<
|
|
283
|
+
<Icon size={12}><path d="M6 18L18 6M6 6l12 12" /></Icon>
|
|
142
284
|
</button>
|
|
143
285
|
</li>
|
|
144
286
|
);
|