@lumenkey/cli 0.3.1 → 0.4.0-rc.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 (182) hide show
  1. package/README.md +36 -2
  2. package/dist/cli/copied.js +86 -85
  3. package/dist/cli/index.js +56 -44
  4. package/dist/cli/render.js +3 -2
  5. package/dist/core/asset-stream.d.ts +16 -0
  6. package/dist/core/asset-stream.js +64 -0
  7. package/dist/core/atomic-config-file.js +8 -7
  8. package/dist/core/chinese-messages.d.ts +4 -0
  9. package/dist/core/chinese-messages.js +907 -0
  10. package/dist/core/chinese-script.d.ts +7 -0
  11. package/dist/core/chinese-script.js +28 -0
  12. package/dist/core/client.js +2 -1
  13. package/dist/core/config-dir.js +2 -1
  14. package/dist/core/config.js +7 -6
  15. package/dist/core/download-root.d.ts +5 -0
  16. package/dist/core/download-root.js +64 -0
  17. package/dist/core/download.js +15 -12
  18. package/dist/core/generation-input.js +3 -2
  19. package/dist/core/generation.d.ts +8 -1
  20. package/dist/core/generation.js +13 -10
  21. package/dist/core/job-display.js +7 -6
  22. package/dist/core/locale.js +163 -148
  23. package/dist/core/outcome.js +6 -4
  24. package/dist/core/output-path.d.ts +3 -1
  25. package/dist/core/output-path.js +5 -2
  26. package/dist/core/params.js +26 -25
  27. package/dist/core/profiles.js +5 -4
  28. package/dist/core/set-values.d.ts +0 -1
  29. package/dist/core/set-values.js +7 -7
  30. package/dist/core/submission-journal.js +31 -16
  31. package/dist/core/version.js +2 -3
  32. package/dist/mcp/server.js +30 -29
  33. package/dist/studio/asset-integrity-hash.d.ts +28 -0
  34. package/dist/studio/asset-integrity-hash.js +93 -0
  35. package/dist/studio/asset-integrity-queue.d.ts +24 -0
  36. package/dist/studio/asset-integrity-queue.js +94 -0
  37. package/dist/studio/asset-store.d.ts +52 -0
  38. package/dist/studio/asset-store.js +352 -0
  39. package/dist/studio/canvas/LICENSES.txt +1590 -0
  40. package/dist/studio/canvas/dependencies.json +862 -0
  41. package/dist/studio/canvas/director-source-manifest.json +47 -0
  42. package/dist/studio/canvas/editor.css +239 -0
  43. package/dist/studio/canvas/editor.js +4613 -0
  44. package/dist/studio/canvas/integration-source-manifest.json +384 -0
  45. package/dist/studio/canvas/source-manifest.json +240 -0
  46. package/dist/studio/canvas-store.d.ts +1254 -0
  47. package/dist/studio/canvas-store.js +205 -0
  48. package/dist/studio/capture-placement.d.ts +11 -0
  49. package/dist/studio/capture-placement.js +85 -0
  50. package/dist/studio/cloud-contract.d.ts +128 -0
  51. package/dist/studio/cloud-contract.js +229 -0
  52. package/dist/studio/cloud-service.d.ts +178 -0
  53. package/dist/studio/cloud-service.js +202 -0
  54. package/dist/studio/director-captures.d.ts +59 -0
  55. package/dist/studio/director-captures.js +141 -0
  56. package/dist/studio/director-contract.d.ts +1489 -0
  57. package/dist/studio/director-contract.js +288 -0
  58. package/dist/studio/director-png.d.ts +6 -0
  59. package/dist/studio/director-png.js +41 -0
  60. package/dist/studio/director-relations.d.ts +40 -0
  61. package/dist/studio/director-relations.js +103 -0
  62. package/dist/studio/document-authority.d.ts +7 -0
  63. package/dist/studio/document-authority.js +50 -0
  64. package/dist/studio/draft-store.d.ts +95 -0
  65. package/dist/studio/draft-store.js +100 -0
  66. package/dist/studio/errors.d.ts +85 -0
  67. package/dist/studio/errors.js +94 -0
  68. package/dist/studio/launch.d.ts +5 -0
  69. package/dist/studio/launch.js +31 -0
  70. package/dist/studio/media.d.ts +9 -0
  71. package/dist/studio/media.js +50 -0
  72. package/dist/studio/output-save-contract.d.ts +1321 -0
  73. package/dist/studio/output-save-contract.js +26 -0
  74. package/dist/studio/output-save-journal.d.ts +971 -0
  75. package/dist/studio/output-save-journal.js +287 -0
  76. package/dist/studio/output-save-recovery.d.ts +18 -0
  77. package/dist/studio/output-save-recovery.js +161 -0
  78. package/dist/studio/output-save-service.d.ts +436 -0
  79. package/dist/studio/output-save-service.js +251 -0
  80. package/dist/studio/output-save-worker.d.ts +40 -0
  81. package/dist/studio/output-save-worker.js +360 -0
  82. package/dist/studio/paths.d.ts +6 -0
  83. package/dist/studio/paths.js +68 -0
  84. package/dist/studio/run-journal.d.ts +993 -0
  85. package/dist/studio/run-journal.js +125 -0
  86. package/dist/studio/server.d.ts +17 -0
  87. package/dist/studio/server.js +440 -0
  88. package/dist/studio/session.d.ts +6 -0
  89. package/dist/studio/session.js +26 -0
  90. package/dist/studio/shell.d.ts +3 -0
  91. package/dist/studio/shell.js +23 -0
  92. package/dist/studio/style.d.ts +2 -0
  93. package/dist/studio/style.js +29 -0
  94. package/dist/studio/types.d.ts +26 -0
  95. package/dist/studio/types.js +2 -0
  96. package/dist/studio/web/app.d.ts +2 -0
  97. package/dist/studio/web/app.js +662 -0
  98. package/dist/studio/web/asset-controller.d.ts +37 -0
  99. package/dist/studio/web/asset-controller.js +191 -0
  100. package/dist/studio/web/asset-model.d.ts +29 -0
  101. package/dist/studio/web/asset-model.js +51 -0
  102. package/dist/studio/web/controller.d.ts +113 -0
  103. package/dist/studio/web/controller.js +401 -0
  104. package/dist/studio/web/messages.d.ts +2 -0
  105. package/dist/studio/web/messages.js +74 -0
  106. package/dist/studio/web/model.d.ts +75 -0
  107. package/dist/studio/web/model.js +182 -0
  108. package/dist/studio/web/save-controller.d.ts +85 -0
  109. package/dist/studio/web/save-controller.js +495 -0
  110. package/dist/studio/web/save-model.d.ts +63 -0
  111. package/dist/studio/web/save-model.js +64 -0
  112. package/dist/studio/web/save-queue.d.ts +7 -0
  113. package/dist/studio/web/save-queue.js +62 -0
  114. package/dist/studio/web/save-view.d.ts +9 -0
  115. package/dist/studio/web/save-view.js +19 -0
  116. package/dist/studio/writer-lock.d.ts +5 -0
  117. package/dist/studio/writer-lock.js +88 -0
  118. package/package.json +11 -5
  119. package/dist/cli/copied.d.ts.map +0 -1
  120. package/dist/cli/copied.js.map +0 -1
  121. package/dist/cli/index.d.ts.map +0 -1
  122. package/dist/cli/index.js.map +0 -1
  123. package/dist/cli/render.d.ts.map +0 -1
  124. package/dist/cli/render.js.map +0 -1
  125. package/dist/core/atomic-config-file.d.ts.map +0 -1
  126. package/dist/core/atomic-config-file.js.map +0 -1
  127. package/dist/core/chat.d.ts.map +0 -1
  128. package/dist/core/chat.js.map +0 -1
  129. package/dist/core/client.d.ts.map +0 -1
  130. package/dist/core/client.js.map +0 -1
  131. package/dist/core/config-dir.d.ts.map +0 -1
  132. package/dist/core/config-dir.js.map +0 -1
  133. package/dist/core/config-error.d.ts.map +0 -1
  134. package/dist/core/config-error.js.map +0 -1
  135. package/dist/core/config.d.ts.map +0 -1
  136. package/dist/core/config.js.map +0 -1
  137. package/dist/core/credit-cap.d.ts.map +0 -1
  138. package/dist/core/credit-cap.js.map +0 -1
  139. package/dist/core/download.d.ts.map +0 -1
  140. package/dist/core/download.js.map +0 -1
  141. package/dist/core/error-result.d.ts.map +0 -1
  142. package/dist/core/error-result.js.map +0 -1
  143. package/dist/core/explain.d.ts.map +0 -1
  144. package/dist/core/explain.js.map +0 -1
  145. package/dist/core/generation-input.d.ts.map +0 -1
  146. package/dist/core/generation-input.js.map +0 -1
  147. package/dist/core/generation-result.d.ts.map +0 -1
  148. package/dist/core/generation-result.js.map +0 -1
  149. package/dist/core/generation.d.ts.map +0 -1
  150. package/dist/core/generation.js.map +0 -1
  151. package/dist/core/job-display.d.ts.map +0 -1
  152. package/dist/core/job-display.js.map +0 -1
  153. package/dist/core/locale.d.ts.map +0 -1
  154. package/dist/core/locale.js.map +0 -1
  155. package/dist/core/origin.d.ts.map +0 -1
  156. package/dist/core/origin.js.map +0 -1
  157. package/dist/core/outcome.d.ts.map +0 -1
  158. package/dist/core/outcome.js.map +0 -1
  159. package/dist/core/output-path.d.ts.map +0 -1
  160. package/dist/core/output-path.js.map +0 -1
  161. package/dist/core/params.d.ts.map +0 -1
  162. package/dist/core/params.js.map +0 -1
  163. package/dist/core/poller.d.ts.map +0 -1
  164. package/dist/core/poller.js.map +0 -1
  165. package/dist/core/profiles.d.ts.map +0 -1
  166. package/dist/core/profiles.js.map +0 -1
  167. package/dist/core/set-values.d.ts.map +0 -1
  168. package/dist/core/set-values.js.map +0 -1
  169. package/dist/core/submission-journal.d.ts.map +0 -1
  170. package/dist/core/submission-journal.js.map +0 -1
  171. package/dist/core/types.d.ts.map +0 -1
  172. package/dist/core/types.js.map +0 -1
  173. package/dist/core/version.d.ts.map +0 -1
  174. package/dist/core/version.js.map +0 -1
  175. package/dist/core/wait.d.ts.map +0 -1
  176. package/dist/core/wait.js.map +0 -1
  177. package/dist/index.d.ts.map +0 -1
  178. package/dist/index.js.map +0 -1
  179. package/dist/mcp/render.d.ts.map +0 -1
  180. package/dist/mcp/render.js.map +0 -1
  181. package/dist/mcp/server.d.ts.map +0 -1
  182. package/dist/mcp/server.js.map +0 -1
@@ -0,0 +1,401 @@
1
+ import { modelsFor, parseFields, validateModels } from './model.js';
2
+ import { createSaveController } from './save-controller.js';
3
+ import { messageForCode } from './messages.js';
4
+ import { createSaveQueue } from './save-queue.js';
5
+ import { createAssetController } from './asset-controller.js';
6
+ import { parseLocalAssetReceipt } from './asset-model.js';
7
+ // Keep HTTP classification and known durable-capture facts across the UI port.
8
+ // Dropping them would turn a recoverable 202/409 into an unclassified retry.
9
+ export function createLocalApi(fetcher = fetch, onUnauthorized) {
10
+ let expired = false;
11
+ // Retain one lifetime signal: fetch may have returned headers while its JSON
12
+ // or binary body is still streaming. A per-request headers-only registry misses it.
13
+ const sessionCancellation = new AbortController();
14
+ const sessionError = () => Object.assign(new Error('请从 CLI 重新打开 Studio'), { code: 'STUDIO_SESSION_REQUIRED', status: 401 });
15
+ const request = async (input, init) => {
16
+ if (expired)
17
+ throw sessionError();
18
+ const response = await fetcher(input, { ...init, signal: AbortSignal.any([sessionCancellation.signal, ...(init?.signal ? [init.signal] : [])]) });
19
+ if (response.status === 401) {
20
+ expired = true;
21
+ sessionCancellation.abort();
22
+ onUnauthorized?.();
23
+ throw sessionError();
24
+ }
25
+ if (expired)
26
+ throw sessionError();
27
+ return response;
28
+ };
29
+ const api = async (route, body) => {
30
+ const response = await request('/studio/api/' + route, { method: body === undefined ? 'GET' : 'POST',
31
+ headers: body === undefined ? {} : { 'Content-Type': 'application/json' },
32
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }), signal: AbortSignal.timeout(40_000) });
33
+ let result;
34
+ try {
35
+ result = await response.json();
36
+ }
37
+ catch (error) {
38
+ if (expired)
39
+ throw sessionError();
40
+ if (error instanceof TypeError || error instanceof Error && ['AbortError', 'TimeoutError'].includes(error.name)) {
41
+ throw Object.assign(new Error('本机回复传输中断,请核对原保存状态。'), { code: 'STUDIO_REQUEST_FAILED', transportFailure: true });
42
+ }
43
+ throw Object.assign(new Error('本机服务没有返回可读取的数据。'), { code: 'STUDIO_RESPONSE_INVALID', status: response.status });
44
+ }
45
+ if (expired)
46
+ throw sessionError();
47
+ if (!response.ok) {
48
+ const diagnostic = uiError(result);
49
+ throw Object.assign(new Error(diagnostic.message), diagnostic, { status: response.status,
50
+ ...(result?.capture ? { capture: result.capture } : {}),
51
+ });
52
+ }
53
+ if (result && typeof result === 'object' && result.ok === false)
54
+ throw Object.assign(new Error('本机服务返回了不符合页面契约的数据,请核对平台记录。'), { code: 'STUDIO_RESPONSE_INVALID', status: response.status });
55
+ return result;
56
+ };
57
+ return Object.assign(api, { request });
58
+ }
59
+ export function uiError(error) {
60
+ const outer = error && typeof error === 'object' ? error : undefined;
61
+ const e = outer?.error && typeof outer.error === 'object' && !Array.isArray(outer.error) ? outer.error : outer;
62
+ const code = typeof e?.code === 'string' ? e.code : 'STUDIO_REQUEST_FAILED';
63
+ return { code, message: messageForCode(code, typeof e?.message === 'string' ? e.message : '请求未成功,请重试或检查本机服务。'),
64
+ ...(typeof e?.type === 'string' ? { type: e.type } : {}),
65
+ ...(typeof e?.httpStatus === 'number' && Number.isInteger(e.httpStatus) && e.httpStatus >= 400 && e.httpStatus <= 599 ? { httpStatus: e.httpStatus } : {}),
66
+ ...(e?.submission === 'none' || e?.submission === 'unknown' || e?.submission === 'created' ? { submission: e.submission } : {}),
67
+ ...(e?.recovery === 'rerun' || e?.recovery === 'resume-command' || e?.recovery === 'expired' || e?.recovery === 'ledger' ? { recovery: e.recovery } : {}),
68
+ ...(typeof e?.resumeDeadline === 'string' ? { resumeDeadline: e.resumeDeadline } : {}),
69
+ };
70
+ }
71
+ export function createStudioController(options) {
72
+ const now = options.now ?? Date.now, uuid = options.uuid ?? (() => crypto.randomUUID());
73
+ const state = { local: { status: 'idle' }, catalog: { status: 'idle' }, balance: { status: 'idle' }, runs: { status: 'idle' }, assets: { status: 'idle' }, drafts: { status: 'idle' }, mode: 'image', edits: { image: { modelId: '', fields: {} }, video: { modelId: '', fields: {} } }, dirty: { image: false, video: false }, connected: false, connecting: false, quoteBusy: false, submitBusy: false, saving: false, refreshing: false, notice: '', sessionExpired: false, saves: { binding: { status: 'idle' }, outputs: {}, operations: {}, targets: {} }, assetChecks: {} };
74
+ const saveQueue = createSaveQueue();
75
+ let cloudEpoch = 0, editEpoch = 0;
76
+ let assetsRead;
77
+ let assetsVersion = 0;
78
+ const emit = () => options.onChange?.(state);
79
+ const api = async (route, body) => {
80
+ if (state.sessionExpired)
81
+ throw Object.assign(new Error('请从 CLI 重新打开 Studio'), { code: 'STUDIO_SESSION_REQUIRED', status: 401 });
82
+ try {
83
+ const result = await (route === 'assets' && body === undefined ? saveQueue.run('asset-list', 2, () => options.api(route, body)) : options.api(route, body));
84
+ if (state.sessionExpired)
85
+ throw { code: 'STUDIO_SESSION_REQUIRED', message: '请从 CLI 重新打开 Studio', status: 401 };
86
+ return result;
87
+ }
88
+ catch (error) {
89
+ if (error?.status === 401 || uiError(error).code === 'STUDIO_SESSION_REQUIRED')
90
+ expireSession();
91
+ throw error;
92
+ }
93
+ };
94
+ const saves = createSaveController({ api, queue: saveQueue, onChange: () => { state.saves = saves.state; emit(); } });
95
+ state.saves = saves.state;
96
+ const assetChecks = createAssetController({ api, queue: saveQueue, onChange: () => { state.assetChecks = assetChecks.state; emit(); } });
97
+ state.assetChecks = assetChecks.state;
98
+ function expireSession() {
99
+ if (state.sessionExpired)
100
+ return;
101
+ state.sessionExpired = true;
102
+ resetCloud();
103
+ assetChecks.dispose();
104
+ assetsVersion++;
105
+ state.error = uiError({ code: 'STUDIO_SESSION_REQUIRED', message: '请从 CLI 重新打开 Studio' });
106
+ state.local = { ...state.local, status: 'error', error: state.error };
107
+ emit();
108
+ }
109
+ const failure = (error) => { state.error = uiError(error); emit(); };
110
+ function invalidate() { editEpoch++; state.quote = undefined; state.error = undefined; state.notice = ''; }
111
+ function resetCloud() { cloudEpoch++; state.connected = false; state.gateway = undefined; state.catalog = { status: 'idle' }; state.balance = { status: 'idle' }; state.runs = { status: 'idle' }; state.quote = undefined; saves.invalidate(); }
112
+ function mergeCleanDrafts(snapshot) {
113
+ for (const mode of ['image', 'video'])
114
+ if (!state.dirty[mode])
115
+ state.edits[mode] = structuredClone(snapshot.drafts[mode] ?? { modelId: '', fields: {} });
116
+ }
117
+ async function load(key, route, project = (v) => v, epoch, current = () => true) {
118
+ if (state.sessionExpired)
119
+ return;
120
+ const previous = state[key].value;
121
+ Object.assign(state, { [key]: { status: 'loading', ...(previous !== undefined ? { value: previous } : {}) } });
122
+ emit();
123
+ try {
124
+ const value = project(await api(route));
125
+ if ((epoch === undefined || epoch === cloudEpoch) && current())
126
+ Object.assign(state, { [key]: { status: 'ready', value } });
127
+ }
128
+ catch (error) {
129
+ if ((epoch === undefined || epoch === cloudEpoch) && current())
130
+ Object.assign(state, { [key]: { status: 'error', ...(previous !== undefined ? { value: previous } : {}), error: uiError(error) } });
131
+ }
132
+ emit();
133
+ }
134
+ async function refreshCloud() {
135
+ if (!state.connected || state.refreshing)
136
+ return;
137
+ state.refreshing = true;
138
+ const epoch = cloudEpoch;
139
+ try {
140
+ await Promise.all([load('catalog', 'cloud/models', v => validateModels(v.items), epoch), load('balance', 'cloud/balance', undefined, epoch), load('runs', 'runs', v => v.items, epoch)]);
141
+ }
142
+ finally {
143
+ state.refreshing = false;
144
+ emit();
145
+ }
146
+ }
147
+ function mergeRun(run) { state.runs = { status: 'ready', value: [run, ...(state.runs.value ?? []).filter(r => r.runId !== run.runId)] }; }
148
+ function refreshAssets(options = {}) {
149
+ if (options.afterWrite)
150
+ assetsVersion++;
151
+ if (!assetsRead)
152
+ assetsRead = (async () => {
153
+ let version;
154
+ do {
155
+ version = assetsVersion;
156
+ const ticket = assetChecks.beginList();
157
+ await load('assets', 'assets', value => parseLocalAssetReceipt({ status: 'ready', value }), undefined, () => version === assetsVersion);
158
+ if (version === assetsVersion && state.assets.status === 'ready' && state.assets.value && !state.sessionExpired)
159
+ assetChecks.rememberList(state.assets.value.assets, ticket);
160
+ } while (version !== assetsVersion && !state.sessionExpired);
161
+ })().finally(() => { assetsRead = undefined; });
162
+ return assetsRead;
163
+ }
164
+ return {
165
+ state,
166
+ expireSession,
167
+ readOutputs: saves.readOutputs,
168
+ saveOutput: saves.saveOutput, checkOutput: saves.checkOutput, readSave: saves.readOperation, retrySave: saves.retryOperation, markSaveVisible: saves.markVisible,
169
+ readAsset: assetChecks.read, markAssetVisible: assetChecks.markVisible,
170
+ setSavePageVisible(value) { saves.setPageVisible(value); assetChecks.setPageVisible(value); },
171
+ disposeSaves() { saves.dispose(); assetChecks.dispose(); },
172
+ async bindSaves() { if (state.local.status === 'ready' && !state.sessionExpired)
173
+ await saves.bind(); },
174
+ async init() {
175
+ await load('local', 'session');
176
+ if (state.local.status !== 'ready')
177
+ return;
178
+ await saves.bind();
179
+ if (state.sessionExpired)
180
+ return;
181
+ await Promise.all([refreshAssets(), state.local.value.storage.configured ? load('drafts', 'drafts') : Promise.resolve()]);
182
+ if (state.drafts.value)
183
+ mergeCleanDrafts(state.drafts.value);
184
+ // Reuse an already explicit connection on browser reload, never create one here.
185
+ if (state.local.value.cloud.connected) {
186
+ state.connected = true;
187
+ state.gateway = state.local.value.cloud.gateway;
188
+ await refreshCloud();
189
+ }
190
+ emit();
191
+ },
192
+ setMode(mode) { if (mode !== state.mode) {
193
+ invalidate();
194
+ state.mode = mode;
195
+ emit();
196
+ } },
197
+ selectModel(id) {
198
+ const model = modelsFor(state.catalog.value ?? [], state.mode).find(m => m.id === id);
199
+ if (!model)
200
+ return;
201
+ invalidate();
202
+ const before = state.edits[state.mode];
203
+ const prompt = before.fields.prompt;
204
+ state.edits[state.mode] = { modelId: id, fields: prompt && Object.hasOwn(model.inputSchema.properties, 'prompt') ? { prompt } : {} };
205
+ state.dirty[state.mode] = true;
206
+ emit();
207
+ },
208
+ setField(key, value) { if (['__proto__', 'constructor', 'prototype'].includes(key))
209
+ return; invalidate(); state.edits[state.mode].fields[key] = value; state.dirty[state.mode] = true; emit(); },
210
+ async saveDraft() {
211
+ if (state.saving || state.drafts.status !== 'ready')
212
+ return;
213
+ state.saving = true;
214
+ state.error = undefined;
215
+ emit();
216
+ const mode = state.mode, draft = structuredClone(state.edits[mode]), revision = state.drafts.value.revision;
217
+ try {
218
+ const saved = await api('drafts', { revision, mode, modelId: draft.modelId, fields: draft.fields });
219
+ state.drafts = { status: 'ready', value: saved };
220
+ if (JSON.stringify(state.edits[mode]) === JSON.stringify(draft))
221
+ state.dirty[mode] = false;
222
+ mergeCleanDrafts(saved);
223
+ state.notice = '草稿已写入本机目录;本次保存不会提交任务。';
224
+ }
225
+ catch (error) {
226
+ failure(error);
227
+ }
228
+ finally {
229
+ state.saving = false;
230
+ emit();
231
+ }
232
+ },
233
+ async rereadDrafts() {
234
+ if (state.saving || state.drafts.status === 'loading')
235
+ return;
236
+ state.notice = '';
237
+ state.error = undefined;
238
+ await load('drafts', 'drafts');
239
+ if (state.drafts.status === 'ready' && state.drafts.value) {
240
+ mergeCleanDrafts(state.drafts.value);
241
+ state.notice = '已读取最新草稿版本;未保存编辑已保留,再保存将写入当前编辑。';
242
+ }
243
+ emit();
244
+ },
245
+ async connect() {
246
+ if (state.sessionExpired || state.connecting || state.submitBusy)
247
+ return;
248
+ state.connecting = true;
249
+ state.error = undefined;
250
+ resetCloud();
251
+ const epoch = cloudEpoch;
252
+ emit();
253
+ try {
254
+ const r = await api('cloud/connect', {});
255
+ if (epoch === cloudEpoch) {
256
+ state.connected = r.connected === true;
257
+ state.gateway = r.gateway;
258
+ await saves.bind();
259
+ await refreshCloud();
260
+ }
261
+ }
262
+ catch (error) {
263
+ if (epoch === cloudEpoch)
264
+ failure(error);
265
+ }
266
+ finally {
267
+ state.connecting = false;
268
+ emit();
269
+ }
270
+ },
271
+ async disconnect() {
272
+ if (state.sessionExpired)
273
+ return;
274
+ resetCloud();
275
+ const epoch = cloudEpoch;
276
+ emit();
277
+ try {
278
+ await api('cloud/disconnect', {});
279
+ if (epoch === cloudEpoch)
280
+ await saves.bind();
281
+ }
282
+ catch (error) {
283
+ if (epoch === cloudEpoch)
284
+ failure(error);
285
+ } // No cancellation promise.
286
+ },
287
+ refreshCloud,
288
+ async quote() {
289
+ if (state.quoteBusy || state.submitBusy)
290
+ return;
291
+ state.error = undefined;
292
+ state.quote = undefined;
293
+ if (!state.connected || !state.local.value?.storage.configured || state.catalog.status !== 'ready') {
294
+ failure({ code: 'STUDIO_NOT_READY', message: '请先设置保存目录并连接云端目录。' });
295
+ return;
296
+ }
297
+ const edit = state.edits[state.mode];
298
+ const model = modelsFor(state.catalog.value, state.mode).find(m => m.id === edit.modelId);
299
+ if (!model) {
300
+ failure({ code: 'STUDIO_MODEL_NOT_AVAILABLE', message: '请选择当前目录中的模型。' });
301
+ return;
302
+ }
303
+ let input;
304
+ try {
305
+ input = parseFields(model, edit.fields);
306
+ }
307
+ catch (error) {
308
+ failure(error);
309
+ return;
310
+ }
311
+ const epoch = cloudEpoch, version = editEpoch;
312
+ state.quoteBusy = true;
313
+ emit();
314
+ try {
315
+ const run = await api('quotes', { requestId: uuid(), modelId: model.id, input });
316
+ if (epoch === cloudEpoch && state.connected) {
317
+ mergeRun(run);
318
+ if (version === editEpoch)
319
+ state.quote = run;
320
+ }
321
+ }
322
+ catch (error) {
323
+ if (epoch === cloudEpoch && version === editEpoch)
324
+ failure(error);
325
+ }
326
+ finally {
327
+ state.quoteBusy = false;
328
+ emit();
329
+ }
330
+ },
331
+ async confirm() {
332
+ if (state.submitBusy)
333
+ return;
334
+ const run = state.quote;
335
+ if (!state.connected || !run || run.state !== 'QUOTED')
336
+ return;
337
+ if (Date.parse(run.quote.expiresAt) <= now() || !Number.isFinite(Date.parse(run.quote.expiresAt))) {
338
+ failure({ code: 'STUDIO_QUOTE_EXPIRED', message: '报价已过期,请重新报价。' });
339
+ return;
340
+ }
341
+ if (state.runs.value?.some(r => ['SUBMISSION_UNKNOWN', 'SUBMITTING'].includes(r.state))) {
342
+ failure({ code: 'STUDIO_SUBMISSION_UNRESOLVED', message: '此前提交待确认,不能新建任务。' });
343
+ return;
344
+ }
345
+ const epoch = cloudEpoch, version = editEpoch;
346
+ state.submitBusy = true;
347
+ state.error = undefined;
348
+ emit();
349
+ try {
350
+ const result = await api(`runs/${run.runId}/confirm`, { confirm: true });
351
+ if (epoch === cloudEpoch) {
352
+ mergeRun(result);
353
+ if (version === editEpoch)
354
+ state.quote = result;
355
+ }
356
+ }
357
+ catch (error) {
358
+ if (epoch === cloudEpoch) {
359
+ failure(error);
360
+ state.quote = undefined;
361
+ await load('runs', 'runs', v => v.items, epoch);
362
+ }
363
+ }
364
+ finally {
365
+ if (epoch === cloudEpoch && state.connected)
366
+ await load('balance', 'cloud/balance', undefined, epoch);
367
+ state.submitBusy = false;
368
+ emit();
369
+ }
370
+ },
371
+ async refreshRun(id) {
372
+ if (!state.connected || state.refreshing)
373
+ return;
374
+ state.refreshing = true;
375
+ const epoch = cloudEpoch;
376
+ emit();
377
+ try {
378
+ const run = await api(`runs/${id}/refresh`, {});
379
+ if (epoch === cloudEpoch) {
380
+ mergeRun(run);
381
+ if (state.quote?.runId === id)
382
+ state.quote = run;
383
+ await saves.readOutputs(id);
384
+ }
385
+ }
386
+ catch (error) {
387
+ if (epoch === cloudEpoch)
388
+ failure(error);
389
+ }
390
+ finally {
391
+ if (epoch === cloudEpoch && state.connected)
392
+ await load('balance', 'cloud/balance', undefined, epoch);
393
+ state.refreshing = false;
394
+ emit();
395
+ }
396
+ },
397
+ refreshAssets,
398
+ reportError: failure,
399
+ };
400
+ }
401
+ //# sourceMappingURL=controller.js.map
@@ -0,0 +1,2 @@
1
+ export declare function messageForCode(code: string, fallback: string, locale?: string): string;
2
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1,74 @@
1
+ // Page-owned zh-CN copy. Unknown codes retain the safe server diagnostic.
2
+ const messages = {
3
+ 'zh-CN': {
4
+ STUDIO_SESSION_REQUIRED: '请从 CLI 重新打开 Studio',
5
+ STUDIO_STORAGE_FAILED: '本机文件操作失败;尚未确认保存成功。',
6
+ STUDIO_STORAGE_BUSY: '保存目录正在使用,或尚未确认上一次写入已结束。',
7
+ STUDIO_STORAGE_CLOSED: '本机存储已关闭,请从 CLI 重新打开 Studio。',
8
+ STUDIO_PATH_UNSAFE: '保存路径包含链接或已发生变化,请在 CLI 中核对目录。',
9
+ STUDIO_OUTPUT_ROOT_REQUIRED: '请通过 CLI 指定绝对保存目录。',
10
+ STUDIO_INPUT_INVALID: '输入格式不符合要求,请检查当前填写的内容。',
11
+ STUDIO_RESPONSE_INVALID: '本机回复不符合页面契约,当前结果尚未确认。',
12
+ STUDIO_REQUEST_FAILED: '请求未成功,请重试或检查本机服务。',
13
+ STUDIO_REQUEST_FORBIDDEN: '请求来源未获准,请从 CLI 重新打开 Studio。',
14
+ STUDIO_ROUTE_NOT_FOUND: '本机接口不存在,请核对 Studio 版本。',
15
+ STUDIO_CLOUD_CONFIG_INVALID: '本机云连接尚未配置,请先在 CLI 中配置。',
16
+ STUDIO_CLOUD_DISCONNECTED: '云端尚未连接;本机编辑仍然保留。',
17
+ STUDIO_CLOUD_RESPONSE_INVALID: '云端回复不符合契约,已停止当前操作。',
18
+ STUDIO_CLOUD_FAILED: '云端请求未成功,请检查连接后再试。',
19
+ STUDIO_RUN_NOT_FOUND: '当前连接下未找到这条任务记录。',
20
+ STUDIO_RUN_CONFLICT: '任务标识与已有记录冲突,未新建任务。',
21
+ STUDIO_JOURNAL_INVALID: '本机任务记录无法核对,请保留文件并检查记录。',
22
+ STUDIO_DRAFT_INVALID: '草稿文件已损坏或被外部修改,未覆盖原文件。',
23
+ STUDIO_DRAFT_CONFLICT: '草稿在另一窗口发生变化,请保留编辑并读取最新版本。',
24
+ STUDIO_CATALOG_INVALID: '素材清单无法读取;原文件保留。',
25
+ STUDIO_MEDIA_TYPE_INVALID: '文件格式不受支持,请选择图片或视频文件。',
26
+ STUDIO_MEDIA_TOO_LARGE: '文件超过本机保存大小上限。',
27
+ STUDIO_ASSET_ID_INVALID: '素材标识无效,请重新读取素材列表。',
28
+ STUDIO_ASSET_NOT_FOUND: '未找到这份本机素材。',
29
+ STUDIO_ASSET_CORRUPT: '素材缺失或校验未通过,请核对本机文件。',
30
+ STUDIO_MODEL_NOT_AVAILABLE: '当前目录没有这款可用模型,请重新选择。',
31
+ STUDIO_NOT_READY: '请先设置保存目录并连接云端目录。',
32
+ STUDIO_CONFIRM_REQUIRED: '请先核对报价,再明确确认生成。',
33
+ STUDIO_QUOTE_EXPIRED: '报价已过期,请重新查看报价。',
34
+ STUDIO_SUBMISSION_UNRESOLVED: '此前提交结果待确认,请核对平台任务与账单记录,不要重复提交。',
35
+ SAVE_CLOUD_CONFIG_REQUIRED: '请先按 CLI 方式配置本机连接。',
36
+ SAVE_OUTPUT_ROOT_REQUIRED: '尚未设置可用保存目录,请通过 CLI 指定目录后重新打开 Studio。',
37
+ SAVE_ROOT_NOT_ALLOWED: '本机保存目录未获准,请在 CLI 中核对批准的目录范围。',
38
+ SAVE_BINDING_REQUIRED: '本机保存连接需要重新绑定,请重读输出列表。',
39
+ SAVE_BINDING_MISMATCH: '这条记录不属于当前保存连接,请核对 CLI 使用的连接。',
40
+ SAVE_AUTHORITY_INVALID: '本机保存记录损坏,保存区域暂不可用;其他功能仍可使用。请保留原文件并联系支持。',
41
+ SAVE_AUTHORITY_UNAVAILABLE: '暂时无法核对本机保存记录,请稍后重读。',
42
+ SAVE_OUTPUT_IDENTITY_UNVERIFIED: '输出身份尚未核实,不能确认本机保存资格。',
43
+ SAVE_OUTPUT_IDENTITY_CONFLICT: '输出身份存在冲突,已停止本机保存。',
44
+ SAVE_JOB_NOT_TERMINAL: '云端任务仍在运行,尚不能保存输出。',
45
+ SAVE_MEDIA_UNSUPPORTED: '这个输出的格式或来源暂不支持本机保存。',
46
+ SAVE_SOURCE_UNAVAILABLE: '暂未取得可用输出来源,不能据此认定文件永久丢失。',
47
+ SAVE_OPERATION_EXISTS: '这份输出已有保存记录,请核对原操作的状态。',
48
+ SAVE_MEDIA_TOO_LARGE: '文件超过本机保存大小上限,未保存成功。',
49
+ SAVE_LENGTH_MISMATCH: '收到的文件长度与来源声明不符,请核对输出来源。',
50
+ SAVE_DIGEST_MISMATCH: '文件校验不一致,请核对输出来源。',
51
+ SAVE_NETWORK_ERROR: '文件传输中断,请核对原保存记录。',
52
+ SAVE_DOWNLOAD_TIMEOUT: '文件传输超时,请核对原保存记录。',
53
+ SAVE_SERVICE_BUSY: '本机服务暂忙,请稍后核对保存状态。',
54
+ SAVE_DISK_FULL: '磁盘空间不足,请释放空间后核对原保存记录。',
55
+ SAVE_PERMISSION_DENIED: '保存目录没有写入权限,请核对目录权限。',
56
+ SAVE_WRITE_FAILED: '本机文件写入未完成,请核对原保存记录。',
57
+ SAVE_PATH_UNSAFE: '文件路径不安全或发生变化,请保留文件并人工核对。',
58
+ SAVE_TARGET_CONFLICT: '目标文件与原保存记录不一致,请保留文件并人工核对。',
59
+ SAVE_REVISION_CONFLICT: '保存记录已更新,正在重新核对;不会自动重试保存。',
60
+ SAVE_RETRY_NOT_ALLOWED: '当前记录不允许安全重试,请先核对保存状态。',
61
+ SAVE_OPERATION_NOT_FOUND: '未找到原保存记录;这不能证明此前未受理。',
62
+ SAVE_ROOT_MISMATCH: '保存根与原记录不一致,请核对 CLI 中的保存位置。',
63
+ SAVE_REGISTRATION_PENDING: '原文件已写入,素材登记尚未完成;可按原保存恢复登记。',
64
+ SAVE_RELATION_PENDING: '素材已登记,输出关联尚未完成;可按原保存恢复关联。',
65
+ SAVE_URL_EXPIRED_RENEWABLE: '输出来源需要更新,请核对原保存记录。',
66
+ SAVE_FILE_MISSING: '曾保存的文件现已缺失,不会自动重新下载。',
67
+ SAVE_FILE_MODIFIED: '曾保存的文件已被修改,不会覆盖用户文件。',
68
+ SAVE_FILE_UNSAFE: '曾保存的文件不再满足安全条件,请人工核对。',
69
+ },
70
+ };
71
+ export function messageForCode(code, fallback, locale = 'zh-CN') {
72
+ return Object.hasOwn(messages[locale] ?? {}, code) ? messages[locale][code] : fallback;
73
+ }
74
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1,75 @@
1
+ export type Mode = 'image' | 'video';
2
+ export interface MediaModel {
3
+ id: string;
4
+ displayName?: string;
5
+ name?: string;
6
+ type?: string;
7
+ capabilities?: string[];
8
+ inputSchema: {
9
+ properties: Record<string, Record<string, unknown>>;
10
+ required?: string[];
11
+ 'x-lumenkey-ui'?: unknown;
12
+ };
13
+ }
14
+ export interface PublicFailure {
15
+ code: string;
16
+ message: string;
17
+ type: string;
18
+ httpStatus?: number;
19
+ submission?: 'none' | 'unknown' | 'created';
20
+ recovery?: 'rerun' | 'resume-command' | 'expired' | 'ledger';
21
+ resumeDeadline?: string;
22
+ }
23
+ export interface PublicRun {
24
+ runId: string;
25
+ modelId: string;
26
+ state: string;
27
+ quote: {
28
+ credits: number;
29
+ expiresAt: string;
30
+ quoteId: string;
31
+ modelId: string;
32
+ };
33
+ job?: {
34
+ jobId: string;
35
+ status: string;
36
+ outputs: {
37
+ url: string;
38
+ type?: string;
39
+ }[];
40
+ error?: {
41
+ code: string;
42
+ message?: string;
43
+ };
44
+ };
45
+ lastErrorCode?: string;
46
+ failure?: PublicFailure;
47
+ createdAt?: string;
48
+ updatedAt?: string;
49
+ }
50
+ export interface Field {
51
+ key: string;
52
+ title: string;
53
+ kind: 'text' | 'number' | 'boolean' | 'json' | 'enum';
54
+ spec: Record<string, unknown>;
55
+ options?: {
56
+ value: string;
57
+ label: string;
58
+ }[];
59
+ defaultHint?: string;
60
+ required: boolean;
61
+ placement?: 'input' | 'common' | 'advanced';
62
+ labelKnown?: boolean;
63
+ }
64
+ export declare function modelsFor(models: MediaModel[], mode: Mode): MediaModel[];
65
+ export declare function validateModels(value: unknown): MediaModel[];
66
+ export declare function describeFields(model: MediaModel): Field[];
67
+ export declare function parseFields(model: MediaModel, fields: Record<string, string>): Record<string, unknown>;
68
+ export declare function runLabel(run: PublicRun): string;
69
+ export declare function runDiagnosticText(run: PublicRun): string | undefined;
70
+ export declare function runRecoveryText(run: PublicRun): string | undefined;
71
+ export declare function safeOutputUrl(value: string): string | undefined;
72
+ export declare function fieldPlacement(field: Pick<Field, 'kind' | 'placement'>): 'input' | 'common' | 'advanced';
73
+ export declare function fieldDisplayLabel(field: Pick<Field, 'title' | 'required'>): string;
74
+ export declare function compactLocation(path: string): string;
75
+ //# sourceMappingURL=model.d.ts.map