@talex-touch/utils 1.0.39 → 1.0.42

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.
@@ -1,12 +1,51 @@
1
1
  import type { Ref } from 'vue'
2
2
  import type {
3
- AiChatPayload,
4
- AiEmbeddingPayload,
5
- AiInvokeOptions,
6
- AiInvokeResult,
7
- AiProviderConfig,
8
- AiVisionOcrPayload,
9
- AiVisionOcrResult,
3
+ IntelligenceAgentPayload,
4
+ IntelligenceAgentResult,
5
+ IntelligenceChatPayload,
6
+ IntelligenceClassificationPayload,
7
+ IntelligenceClassificationResult,
8
+ IntelligenceCodeDebugPayload,
9
+ IntelligenceCodeDebugResult,
10
+ IntelligenceCodeExplainPayload,
11
+ IntelligenceCodeExplainResult,
12
+ IntelligenceCodeGeneratePayload,
13
+ IntelligenceCodeGenerateResult,
14
+ IntelligenceCodeRefactorPayload,
15
+ IntelligenceCodeRefactorResult,
16
+ IntelligenceCodeReviewPayload,
17
+ IntelligenceCodeReviewResult,
18
+ IntelligenceContentExtractPayload,
19
+ IntelligenceContentExtractResult,
20
+ IntelligenceEmbeddingPayload,
21
+ IntelligenceGrammarCheckPayload,
22
+ IntelligenceGrammarCheckResult,
23
+ IntelligenceImageAnalyzePayload,
24
+ IntelligenceImageAnalyzeResult,
25
+ IntelligenceImageCaptionPayload,
26
+ IntelligenceImageCaptionResult,
27
+ IntelligenceImageGeneratePayload,
28
+ IntelligenceImageGenerateResult,
29
+ IntelligenceIntentDetectPayload,
30
+ IntelligenceIntentDetectResult,
31
+ IntelligenceInvokeOptions,
32
+ IntelligenceInvokeResult,
33
+ IntelligenceKeywordsExtractPayload,
34
+ IntelligenceKeywordsExtractResult,
35
+ IntelligenceProviderConfig,
36
+ IntelligenceRAGQueryPayload,
37
+ IntelligenceRAGQueryResult,
38
+ IntelligenceRerankPayload,
39
+ IntelligenceRerankResult,
40
+ IntelligenceRewritePayload,
41
+ IntelligenceSemanticSearchPayload,
42
+ IntelligenceSemanticSearchResult,
43
+ IntelligenceSentimentAnalyzePayload,
44
+ IntelligenceSentimentAnalyzeResult,
45
+ IntelligenceSummarizePayload,
46
+ IntelligenceTranslatePayload,
47
+ IntelligenceVisionOcrPayload,
48
+ IntelligenceVisionOcrResult,
10
49
  } from '../../types/intelligence'
11
50
  import { ref } from 'vue'
12
51
  import { useChannel } from './use-channel'
@@ -20,11 +59,11 @@ interface IntelligenceComposable {
20
59
  invoke: <T = any>(
21
60
  capabilityId: string,
22
61
  payload: any,
23
- options?: AiInvokeOptions,
24
- ) => Promise<AiInvokeResult<T>>
62
+ options?: IntelligenceInvokeOptions,
63
+ ) => Promise<IntelligenceInvokeResult<T>>
25
64
 
26
65
  // Provider testing
27
- testProvider: (config: AiProviderConfig) => Promise<{
66
+ testProvider: (config: IntelligenceProviderConfig) => Promise<{
28
67
  success: boolean
29
68
  message: string
30
69
  latency?: number
@@ -50,21 +89,56 @@ interface IntelligenceComposable {
50
89
  inputHint: string
51
90
  }>
52
91
 
53
- // Convenient text methods
92
+ // Text methods
54
93
  text: {
55
- chat: (payload: AiChatPayload, options?: AiInvokeOptions) => Promise<AiInvokeResult<string>>
56
- translate: (payload: { text: string, sourceLang?: string, targetLang: string }, options?: AiInvokeOptions) => Promise<AiInvokeResult<string>>
57
- summarize: (payload: { text: string, maxLength?: number, style?: 'concise' | 'detailed' | 'bullet-points' }, options?: AiInvokeOptions) => Promise<AiInvokeResult<string>>
94
+ chat: (payload: IntelligenceChatPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<string>>
95
+ translate: (payload: IntelligenceTranslatePayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<string>>
96
+ summarize: (payload: IntelligenceSummarizePayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<string>>
97
+ rewrite: (payload: IntelligenceRewritePayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<string>>
98
+ grammarCheck: (payload: IntelligenceGrammarCheckPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceGrammarCheckResult>>
58
99
  }
59
100
 
60
- // Convenient embedding methods
101
+ // Embedding methods
61
102
  embedding: {
62
- generate: (payload: AiEmbeddingPayload, options?: AiInvokeOptions) => Promise<AiInvokeResult<number[]>>
103
+ generate: (payload: IntelligenceEmbeddingPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<number[]>>
63
104
  }
64
105
 
65
- // Convenient vision methods
106
+ // Code methods
107
+ code: {
108
+ generate: (payload: IntelligenceCodeGeneratePayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceCodeGenerateResult>>
109
+ explain: (payload: IntelligenceCodeExplainPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceCodeExplainResult>>
110
+ review: (payload: IntelligenceCodeReviewPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceCodeReviewResult>>
111
+ refactor: (payload: IntelligenceCodeRefactorPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceCodeRefactorResult>>
112
+ debug: (payload: IntelligenceCodeDebugPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceCodeDebugResult>>
113
+ }
114
+
115
+ // Analysis methods
116
+ analysis: {
117
+ detectIntent: (payload: IntelligenceIntentDetectPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceIntentDetectResult>>
118
+ analyzeSentiment: (payload: IntelligenceSentimentAnalyzePayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceSentimentAnalyzeResult>>
119
+ extractContent: (payload: IntelligenceContentExtractPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceContentExtractResult>>
120
+ extractKeywords: (payload: IntelligenceKeywordsExtractPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceKeywordsExtractResult>>
121
+ classify: (payload: IntelligenceClassificationPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceClassificationResult>>
122
+ }
123
+
124
+ // Vision methods
66
125
  vision: {
67
- ocr: (payload: AiVisionOcrPayload, options?: AiInvokeOptions) => Promise<AiInvokeResult<AiVisionOcrResult>>
126
+ ocr: (payload: IntelligenceVisionOcrPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceVisionOcrResult>>
127
+ caption: (payload: IntelligenceImageCaptionPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceImageCaptionResult>>
128
+ analyze: (payload: IntelligenceImageAnalyzePayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceImageAnalyzeResult>>
129
+ generate: (payload: IntelligenceImageGeneratePayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceImageGenerateResult>>
130
+ }
131
+
132
+ // RAG methods
133
+ rag: {
134
+ query: (payload: IntelligenceRAGQueryPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceRAGQueryResult>>
135
+ semanticSearch: (payload: IntelligenceSemanticSearchPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceSemanticSearchResult>>
136
+ rerank: (payload: IntelligenceRerankPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceRerankResult>>
137
+ }
138
+
139
+ // Agent methods
140
+ agent: {
141
+ run: (payload: IntelligenceAgentPayload, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<IntelligenceAgentResult>>
68
142
  }
69
143
 
70
144
  // Loading state
@@ -143,13 +217,13 @@ export function useIntelligence(_options: UseIntelligenceOptions = {}): Intellig
143
217
 
144
218
  return {
145
219
  // Core invoke
146
- invoke: <T = any>(capabilityId: string, payload: any, options?: AiInvokeOptions) =>
220
+ invoke: <T = any>(capabilityId: string, payload: any, options?: IntelligenceInvokeOptions) =>
147
221
  withLoadingState(() =>
148
- sendChannelRequest<AiInvokeResult<T>>('intelligence:invoke', { capabilityId, payload, options }),
222
+ sendChannelRequest<IntelligenceInvokeResult<T>>('intelligence:invoke', { capabilityId, payload, options }),
149
223
  ),
150
224
 
151
225
  // Provider testing
152
- testProvider: (config: AiProviderConfig) =>
226
+ testProvider: (config: IntelligenceProviderConfig) =>
153
227
  withLoadingState(() =>
154
228
  sendChannelRequest<{
155
229
  success: boolean
@@ -175,41 +249,59 @@ export function useIntelligence(_options: UseIntelligenceOptions = {}): Intellig
175
249
  inputHint: string
176
250
  }>('intelligence:get-capability-test-meta', params),
177
251
 
178
- // Convenient text methods
252
+ // Text methods
179
253
  text: {
180
- chat: (payload: AiChatPayload, options?: AiInvokeOptions) =>
254
+ chat: (payload: IntelligenceChatPayload, options?: IntelligenceInvokeOptions) =>
181
255
  withLoadingState(() =>
182
- sendChannelRequest<AiInvokeResult<string>>('intelligence:invoke', {
256
+ sendChannelRequest<IntelligenceInvokeResult<string>>('intelligence:invoke', {
183
257
  capabilityId: 'text.chat',
184
258
  payload,
185
259
  options,
186
260
  }),
187
261
  ),
188
262
 
189
- translate: (payload: { text: string, sourceLang?: string, targetLang: string }, options?: AiInvokeOptions) =>
263
+ translate: (payload: IntelligenceTranslatePayload, options?: IntelligenceInvokeOptions) =>
190
264
  withLoadingState(() =>
191
- sendChannelRequest<AiInvokeResult<string>>('intelligence:invoke', {
265
+ sendChannelRequest<IntelligenceInvokeResult<string>>('intelligence:invoke', {
192
266
  capabilityId: 'text.translate',
193
267
  payload,
194
268
  options,
195
269
  }),
196
270
  ),
197
271
 
198
- summarize: (payload: { text: string, maxLength?: number, style?: 'concise' | 'detailed' | 'bullet-points' }, options?: AiInvokeOptions) =>
272
+ summarize: (payload: IntelligenceSummarizePayload, options?: IntelligenceInvokeOptions) =>
199
273
  withLoadingState(() =>
200
- sendChannelRequest<AiInvokeResult<string>>('intelligence:invoke', {
274
+ sendChannelRequest<IntelligenceInvokeResult<string>>('intelligence:invoke', {
201
275
  capabilityId: 'text.summarize',
202
276
  payload,
203
277
  options,
204
278
  }),
205
279
  ),
280
+
281
+ rewrite: (payload: IntelligenceRewritePayload, options?: IntelligenceInvokeOptions) =>
282
+ withLoadingState(() =>
283
+ sendChannelRequest<IntelligenceInvokeResult<string>>('intelligence:invoke', {
284
+ capabilityId: 'text.rewrite',
285
+ payload,
286
+ options,
287
+ }),
288
+ ),
289
+
290
+ grammarCheck: (payload: IntelligenceGrammarCheckPayload, options?: IntelligenceInvokeOptions) =>
291
+ withLoadingState(() =>
292
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceGrammarCheckResult>>('intelligence:invoke', {
293
+ capabilityId: 'text.grammar',
294
+ payload,
295
+ options,
296
+ }),
297
+ ),
206
298
  },
207
299
 
208
- // Convenient embedding methods
300
+ // Embedding methods
209
301
  embedding: {
210
- generate: (payload: AiEmbeddingPayload, options?: AiInvokeOptions) =>
302
+ generate: (payload: IntelligenceEmbeddingPayload, options?: IntelligenceInvokeOptions) =>
211
303
  withLoadingState(() =>
212
- sendChannelRequest<AiInvokeResult<number[]>>('intelligence:invoke', {
304
+ sendChannelRequest<IntelligenceInvokeResult<number[]>>('intelligence:invoke', {
213
305
  capabilityId: 'embedding.generate',
214
306
  payload,
215
307
  options,
@@ -217,16 +309,181 @@ export function useIntelligence(_options: UseIntelligenceOptions = {}): Intellig
217
309
  ),
218
310
  },
219
311
 
220
- // Convenient vision methods
312
+ // Code methods
313
+ code: {
314
+ generate: (payload: IntelligenceCodeGeneratePayload, options?: IntelligenceInvokeOptions) =>
315
+ withLoadingState(() =>
316
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceCodeGenerateResult>>('intelligence:invoke', {
317
+ capabilityId: 'code.generate',
318
+ payload,
319
+ options,
320
+ }),
321
+ ),
322
+
323
+ explain: (payload: IntelligenceCodeExplainPayload, options?: IntelligenceInvokeOptions) =>
324
+ withLoadingState(() =>
325
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceCodeExplainResult>>('intelligence:invoke', {
326
+ capabilityId: 'code.explain',
327
+ payload,
328
+ options,
329
+ }),
330
+ ),
331
+
332
+ review: (payload: IntelligenceCodeReviewPayload, options?: IntelligenceInvokeOptions) =>
333
+ withLoadingState(() =>
334
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceCodeReviewResult>>('intelligence:invoke', {
335
+ capabilityId: 'code.review',
336
+ payload,
337
+ options,
338
+ }),
339
+ ),
340
+
341
+ refactor: (payload: IntelligenceCodeRefactorPayload, options?: IntelligenceInvokeOptions) =>
342
+ withLoadingState(() =>
343
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceCodeRefactorResult>>('intelligence:invoke', {
344
+ capabilityId: 'code.refactor',
345
+ payload,
346
+ options,
347
+ }),
348
+ ),
349
+
350
+ debug: (payload: IntelligenceCodeDebugPayload, options?: IntelligenceInvokeOptions) =>
351
+ withLoadingState(() =>
352
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceCodeDebugResult>>('intelligence:invoke', {
353
+ capabilityId: 'code.debug',
354
+ payload,
355
+ options,
356
+ }),
357
+ ),
358
+ },
359
+
360
+ // Analysis methods
361
+ analysis: {
362
+ detectIntent: (payload: IntelligenceIntentDetectPayload, options?: IntelligenceInvokeOptions) =>
363
+ withLoadingState(() =>
364
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceIntentDetectResult>>('intelligence:invoke', {
365
+ capabilityId: 'intent.detect',
366
+ payload,
367
+ options,
368
+ }),
369
+ ),
370
+
371
+ analyzeSentiment: (payload: IntelligenceSentimentAnalyzePayload, options?: IntelligenceInvokeOptions) =>
372
+ withLoadingState(() =>
373
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceSentimentAnalyzeResult>>('intelligence:invoke', {
374
+ capabilityId: 'sentiment.analyze',
375
+ payload,
376
+ options,
377
+ }),
378
+ ),
379
+
380
+ extractContent: (payload: IntelligenceContentExtractPayload, options?: IntelligenceInvokeOptions) =>
381
+ withLoadingState(() =>
382
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceContentExtractResult>>('intelligence:invoke', {
383
+ capabilityId: 'content.extract',
384
+ payload,
385
+ options,
386
+ }),
387
+ ),
388
+
389
+ extractKeywords: (payload: IntelligenceKeywordsExtractPayload, options?: IntelligenceInvokeOptions) =>
390
+ withLoadingState(() =>
391
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceKeywordsExtractResult>>('intelligence:invoke', {
392
+ capabilityId: 'keywords.extract',
393
+ payload,
394
+ options,
395
+ }),
396
+ ),
397
+
398
+ classify: (payload: IntelligenceClassificationPayload, options?: IntelligenceInvokeOptions) =>
399
+ withLoadingState(() =>
400
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceClassificationResult>>('intelligence:invoke', {
401
+ capabilityId: 'text.classify',
402
+ payload,
403
+ options,
404
+ }),
405
+ ),
406
+ },
407
+
408
+ // Vision methods
221
409
  vision: {
222
- ocr: (payload: AiVisionOcrPayload, options?: AiInvokeOptions) =>
410
+ ocr: (payload: IntelligenceVisionOcrPayload, options?: IntelligenceInvokeOptions) =>
223
411
  withLoadingState(() =>
224
- sendChannelRequest<AiInvokeResult<AiVisionOcrResult>>('intelligence:invoke', {
412
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceVisionOcrResult>>('intelligence:invoke', {
225
413
  capabilityId: 'vision.ocr',
226
414
  payload,
227
415
  options,
228
416
  }),
229
417
  ),
418
+
419
+ caption: (payload: IntelligenceImageCaptionPayload, options?: IntelligenceInvokeOptions) =>
420
+ withLoadingState(() =>
421
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceImageCaptionResult>>('intelligence:invoke', {
422
+ capabilityId: 'image.caption',
423
+ payload,
424
+ options,
425
+ }),
426
+ ),
427
+
428
+ analyze: (payload: IntelligenceImageAnalyzePayload, options?: IntelligenceInvokeOptions) =>
429
+ withLoadingState(() =>
430
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceImageAnalyzeResult>>('intelligence:invoke', {
431
+ capabilityId: 'image.analyze',
432
+ payload,
433
+ options,
434
+ }),
435
+ ),
436
+
437
+ generate: (payload: IntelligenceImageGeneratePayload, options?: IntelligenceInvokeOptions) =>
438
+ withLoadingState(() =>
439
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceImageGenerateResult>>('intelligence:invoke', {
440
+ capabilityId: 'image.generate',
441
+ payload,
442
+ options,
443
+ }),
444
+ ),
445
+ },
446
+
447
+ // RAG methods
448
+ rag: {
449
+ query: (payload: IntelligenceRAGQueryPayload, options?: IntelligenceInvokeOptions) =>
450
+ withLoadingState(() =>
451
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceRAGQueryResult>>('intelligence:invoke', {
452
+ capabilityId: 'rag.query',
453
+ payload,
454
+ options,
455
+ }),
456
+ ),
457
+
458
+ semanticSearch: (payload: IntelligenceSemanticSearchPayload, options?: IntelligenceInvokeOptions) =>
459
+ withLoadingState(() =>
460
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceSemanticSearchResult>>('intelligence:invoke', {
461
+ capabilityId: 'search.semantic',
462
+ payload,
463
+ options,
464
+ }),
465
+ ),
466
+
467
+ rerank: (payload: IntelligenceRerankPayload, options?: IntelligenceInvokeOptions) =>
468
+ withLoadingState(() =>
469
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceRerankResult>>('intelligence:invoke', {
470
+ capabilityId: 'search.rerank',
471
+ payload,
472
+ options,
473
+ }),
474
+ ),
475
+ },
476
+
477
+ // Agent methods
478
+ agent: {
479
+ run: (payload: IntelligenceAgentPayload, options?: IntelligenceInvokeOptions) =>
480
+ withLoadingState(() =>
481
+ sendChannelRequest<IntelligenceInvokeResult<IntelligenceAgentResult>>('intelligence:invoke', {
482
+ capabilityId: 'agent.run',
483
+ payload,
484
+ options,
485
+ }),
486
+ ),
230
487
  },
231
488
 
232
489
  // Reactive state
@@ -113,6 +113,15 @@ export function createStorageProxy<T extends object>(key: string, factory: () =>
113
113
  })
114
114
  }
115
115
 
116
+ /**
117
+ * Save result from main process
118
+ */
119
+ export interface SaveResult {
120
+ success: boolean
121
+ version: number
122
+ conflict?: boolean
123
+ }
124
+
116
125
  /**
117
126
  * A reactive storage utility with optional auto-save and update subscriptions.
118
127
  *
@@ -127,6 +136,8 @@ export class TouchStorage<T extends object> {
127
136
  private readonly _onUpdate: Array<() => void> = []
128
137
  #channelInitialized = false
129
138
  #skipNextWatchTrigger = false
139
+ #currentVersion = 0
140
+ #isRemoteUpdate = false
130
141
 
131
142
  /**
132
143
  * The reactive data exposed to users.
@@ -189,17 +200,29 @@ export class TouchStorage<T extends object> {
189
200
 
190
201
  this.#channelInitialized = true
191
202
 
192
- const result = channel!.sendSync('storage:get', this.#qualifiedName)
193
- const parsed = result ? (result as Partial<T>) : {}
194
-
195
- this.assignData(parsed)
203
+ // Try to get versioned data first, fallback to legacy
204
+ const versionedResult = channel!.sendSync('storage:get-versioned', this.#qualifiedName) as { data: Partial<T>, version: number } | null
205
+ if (versionedResult) {
206
+ this.#currentVersion = versionedResult.version
207
+ this.assignData(versionedResult.data, true, true)
208
+ }
209
+ else {
210
+ const result = channel!.sendSync('storage:get', this.#qualifiedName)
211
+ const parsed = result ? (result as Partial<T>) : {}
212
+ this.#currentVersion = 1
213
+ this.assignData(parsed, true, true)
214
+ }
196
215
 
197
- // Register update listener
216
+ // Register update listener - only triggered for OTHER windows' changes
217
+ // (source window is excluded by main process)
198
218
  channel!.regChannel('storage:update', ({ data }) => {
199
- const { name } = data!
219
+ const { name, version } = data as { name: string, version?: number }
200
220
 
201
221
  if (name === this.#qualifiedName) {
202
- this.loadFromRemote()
222
+ // Only reload if remote version is newer
223
+ if (version === undefined || version > this.#currentVersion) {
224
+ this.#loadFromRemoteWithVersion()
225
+ }
203
226
  }
204
227
  })
205
228
 
@@ -209,6 +232,24 @@ export class TouchStorage<T extends object> {
209
232
  }
210
233
  }
211
234
 
235
+ /**
236
+ * Load from remote and update version
237
+ * @private
238
+ */
239
+ #loadFromRemoteWithVersion(): void {
240
+ if (!channel)
241
+ return
242
+
243
+ const versionedResult = channel.sendSync('storage:get-versioned', this.#qualifiedName) as { data: Partial<T>, version: number } | null
244
+ if (versionedResult && versionedResult.version > this.#currentVersion) {
245
+ this.#currentVersion = versionedResult.version
246
+ // Mark as remote update to skip auto-save
247
+ this.#isRemoteUpdate = true
248
+ this.assignData(versionedResult.data, true, true)
249
+ this.#isRemoteUpdate = false
250
+ }
251
+ }
252
+
212
253
  /**
213
254
  * Returns the unique identifier of this storage.
214
255
  *
@@ -253,11 +294,26 @@ export class TouchStorage<T extends object> {
253
294
  return
254
295
  }
255
296
 
256
- await channel.send('storage:save', {
297
+ // Skip save if this is a remote update (to avoid echo)
298
+ if (this.#isRemoteUpdate) {
299
+ return
300
+ }
301
+
302
+ const result = await channel.send('storage:save', {
257
303
  key: this.#qualifiedName,
258
304
  content: JSON.stringify(this.data),
259
305
  clear: false,
260
- })
306
+ version: this.#currentVersion,
307
+ }) as SaveResult
308
+
309
+ if (result.success) {
310
+ this.#currentVersion = result.version
311
+ }
312
+ else if (result.conflict) {
313
+ // Conflict detected - reload from remote
314
+ console.warn(`[TouchStorage] Conflict detected for "${this.#qualifiedName}", reloading...`)
315
+ this.#loadFromRemoteWithVersion()
316
+ }
261
317
  }, 300)
262
318
 
263
319
  /**
@@ -364,8 +420,9 @@ export class TouchStorage<T extends object> {
364
420
  *
365
421
  * @param newData Partial update data
366
422
  * @param stopWatch Whether to stop the watcher during assignment
423
+ * @param skipSave Whether to skip saving (for remote updates)
367
424
  */
368
- private assignData(newData: Partial<T>, stopWatch: boolean = true): void {
425
+ private assignData(newData: Partial<T>, stopWatch: boolean = true, skipSave: boolean = false): void {
369
426
  if (stopWatch && this.#autoSave) {
370
427
  this.#assigning = true
371
428
  }
@@ -385,7 +442,10 @@ export class TouchStorage<T extends object> {
385
442
  Promise.resolve().then(resetAssigning)
386
443
  }
387
444
 
388
- this.#runAutoSavePipeline({ force: true })
445
+ // Only run auto-save pipeline if not a remote update
446
+ if (!skipSave && !this.#isRemoteUpdate) {
447
+ this.#runAutoSavePipeline({ force: true })
448
+ }
389
449
  }
390
450
  }
391
451
 
@@ -444,13 +504,36 @@ export class TouchStorage<T extends object> {
444
504
  return this
445
505
  }
446
506
 
447
- const result = channel.sendSync('storage:get', this.#qualifiedName)
448
- const parsed = result ? (result as Partial<T>) : {}
449
- this.assignData(parsed, true)
450
-
507
+ this.#loadFromRemoteWithVersion()
451
508
  return this
452
509
  }
453
510
 
511
+ /**
512
+ * Get current version number
513
+ * @returns Current version
514
+ */
515
+ getVersion(): number {
516
+ return this.#currentVersion
517
+ }
518
+
519
+ /**
520
+ * Save data synchronously (for window close)
521
+ * This bypasses debouncing and saves immediately
522
+ */
523
+ saveSync(): void {
524
+ if (!channel)
525
+ return
526
+ if (this.#isRemoteUpdate)
527
+ return
528
+
529
+ channel.sendSync('storage:save-sync', {
530
+ key: this.#qualifiedName,
531
+ content: JSON.stringify(this.data),
532
+ clear: false,
533
+ version: this.#currentVersion,
534
+ })
535
+ }
536
+
454
537
  /**
455
538
  * Gets the current data state.
456
539
  *
@@ -1,8 +1,8 @@
1
- import type { AiProviderConfig, AISDKGlobalConfig, AISDKStorageData } from '../../types/intelligence'
1
+ import type { IntelligenceProviderConfig, AISDKGlobalConfig, AISDKStorageData } from '../../types/intelligence'
2
2
  import { StorageList } from '../../common/storage/constants'
3
3
  import {
4
4
 
5
- AiProviderType,
5
+ IntelligenceProviderType,
6
6
 
7
7
  DEFAULT_CAPABILITIES,
8
8
  DEFAULT_GLOBAL_CONFIG,
@@ -11,8 +11,8 @@ import {
11
11
  import { createStorageProxy, TouchStorage } from './base-storage'
12
12
 
13
13
  // Re-export types for convenience
14
- export { AiProviderType }
15
- export type { AiProviderConfig, AISDKGlobalConfig }
14
+ export { IntelligenceProviderType }
15
+ export type { IntelligenceProviderConfig, AISDKGlobalConfig }
16
16
 
17
17
  const defaultIntelligenceData: AISDKStorageData = {
18
18
  providers: [...DEFAULT_PROVIDERS],
@@ -32,7 +32,7 @@ class IntelligenceStorage extends TouchStorage<AISDKStorageData> {
32
32
  /**
33
33
  * 添加新的提供商
34
34
  */
35
- addProvider(provider: AiProviderConfig): void {
35
+ addProvider(provider: IntelligenceProviderConfig): void {
36
36
  const currentData = this.get()
37
37
  const updatedProviders = [...currentData.providers, provider]
38
38
  this.set({
@@ -44,7 +44,7 @@ class IntelligenceStorage extends TouchStorage<AISDKStorageData> {
44
44
  /**
45
45
  * 更新提供商配置
46
46
  */
47
- updateProvider(id: string, updatedProvider: Partial<AiProviderConfig>): void {
47
+ updateProvider(id: string, updatedProvider: Partial<IntelligenceProviderConfig>): void {
48
48
  const currentData = this.get()
49
49
  const providerIndex = currentData.providers.findIndex(p => p.id === id)
50
50
 
@@ -93,14 +93,14 @@ class IntelligenceStorage extends TouchStorage<AISDKStorageData> {
93
93
  /**
94
94
  * 获取特定类型的提供商
95
95
  */
96
- getProvidersByType(type: AiProviderType): AiProviderConfig[] {
96
+ getProvidersByType(type: IntelligenceProviderType): IntelligenceProviderConfig[] {
97
97
  return this.get().providers.filter(p => p.type === type)
98
98
  }
99
99
 
100
100
  /**
101
101
  * 获取启用的提供商
102
102
  */
103
- getEnabledProviders(): AiProviderConfig[] {
103
+ getEnabledProviders(): IntelligenceProviderConfig[] {
104
104
  return this.get().providers.filter(p => p.enabled)
105
105
  }
106
106
 
@@ -113,7 +113,7 @@ class IntelligenceStorage extends TouchStorage<AISDKStorageData> {
113
113
  return false
114
114
 
115
115
  // 检查是否有必要的配置项
116
- const hasApiKey = provider.type === AiProviderType.LOCAL || !!provider.apiKey
116
+ const hasApiKey = provider.type === IntelligenceProviderType.LOCAL || !!provider.apiKey
117
117
  const hasModels = !!(provider.models && provider.models.length > 0)
118
118
 
119
119
  return hasApiKey && hasModels