@lokvis/ui-react 0.2.1 → 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.
Files changed (165) hide show
  1. package/dist/components/AssetPanel.d.ts.map +1 -1
  2. package/dist/components/AssetPanel.js +18 -62
  3. package/dist/components/AssetPanel.js.map +1 -1
  4. package/dist/components/Canvas.d.ts +4 -4
  5. package/dist/components/Canvas.d.ts.map +1 -1
  6. package/dist/components/Canvas.js +9 -9
  7. package/dist/components/Canvas.js.map +1 -1
  8. package/dist/components/CommandPalette.d.ts +7 -7
  9. package/dist/components/CommandPalette.d.ts.map +1 -1
  10. package/dist/components/CommandPalette.js +10 -10
  11. package/dist/components/CommandPalette.js.map +1 -1
  12. package/dist/components/CompareSlider.d.ts +9 -8
  13. package/dist/components/CompareSlider.d.ts.map +1 -1
  14. package/dist/components/CompareSlider.js +10 -9
  15. package/dist/components/CompareSlider.js.map +1 -1
  16. package/dist/components/DownloadPanel.d.ts +3 -3
  17. package/dist/components/DownloadPanel.d.ts.map +1 -1
  18. package/dist/components/DownloadPanel.js +10 -17
  19. package/dist/components/DownloadPanel.js.map +1 -1
  20. package/dist/components/ErrorBanner.d.ts +2 -2
  21. package/dist/components/ErrorBanner.d.ts.map +1 -1
  22. package/dist/components/ErrorBanner.js +3 -3
  23. package/dist/components/ErrorBanner.js.map +1 -1
  24. package/dist/components/ErrorBoundary.d.ts +40 -0
  25. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  26. package/dist/components/ErrorBoundary.js +52 -0
  27. package/dist/components/ErrorBoundary.js.map +1 -0
  28. package/dist/components/ExifPanel.d.ts +1 -1
  29. package/dist/components/ExifPanel.d.ts.map +1 -1
  30. package/dist/components/ExifPanel.js +2 -2
  31. package/dist/components/ExifPanel.js.map +1 -1
  32. package/dist/components/GlobalDropzone.d.ts +5 -5
  33. package/dist/components/GlobalDropzone.d.ts.map +1 -1
  34. package/dist/components/GlobalDropzone.js +15 -8
  35. package/dist/components/GlobalDropzone.js.map +1 -1
  36. package/dist/components/HistoryPanel.d.ts +2 -2
  37. package/dist/components/HistoryPanel.d.ts.map +1 -1
  38. package/dist/components/HistoryPanel.js +23 -23
  39. package/dist/components/HistoryPanel.js.map +1 -1
  40. package/dist/components/Inspector.d.ts.map +1 -1
  41. package/dist/components/Inspector.js +13 -10
  42. package/dist/components/Inspector.js.map +1 -1
  43. package/dist/components/ParamForm.d.ts.map +1 -1
  44. package/dist/components/ParamForm.js +10 -7
  45. package/dist/components/ParamForm.js.map +1 -1
  46. package/dist/components/PipelineBar.d.ts.map +1 -1
  47. package/dist/components/PipelineBar.js +19 -28
  48. package/dist/components/PipelineBar.js.map +1 -1
  49. package/dist/components/ProgressBar.d.ts +4 -4
  50. package/dist/components/ProgressBar.d.ts.map +1 -1
  51. package/dist/components/ProgressBar.js +8 -8
  52. package/dist/components/ProgressBar.js.map +1 -1
  53. package/dist/components/StatusBar.d.ts +3 -3
  54. package/dist/components/StatusBar.d.ts.map +1 -1
  55. package/dist/components/StatusBar.js +12 -21
  56. package/dist/components/StatusBar.js.map +1 -1
  57. package/dist/components/StatusDot.d.ts +12 -0
  58. package/dist/components/StatusDot.d.ts.map +1 -0
  59. package/dist/components/StatusDot.js +15 -0
  60. package/dist/components/StatusDot.js.map +1 -0
  61. package/dist/components/ThemeToggle.d.ts +4 -4
  62. package/dist/components/ThemeToggle.d.ts.map +1 -1
  63. package/dist/components/ThemeToggle.js +7 -7
  64. package/dist/components/ThemeToggle.js.map +1 -1
  65. package/dist/components/ToolRunner.d.ts +53 -0
  66. package/dist/components/ToolRunner.d.ts.map +1 -0
  67. package/dist/components/ToolRunner.js +142 -0
  68. package/dist/components/ToolRunner.js.map +1 -0
  69. package/dist/components/Toolbar.d.ts.map +1 -1
  70. package/dist/components/Toolbar.js +3 -3
  71. package/dist/components/Toolbar.js.map +1 -1
  72. package/dist/components/WorkflowEditor.d.ts +6 -6
  73. package/dist/components/WorkflowEditor.d.ts.map +1 -1
  74. package/dist/components/WorkflowEditor.js +40 -49
  75. package/dist/components/WorkflowEditor.js.map +1 -1
  76. package/dist/components/WorkflowTemplates.d.ts +2 -2
  77. package/dist/components/WorkflowTemplates.d.ts.map +1 -1
  78. package/dist/components/WorkflowTemplates.js +17 -7
  79. package/dist/components/WorkflowTemplates.js.map +1 -1
  80. package/dist/components/Workspace.d.ts +45 -23
  81. package/dist/components/Workspace.d.ts.map +1 -1
  82. package/dist/components/Workspace.js +87 -40
  83. package/dist/components/Workspace.js.map +1 -1
  84. package/dist/hooks/useCustomPresets.d.ts +98 -0
  85. package/dist/hooks/useCustomPresets.d.ts.map +1 -0
  86. package/dist/hooks/useCustomPresets.js +210 -0
  87. package/dist/hooks/useCustomPresets.js.map +1 -0
  88. package/dist/hooks/useLokvis.d.ts +19 -1
  89. package/dist/hooks/useLokvis.d.ts.map +1 -1
  90. package/dist/hooks/useLokvis.js +30 -9
  91. package/dist/hooks/useLokvis.js.map +1 -1
  92. package/dist/index.d.ts +4 -0
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +6 -0
  95. package/dist/index.js.map +1 -1
  96. package/dist/store/assets-slice.d.ts +8 -1
  97. package/dist/store/assets-slice.d.ts.map +1 -1
  98. package/dist/store/assets-slice.js +101 -54
  99. package/dist/store/assets-slice.js.map +1 -1
  100. package/dist/store/capabilities-slice.d.ts +1 -1
  101. package/dist/store/capabilities-slice.d.ts.map +1 -1
  102. package/dist/store/capabilities-slice.js +8 -1
  103. package/dist/store/capabilities-slice.js.map +1 -1
  104. package/dist/store/index.d.ts +1 -1
  105. package/dist/store/index.d.ts.map +1 -1
  106. package/dist/store/index.js +1 -1
  107. package/dist/store/index.js.map +1 -1
  108. package/dist/store/runtime-slice.d.ts.map +1 -1
  109. package/dist/store/runtime-slice.js +22 -16
  110. package/dist/store/runtime-slice.js.map +1 -1
  111. package/dist/store/subscribe-utils.d.ts +45 -0
  112. package/dist/store/subscribe-utils.d.ts.map +1 -0
  113. package/dist/store/subscribe-utils.js +69 -0
  114. package/dist/store/subscribe-utils.js.map +1 -0
  115. package/dist/store/types.d.ts +19 -11
  116. package/dist/store/types.d.ts.map +1 -1
  117. package/dist/store/types.js +0 -10
  118. package/dist/store/types.js.map +1 -1
  119. package/dist/store/workflow-slice.d.ts +1 -1
  120. package/dist/store/workflow-slice.d.ts.map +1 -1
  121. package/dist/store/workflow-slice.js +19 -51
  122. package/dist/store/workflow-slice.js.map +1 -1
  123. package/dist/utils.d.ts +24 -0
  124. package/dist/utils.d.ts.map +1 -0
  125. package/dist/utils.js +31 -0
  126. package/dist/utils.js.map +1 -0
  127. package/package.json +7 -6
  128. package/src/components/AssetPanel.tsx +224 -264
  129. package/src/components/Canvas.tsx +178 -174
  130. package/src/components/CommandPalette.tsx +242 -242
  131. package/src/components/CompareSlider.tsx +168 -165
  132. package/src/components/DownloadPanel.tsx +201 -207
  133. package/src/components/ErrorBanner.tsx +64 -64
  134. package/src/components/ErrorBoundary.tsx +101 -0
  135. package/src/components/ExifPanel.tsx +154 -154
  136. package/src/components/GlobalDropzone.tsx +175 -170
  137. package/src/components/HistoryPanel.tsx +219 -219
  138. package/src/components/Inspector.tsx +145 -137
  139. package/src/components/ParamForm.tsx +140 -137
  140. package/src/components/PipelineBar.tsx +97 -104
  141. package/src/components/ProgressBar.tsx +78 -78
  142. package/src/components/StatusBar.tsx +145 -152
  143. package/src/components/StatusDot.tsx +27 -0
  144. package/src/components/ThemeToggle.tsx +98 -98
  145. package/src/components/ToolRunner.tsx +352 -0
  146. package/src/components/Toolbar.tsx +65 -65
  147. package/src/components/WorkflowEditor.tsx +333 -326
  148. package/src/components/WorkflowTemplates.tsx +101 -78
  149. package/src/components/Workspace.tsx +310 -223
  150. package/src/css-modules.d.ts +3 -0
  151. package/src/hooks/useCustomPresets.ts +284 -0
  152. package/src/hooks/useLokvis.ts +43 -10
  153. package/src/index.ts +7 -0
  154. package/src/store/assets-slice.ts +106 -52
  155. package/src/store/capabilities-slice.ts +10 -2
  156. package/src/store/index.ts +1 -1
  157. package/src/store/runtime-slice.ts +20 -15
  158. package/src/store/subscribe-utils.ts +82 -0
  159. package/src/store/types.ts +19 -12
  160. package/src/store/workflow-slice.ts +19 -53
  161. package/src/utils.ts +41 -0
  162. package/dist/store.d.ts +0 -76
  163. package/dist/store.d.ts.map +0 -1
  164. package/dist/store.js +0 -217
  165. package/dist/store.js.map +0 -1
@@ -7,8 +7,8 @@
7
7
  * - cursor 当前位置高亮,cursor 之后的条目标灰(redo 分支)
8
8
  *
9
9
  * W9.1: 支持 `variant` 切换布局方向
10
- * - `vertical`(默认):侧栏垂直布局,适合嵌入左/右栏
11
- * - `horizontal`:底部横条布局,作为底部 history bar 使用
10
+ * - `vertical`(默认):侧栏垂直布局,适合嵌入左/右栏
11
+ * - `horizontal`:底部横条布局,作为底部 history bar 使用
12
12
  *
13
13
  * 数据来自 history-slice(store 单一来源),由 runtime-slice 的
14
14
  * history:changed 订阅自动更新,组件无需自行订阅事件。
@@ -21,234 +21,234 @@ import { Icon } from '@lokvis/ui-core';
21
21
  import { useWorkspaceStore } from '../store/index.js';
22
22
 
23
23
  export interface HistoryPanelProps {
24
- className?: string;
25
- /** 布局方向(W9.1):vertical 侧栏,horizontal 底部横条 */
26
- variant?: 'vertical' | 'horizontal';
24
+ className?: string;
25
+ /** 布局方向(W9.1):vertical 侧栏,horizontal 底部横条 */
26
+ variant?: 'vertical' | 'horizontal';
27
27
  }
28
28
 
29
29
  export function HistoryPanel({ className = '', variant = 'vertical' }: HistoryPanelProps) {
30
- const entries = useWorkspaceStore((s) => s.historyEntries);
31
- const cursor = useWorkspaceStore((s) => s.historyCursor);
32
- const workflowId = useWorkspaceStore((s) => s.historyWorkflowId);
33
- const undo = useWorkspaceStore((s) => s.undo);
34
- const redo = useWorkspaceStore((s) => s.redo);
35
- const jumpTo = useWorkspaceStore((s) => s.jumpToHistory);
30
+ const entries = useWorkspaceStore((s) => s.historyEntries);
31
+ const cursor = useWorkspaceStore((s) => s.historyCursor);
32
+ const workflowId = useWorkspaceStore((s) => s.historyWorkflowId);
33
+ const undo = useWorkspaceStore((s) => s.undo);
34
+ const redo = useWorkspaceStore((s) => s.redo);
35
+ const jumpTo = useWorkspaceStore((s) => s.jumpToHistory);
36
36
 
37
- const canUndo = cursor >= 0;
38
- const canRedo = cursor < entries.length - 1;
39
- const horizontal = variant === 'horizontal';
37
+ const canUndo = cursor >= 0;
38
+ const canRedo = cursor < entries.length - 1;
39
+ const horizontal = variant === 'horizontal';
40
40
 
41
- const undoBtn = (
42
- <button
43
- type="button"
44
- onClick={() => void undo()}
45
- disabled={!canUndo}
46
- aria-label="Undo"
47
- className="rounded p-1 text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-700 disabled:cursor-not-allowed disabled:opacity-30 dark:hover:bg-zinc-800 dark:hover:text-zinc-300"
48
- >
49
- <Icon size={13}>
50
- <path d="M9 14L4 9l5-5" />
51
- <path d="M4 9h11a4 4 0 0 1 0 8h-3" />
52
- </Icon>
53
- </button>
54
- );
55
- const redoBtn = (
56
- <button
57
- type="button"
58
- onClick={() => void redo()}
59
- disabled={!canRedo}
60
- aria-label="Redo"
61
- className="rounded p-1 text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-700 disabled:cursor-not-allowed disabled:opacity-30 dark:hover:bg-zinc-800 dark:hover:text-zinc-300"
62
- >
63
- <Icon size={13}>
64
- <path d="M15 14l5-5-5-5" />
65
- <path d="M20 9H9a4 4 0 0 0 0 8h3" />
66
- </Icon>
67
- </button>
68
- );
41
+ const undoBtn = (
42
+ <button
43
+ type="button"
44
+ onClick={() => void undo()}
45
+ disabled={!canUndo}
46
+ aria-label="Undo"
47
+ className="rounded p-1 text-[var(--lokvis-fg-muted)] transition-colors hover:bg-[var(--lokvis-surface-muted)] hover:text-[var(--lokvis-fg-muted)] disabled:cursor-not-allowed disabled:opacity-30"
48
+ >
49
+ <Icon size={13}>
50
+ <path d="M9 14L4 9l5-5" />
51
+ <path d="M4 9h11a4 4 0 0 1 0 8h-3" />
52
+ </Icon>
53
+ </button>
54
+ );
55
+ const redoBtn = (
56
+ <button
57
+ type="button"
58
+ onClick={() => void redo()}
59
+ disabled={!canRedo}
60
+ aria-label="Redo"
61
+ className="rounded p-1 text-[var(--lokvis-fg-muted)] transition-colors hover:bg-[var(--lokvis-surface-muted)] hover:text-[var(--lokvis-fg-muted)] disabled:cursor-not-allowed disabled:opacity-30"
62
+ >
63
+ <Icon size={13}>
64
+ <path d="M15 14l5-5-5-5" />
65
+ <path d="M20 9H9a4 4 0 0 0 0 8h3" />
66
+ </Icon>
67
+ </button>
68
+ );
69
69
 
70
- const headerEl = (
71
- <div
72
- className={`flex shrink-0 items-center justify-between px-3 ${
73
- horizontal ? 'h-full border-r border-zinc-200 dark:border-zinc-800' : 'h-10 border-b border-zinc-200 dark:border-zinc-800'
74
- }`}
75
- >
76
- <span className="text-[11px] font-semibold uppercase tracking-wider text-zinc-400">
77
- History
78
- </span>
79
- <div className="flex items-center gap-0.5">
80
- {undoBtn}
81
- {redoBtn}
82
- </div>
83
- </div>
84
- );
70
+ const headerEl = (
71
+ <div
72
+ className={`flex shrink-0 items-center justify-between px-3 ${
73
+ horizontal ? 'h-full border-r border-[var(--lokvis-border)]' : 'h-10 border-b border-[var(--lokvis-border)]'
74
+ }`}
75
+ >
76
+ <span className="text-[11px] font-semibold uppercase tracking-wider text-[var(--lokvis-fg-subtle)]">
77
+ History
78
+ </span>
79
+ <div className="flex items-center gap-0.5">
80
+ {undoBtn}
81
+ {redoBtn}
82
+ </div>
83
+ </div>
84
+ );
85
85
 
86
- // 空状态
87
- if (!workflowId || entries.length === 0) {
88
- return (
89
- <aside
90
- className={`flex shrink-0 ${
91
- horizontal
92
- ? 'h-12 flex-row items-center border-t border-zinc-200 dark:border-zinc-800'
93
- : 'w-48 flex-col border-r border-zinc-200 dark:border-zinc-800'
94
- } ${className}`}
95
- aria-label="History panel"
96
- >
97
- {headerEl}
98
- <p className="px-3 text-[10px] text-zinc-400">No history yet</p>
99
- </aside>
100
- );
101
- }
86
+ // 空状态
87
+ if (!workflowId || entries.length === 0) {
88
+ return (
89
+ <aside
90
+ className={`flex shrink-0 ${
91
+ horizontal
92
+ ? 'h-12 flex-row items-center border-t border-[var(--lokvis-border)]'
93
+ : 'w-48 flex-col border-r border-[var(--lokvis-border)]'
94
+ } ${className}`}
95
+ aria-label="History panel"
96
+ >
97
+ {headerEl}
98
+ <p className="px-3 text-[10px] text-[var(--lokvis-fg-subtle)]">No history yet</p>
99
+ </aside>
100
+ );
101
+ }
102
102
 
103
- if (horizontal) {
104
- // 横向布局:header | entries(横向滚动)
105
- return (
106
- <aside
107
- className={`flex h-12 shrink-0 items-stretch border-t border-zinc-200 dark:border-zinc-800 ${className}`}
108
- aria-label="History panel"
109
- >
110
- {headerEl}
111
- <ol className="flex flex-1 items-center gap-1 overflow-x-auto px-2">
112
- {/* 初始状态项 */}
113
- <li className="shrink-0">
114
- <button
115
- type="button"
116
- onClick={() => void jumpTo(-1)}
117
- className={`flex items-center gap-1.5 rounded px-2 py-1 text-[10px] transition-colors ${
118
- cursor === -1
119
- ? 'bg-indigo-50 text-indigo-700 ring-1 ring-indigo-200 dark:bg-indigo-950/30 dark:text-indigo-300 dark:ring-indigo-800'
120
- : 'text-zinc-500 hover:bg-zinc-50 dark:hover:bg-zinc-800'
121
- }`}
122
- >
123
- <span className="h-1.5 w-1.5 shrink-0 rounded-full bg-zinc-300 dark:bg-zinc-600" aria-hidden="true" />
124
- <span className="truncate">Initial</span>
125
- </button>
126
- </li>
127
- {entries.map((entry, i) => {
128
- const isCurrent = i === cursor;
129
- const isRedoBranch = i > cursor;
130
- return (
131
- <li key={entry.id} className="shrink-0">
132
- <button
133
- type="button"
134
- onClick={() => void jumpTo(i)}
135
- aria-current={isCurrent ? 'step' : undefined}
136
- className={`flex items-center gap-1.5 rounded px-2 py-1 text-[10px] transition-colors ${
137
- isCurrent
138
- ? 'bg-indigo-50 text-indigo-700 ring-1 ring-indigo-200 dark:bg-indigo-950/30 dark:text-indigo-300 dark:ring-indigo-800'
139
- : isRedoBranch
140
- ? 'text-zinc-300 hover:bg-zinc-50 dark:text-zinc-600 dark:hover:bg-zinc-800/50'
141
- : 'text-zinc-600 hover:bg-zinc-50 dark:text-zinc-300 dark:hover:bg-zinc-800'
142
- }`}
143
- >
144
- <span
145
- className={`h-1.5 w-1.5 shrink-0 rounded-full ${
146
- isCurrent
147
- ? 'bg-indigo-500'
148
- : isRedoBranch
149
- ? 'bg-zinc-200 dark:bg-zinc-700'
150
- : 'bg-zinc-400 dark:bg-zinc-500'
151
- }`}
152
- aria-hidden="true"
153
- />
154
- <span className="truncate font-medium">{describeHistoryEntry(entry)}</span>
155
- </button>
156
- </li>
157
- );
158
- })}
159
- </ol>
160
- </aside>
161
- );
162
- }
103
+ if (horizontal) {
104
+ // 横向布局:header | entries(横向滚动)
105
+ return (
106
+ <aside
107
+ className={`flex h-12 shrink-0 items-stretch border-t border-[var(--lokvis-border)] ${className}`}
108
+ aria-label="History panel"
109
+ >
110
+ {headerEl}
111
+ <ol className="flex flex-1 items-center gap-1 overflow-x-auto px-2">
112
+ {/* 初始状态项 */}
113
+ <li className="shrink-0">
114
+ <button
115
+ type="button"
116
+ onClick={() => void jumpTo(-1)}
117
+ className={`flex items-center gap-1.5 rounded px-2 py-1 text-[10px] transition-colors ${
118
+ cursor === -1
119
+ ? 'bg-[var(--lokvis-primary)]/10 text-[var(--lokvis-primary)] ring-1 ring-[var(--lokvis-primary)]/40'
120
+ : 'text-[var(--lokvis-fg-muted)] hover:bg-[var(--lokvis-surface)]'
121
+ }`}
122
+ >
123
+ <span className="h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--lokvis-fg-subtle)]" aria-hidden="true" />
124
+ <span className="truncate">Initial</span>
125
+ </button>
126
+ </li>
127
+ {entries.map((entry, i) => {
128
+ const isCurrent = i === cursor;
129
+ const isRedoBranch = i > cursor;
130
+ return (
131
+ <li key={entry.id} className="shrink-0">
132
+ <button
133
+ type="button"
134
+ onClick={() => void jumpTo(i)}
135
+ aria-current={isCurrent ? 'step' : undefined}
136
+ className={`flex items-center gap-1.5 rounded px-2 py-1 text-[10px] transition-colors ${
137
+ isCurrent
138
+ ? 'bg-[var(--lokvis-primary)]/10 text-[var(--lokvis-primary)] ring-1 ring-[var(--lokvis-primary)]/40'
139
+ : isRedoBranch
140
+ ? 'text-[var(--lokvis-fg-subtle)] hover:bg-[var(--lokvis-surface)]'
141
+ : 'text-[var(--lokvis-fg-muted)] hover:bg-[var(--lokvis-surface)]'
142
+ }`}
143
+ >
144
+ <span
145
+ className={`h-1.5 w-1.5 shrink-0 rounded-full ${
146
+ isCurrent
147
+ ? 'bg-[var(--lokvis-primary)]'
148
+ : isRedoBranch
149
+ ? 'bg-[var(--lokvis-border)]'
150
+ : 'bg-[var(--lokvis-fg-subtle)]'
151
+ }`}
152
+ aria-hidden="true"
153
+ />
154
+ <span className="truncate font-medium">{describeHistoryEntry(entry)}</span>
155
+ </button>
156
+ </li>
157
+ );
158
+ })}
159
+ </ol>
160
+ </aside>
161
+ );
162
+ }
163
163
 
164
- // 垂直布局(原实现)
165
- return (
166
- <aside
167
- className={`flex w-48 shrink-0 flex-col border-r border-zinc-200 dark:border-zinc-800 ${className}`}
168
- aria-label="History panel"
169
- >
170
- {headerEl}
171
- <div className="flex-1 overflow-y-auto px-2 py-2">
172
- <ol className="space-y-0.5">
173
- {/* 初始状态项(index=-1) */}
174
- <li>
175
- <button
176
- type="button"
177
- onClick={() => void jumpTo(-1)}
178
- className={`flex w-full items-center gap-1.5 rounded px-2 py-1 text-left text-[10px] transition-colors ${
179
- cursor === -1
180
- ? 'bg-indigo-50 text-indigo-700 ring-1 ring-indigo-200 dark:bg-indigo-950/30 dark:text-indigo-300 dark:ring-indigo-800'
181
- : 'text-zinc-500 hover:bg-zinc-50 dark:hover:bg-zinc-800'
182
- }`}
183
- >
184
- <span className="h-1.5 w-1.5 shrink-0 rounded-full bg-zinc-300 dark:bg-zinc-600" aria-hidden="true" />
185
- <span className="truncate">Initial</span>
186
- </button>
187
- </li>
188
- {entries.map((entry, i) => {
189
- const isCurrent = i === cursor;
190
- const isRedoBranch = i > cursor;
191
- return (
192
- <li key={entry.id}>
193
- <button
194
- type="button"
195
- onClick={() => void jumpTo(i)}
196
- aria-current={isCurrent ? 'step' : undefined}
197
- className={`flex w-full items-start gap-1.5 rounded px-2 py-1 text-left text-[10px] transition-colors ${
198
- isCurrent
199
- ? 'bg-indigo-50 text-indigo-700 ring-1 ring-indigo-200 dark:bg-indigo-950/30 dark:text-indigo-300 dark:ring-indigo-800'
200
- : isRedoBranch
201
- ? 'text-zinc-300 hover:bg-zinc-50 dark:text-zinc-600 dark:hover:bg-zinc-800/50'
202
- : 'text-zinc-600 hover:bg-zinc-50 dark:text-zinc-300 dark:hover:bg-zinc-800'
203
- }`}
204
- >
205
- <span
206
- className={`mt-0.5 h-1.5 w-1.5 shrink-0 rounded-full ${
207
- isCurrent
208
- ? 'bg-indigo-500'
209
- : isRedoBranch
210
- ? 'bg-zinc-200 dark:bg-zinc-700'
211
- : 'bg-zinc-400 dark:bg-zinc-500'
212
- }`}
213
- aria-hidden="true"
214
- />
215
- <div className="min-w-0 flex-1">
216
- <div className="truncate font-medium">
217
- {describeHistoryEntry(entry)}
218
- </div>
219
- <div className="text-[9px] text-zinc-400">
220
- {new Date(entry.timestamp).toLocaleTimeString()}
221
- </div>
222
- </div>
223
- </button>
224
- </li>
225
- );
226
- })}
227
- </ol>
228
- </div>
229
- </aside>
230
- );
164
+ // 垂直布局(原实现)
165
+ return (
166
+ <aside
167
+ className={`flex w-48 shrink-0 flex-col border-r border-[var(--lokvis-border)] ${className}`}
168
+ aria-label="History panel"
169
+ >
170
+ {headerEl}
171
+ <div className="flex-1 overflow-y-auto px-2 py-2">
172
+ <ol className="space-y-0.5">
173
+ {/* 初始状态项(index=-1) */}
174
+ <li>
175
+ <button
176
+ type="button"
177
+ onClick={() => void jumpTo(-1)}
178
+ className={`flex w-full items-center gap-1.5 rounded px-2 py-1 text-left text-[10px] transition-colors ${
179
+ cursor === -1
180
+ ? 'bg-[var(--lokvis-primary)]/10 text-[var(--lokvis-primary)] ring-1 ring-[var(--lokvis-primary)]/40'
181
+ : 'text-[var(--lokvis-fg-muted)] hover:bg-[var(--lokvis-surface)]'
182
+ }`}
183
+ >
184
+ <span className="h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--lokvis-fg-subtle)]" aria-hidden="true" />
185
+ <span className="truncate">Initial</span>
186
+ </button>
187
+ </li>
188
+ {entries.map((entry, i) => {
189
+ const isCurrent = i === cursor;
190
+ const isRedoBranch = i > cursor;
191
+ return (
192
+ <li key={entry.id}>
193
+ <button
194
+ type="button"
195
+ onClick={() => void jumpTo(i)}
196
+ aria-current={isCurrent ? 'step' : undefined}
197
+ className={`flex w-full items-start gap-1.5 rounded px-2 py-1 text-left text-[10px] transition-colors ${
198
+ isCurrent
199
+ ? 'bg-[var(--lokvis-primary)]/10 text-[var(--lokvis-primary)] ring-1 ring-[var(--lokvis-primary)]/40'
200
+ : isRedoBranch
201
+ ? 'text-[var(--lokvis-fg-subtle)] hover:bg-[var(--lokvis-surface)]'
202
+ : 'text-[var(--lokvis-fg-muted)] hover:bg-[var(--lokvis-surface)]'
203
+ }`}
204
+ >
205
+ <span
206
+ className={`mt-0.5 h-1.5 w-1.5 shrink-0 rounded-full ${
207
+ isCurrent
208
+ ? 'bg-[var(--lokvis-primary)]'
209
+ : isRedoBranch
210
+ ? 'bg-[var(--lokvis-border)]'
211
+ : 'bg-[var(--lokvis-fg-subtle)]'
212
+ }`}
213
+ aria-hidden="true"
214
+ />
215
+ <div className="min-w-0 flex-1">
216
+ <div className="truncate font-medium">
217
+ {describeHistoryEntry(entry)}
218
+ </div>
219
+ <div className="text-[9px] text-[var(--lokvis-fg-subtle)]">
220
+ {new Date(entry.timestamp).toLocaleTimeString()}
221
+ </div>
222
+ </div>
223
+ </button>
224
+ </li>
225
+ );
226
+ })}
227
+ </ol>
228
+ </div>
229
+ </aside>
230
+ );
231
231
  }
232
232
 
233
233
  /** 把 HistoryEntry 摘要为可读标签(capability 简称 + 关键参数) */
234
234
  function describeHistoryEntry(entry: HistoryEntry): string {
235
- // image.resize → Resize;image.watermark → Watermark
236
- const short = entry.capability.replace(/^image\./, '');
237
- const cap = short.charAt(0).toUpperCase() + short.slice(1);
238
- const params = entry.params ?? {};
239
- // 摘取 1-2 个关键参数作为提示
240
- const hints: string[] = [];
241
- if (typeof params.width === 'number' || typeof params.width === 'string') {
242
- hints.push(`${params.width}w`);
243
- }
244
- if (typeof params.quality === 'number') {
245
- hints.push(`q${params.quality}`);
246
- }
247
- if (typeof params.format === 'string') {
248
- hints.push(String(params.format));
249
- }
250
- if (typeof params.text === 'string' && params.text) {
251
- hints.push(`"${String(params.text).slice(0, 8)}"`);
252
- }
253
- return hints.length > 0 ? `${cap} (${hints.join(' ')})` : cap;
235
+ // image.resize → Resize;image.watermark → Watermark
236
+ const short = entry.capability.replace(/^image\./, '');
237
+ const cap = short.charAt(0).toUpperCase() + short.slice(1);
238
+ const params = entry.params ?? {};
239
+ // 摘取 1-2 个关键参数作为提示
240
+ const hints: string[] = [];
241
+ if (typeof params.width === 'number' || typeof params.width === 'string') {
242
+ hints.push(`${params.width}w`);
243
+ }
244
+ if (typeof params.quality === 'number') {
245
+ hints.push(`q${params.quality}`);
246
+ }
247
+ if (typeof params.format === 'string') {
248
+ hints.push(String(params.format));
249
+ }
250
+ if (typeof params.text === 'string' && params.text) {
251
+ hints.push(`"${String(params.text).slice(0, 8)}"`);
252
+ }
253
+ return hints.length > 0 ? `${cap} (${hints.join(' ')})` : cap;
254
254
  }