@yh-ui/hooks 0.1.17 → 0.1.21
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/README.md +289 -0
- package/dist/index.cjs +147 -1460
- package/dist/index.d.ts +18 -698
- package/dist/index.mjs +15 -1427
- package/dist/storage/index.cjs +128 -0
- package/dist/storage/index.d.ts +26 -0
- package/dist/storage/index.mjs +112 -0
- package/dist/use-ai/index.cjs +11 -0
- package/dist/use-ai/index.d.ts +1 -0
- package/dist/use-ai/index.mjs +1 -0
- package/dist/use-ai/use-ai-conversations.cjs +73 -6
- package/dist/use-ai/use-ai-conversations.d.ts +34 -4
- package/dist/use-ai/use-ai-conversations.mjs +71 -4
- package/dist/use-ai/use-ai-persistence.cjs +160 -0
- package/dist/use-ai/use-ai-persistence.d.ts +62 -0
- package/dist/use-ai/use-ai-persistence.mjs +154 -0
- package/dist/use-countdown/index.cjs +51 -0
- package/dist/use-countdown/index.d.ts +17 -0
- package/dist/use-countdown/index.mjs +40 -0
- package/dist/use-sku/index.cjs +62 -0
- package/dist/use-sku/index.d.ts +30 -0
- package/dist/use-sku/index.mjs +58 -0
- package/dist/use-virtual-scroll/index.cjs +17 -26
- package/dist/use-virtual-scroll/index.d.ts +5 -9
- package/dist/use-virtual-scroll/index.mjs +18 -23
- package/package.json +6 -4
- package/dist/index.d.cts +0 -699
- package/dist/index.d.mts +0 -699
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.IndexedDBAdapter = exports.APIClient = void 0;
|
|
7
|
+
class IndexedDBAdapter {
|
|
8
|
+
dbName;
|
|
9
|
+
storeName;
|
|
10
|
+
db = null;
|
|
11
|
+
constructor(dbName = "yh-ui-db", storeName = "yh-ui-store") {
|
|
12
|
+
this.dbName = dbName;
|
|
13
|
+
this.storeName = storeName;
|
|
14
|
+
}
|
|
15
|
+
async getDB() {
|
|
16
|
+
if (this.db) return this.db;
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const request = indexedDB.open(this.dbName, 1);
|
|
19
|
+
request.onerror = () => reject(request.error);
|
|
20
|
+
request.onsuccess = () => {
|
|
21
|
+
this.db = request.result;
|
|
22
|
+
resolve(request.result);
|
|
23
|
+
};
|
|
24
|
+
request.onupgradeneeded = event => {
|
|
25
|
+
const db = event.target.result;
|
|
26
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
27
|
+
db.createObjectStore(this.storeName, {
|
|
28
|
+
keyPath: "key"
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async get(key) {
|
|
35
|
+
const db = await this.getDB();
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const transaction = db.transaction(this.storeName, "readonly");
|
|
38
|
+
const store = transaction.objectStore(this.storeName);
|
|
39
|
+
const request = store.get(key);
|
|
40
|
+
request.onerror = () => reject(request.error);
|
|
41
|
+
request.onsuccess = () => resolve(request.result?.value ?? null);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async set(key, value) {
|
|
45
|
+
const db = await this.getDB();
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const transaction = db.transaction(this.storeName, "readwrite");
|
|
48
|
+
const store = transaction.objectStore(this.storeName);
|
|
49
|
+
const request = store.put({
|
|
50
|
+
key,
|
|
51
|
+
value
|
|
52
|
+
});
|
|
53
|
+
request.onerror = () => reject(request.error);
|
|
54
|
+
request.onsuccess = () => resolve();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async remove(key) {
|
|
58
|
+
const db = await this.getDB();
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const transaction = db.transaction(this.storeName, "readwrite");
|
|
61
|
+
const store = transaction.objectStore(this.storeName);
|
|
62
|
+
const request = store.delete(key);
|
|
63
|
+
request.onerror = () => reject(request.error);
|
|
64
|
+
request.onsuccess = () => resolve();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async clear() {
|
|
68
|
+
const db = await this.getDB();
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const transaction = db.transaction(this.storeName, "readwrite");
|
|
71
|
+
const store = transaction.objectStore(this.storeName);
|
|
72
|
+
const request = store.clear();
|
|
73
|
+
request.onerror = () => reject(request.error);
|
|
74
|
+
request.onsuccess = () => resolve();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.IndexedDBAdapter = IndexedDBAdapter;
|
|
79
|
+
class APIClient {
|
|
80
|
+
baseURL;
|
|
81
|
+
headers;
|
|
82
|
+
constructor(baseURL, headers = {}) {
|
|
83
|
+
this.baseURL = baseURL.replace(/\/$/, "");
|
|
84
|
+
this.headers = headers;
|
|
85
|
+
}
|
|
86
|
+
async get(key) {
|
|
87
|
+
const response = await fetch(`${this.baseURL}/storage/${key}`, {
|
|
88
|
+
headers: this.headers
|
|
89
|
+
});
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
if (response.status === 404) return null;
|
|
92
|
+
throw new Error(`HTTP ${response.status}`);
|
|
93
|
+
}
|
|
94
|
+
return response.json();
|
|
95
|
+
}
|
|
96
|
+
async set(key, value) {
|
|
97
|
+
const response = await fetch(`${this.baseURL}/storage/${key}`, {
|
|
98
|
+
method: "PUT",
|
|
99
|
+
headers: {
|
|
100
|
+
...this.headers,
|
|
101
|
+
"Content-Type": "application/json"
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify(value)
|
|
104
|
+
});
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
throw new Error(`HTTP ${response.status}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async remove(key) {
|
|
110
|
+
const response = await fetch(`${this.baseURL}/storage/${key}`, {
|
|
111
|
+
method: "DELETE",
|
|
112
|
+
headers: this.headers
|
|
113
|
+
});
|
|
114
|
+
if (!response.ok && response.status !== 404) {
|
|
115
|
+
throw new Error(`HTTP ${response.status}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async clear() {
|
|
119
|
+
const response = await fetch(`${this.baseURL}/storage`, {
|
|
120
|
+
method: "DELETE",
|
|
121
|
+
headers: this.headers
|
|
122
|
+
});
|
|
123
|
+
if (!response.ok && response.status !== 404) {
|
|
124
|
+
throw new Error(`HTTP ${response.status}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.APIClient = APIClient;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface StorageAdapter {
|
|
2
|
+
get<T>(key: string): Promise<T | null>;
|
|
3
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
4
|
+
remove(key: string): Promise<void>;
|
|
5
|
+
clear(): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export declare class IndexedDBAdapter implements StorageAdapter {
|
|
8
|
+
private dbName;
|
|
9
|
+
private storeName;
|
|
10
|
+
private db;
|
|
11
|
+
constructor(dbName?: string, storeName?: string);
|
|
12
|
+
private getDB;
|
|
13
|
+
get<T>(key: string): Promise<T | null>;
|
|
14
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
15
|
+
remove(key: string): Promise<void>;
|
|
16
|
+
clear(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export declare class APIClient implements StorageAdapter {
|
|
19
|
+
private baseURL;
|
|
20
|
+
private headers;
|
|
21
|
+
constructor(baseURL: string, headers?: Record<string, string>);
|
|
22
|
+
get<T>(key: string): Promise<T | null>;
|
|
23
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
24
|
+
remove(key: string): Promise<void>;
|
|
25
|
+
clear(): Promise<void>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export class IndexedDBAdapter {
|
|
2
|
+
dbName;
|
|
3
|
+
storeName;
|
|
4
|
+
db = null;
|
|
5
|
+
constructor(dbName = "yh-ui-db", storeName = "yh-ui-store") {
|
|
6
|
+
this.dbName = dbName;
|
|
7
|
+
this.storeName = storeName;
|
|
8
|
+
}
|
|
9
|
+
async getDB() {
|
|
10
|
+
if (this.db) return this.db;
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const request = indexedDB.open(this.dbName, 1);
|
|
13
|
+
request.onerror = () => reject(request.error);
|
|
14
|
+
request.onsuccess = () => {
|
|
15
|
+
this.db = request.result;
|
|
16
|
+
resolve(request.result);
|
|
17
|
+
};
|
|
18
|
+
request.onupgradeneeded = (event) => {
|
|
19
|
+
const db = event.target.result;
|
|
20
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
21
|
+
db.createObjectStore(this.storeName, { keyPath: "key" });
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async get(key) {
|
|
27
|
+
const db = await this.getDB();
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const transaction = db.transaction(this.storeName, "readonly");
|
|
30
|
+
const store = transaction.objectStore(this.storeName);
|
|
31
|
+
const request = store.get(key);
|
|
32
|
+
request.onerror = () => reject(request.error);
|
|
33
|
+
request.onsuccess = () => resolve(request.result?.value ?? null);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async set(key, value) {
|
|
37
|
+
const db = await this.getDB();
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const transaction = db.transaction(this.storeName, "readwrite");
|
|
40
|
+
const store = transaction.objectStore(this.storeName);
|
|
41
|
+
const request = store.put({ key, value });
|
|
42
|
+
request.onerror = () => reject(request.error);
|
|
43
|
+
request.onsuccess = () => resolve();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async remove(key) {
|
|
47
|
+
const db = await this.getDB();
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const transaction = db.transaction(this.storeName, "readwrite");
|
|
50
|
+
const store = transaction.objectStore(this.storeName);
|
|
51
|
+
const request = store.delete(key);
|
|
52
|
+
request.onerror = () => reject(request.error);
|
|
53
|
+
request.onsuccess = () => resolve();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async clear() {
|
|
57
|
+
const db = await this.getDB();
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const transaction = db.transaction(this.storeName, "readwrite");
|
|
60
|
+
const store = transaction.objectStore(this.storeName);
|
|
61
|
+
const request = store.clear();
|
|
62
|
+
request.onerror = () => reject(request.error);
|
|
63
|
+
request.onsuccess = () => resolve();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export class APIClient {
|
|
68
|
+
baseURL;
|
|
69
|
+
headers;
|
|
70
|
+
constructor(baseURL, headers = {}) {
|
|
71
|
+
this.baseURL = baseURL.replace(/\/$/, "");
|
|
72
|
+
this.headers = headers;
|
|
73
|
+
}
|
|
74
|
+
async get(key) {
|
|
75
|
+
const response = await fetch(`${this.baseURL}/storage/${key}`, {
|
|
76
|
+
headers: this.headers
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
if (response.status === 404) return null;
|
|
80
|
+
throw new Error(`HTTP ${response.status}`);
|
|
81
|
+
}
|
|
82
|
+
return response.json();
|
|
83
|
+
}
|
|
84
|
+
async set(key, value) {
|
|
85
|
+
const response = await fetch(`${this.baseURL}/storage/${key}`, {
|
|
86
|
+
method: "PUT",
|
|
87
|
+
headers: { ...this.headers, "Content-Type": "application/json" },
|
|
88
|
+
body: JSON.stringify(value)
|
|
89
|
+
});
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new Error(`HTTP ${response.status}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async remove(key) {
|
|
95
|
+
const response = await fetch(`${this.baseURL}/storage/${key}`, {
|
|
96
|
+
method: "DELETE",
|
|
97
|
+
headers: this.headers
|
|
98
|
+
});
|
|
99
|
+
if (!response.ok && response.status !== 404) {
|
|
100
|
+
throw new Error(`HTTP ${response.status}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async clear() {
|
|
104
|
+
const response = await fetch(`${this.baseURL}/storage`, {
|
|
105
|
+
method: "DELETE",
|
|
106
|
+
headers: this.headers
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok && response.status !== 404) {
|
|
109
|
+
throw new Error(`HTTP ${response.status}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
package/dist/use-ai/index.cjs
CHANGED
|
@@ -57,4 +57,15 @@ Object.keys(_useAiVoice).forEach(function (key) {
|
|
|
57
57
|
return _useAiVoice[key];
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
|
+
});
|
|
61
|
+
var _useAiPersistence = require("./use-ai-persistence.cjs");
|
|
62
|
+
Object.keys(_useAiPersistence).forEach(function (key) {
|
|
63
|
+
if (key === "default" || key === "__esModule") return;
|
|
64
|
+
if (key in exports && exports[key] === _useAiPersistence[key]) return;
|
|
65
|
+
Object.defineProperty(exports, key, {
|
|
66
|
+
enumerable: true,
|
|
67
|
+
get: function () {
|
|
68
|
+
return _useAiPersistence[key];
|
|
69
|
+
}
|
|
70
|
+
});
|
|
60
71
|
});
|
package/dist/use-ai/index.d.ts
CHANGED
package/dist/use-ai/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.localStorageAdapter = exports.
|
|
6
|
+
exports.localStorageAdapter = exports.AiIndexedDBAdapter = void 0;
|
|
7
7
|
exports.useAiConversations = useAiConversations;
|
|
8
8
|
var _vue = require("vue");
|
|
9
9
|
const localStorageAdapter = exports.localStorageAdapter = {
|
|
@@ -25,7 +25,7 @@ const localStorageAdapter = exports.localStorageAdapter = {
|
|
|
25
25
|
} catch {}
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
|
-
class
|
|
28
|
+
class AiIndexedDBAdapter {
|
|
29
29
|
db = null;
|
|
30
30
|
dbName;
|
|
31
31
|
storeName = "ai_conversations";
|
|
@@ -80,7 +80,7 @@ class IndexedDBAdapter {
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
exports.
|
|
83
|
+
exports.AiIndexedDBAdapter = AiIndexedDBAdapter;
|
|
84
84
|
function getGroupLabel(updatedAt) {
|
|
85
85
|
const now = Date.now();
|
|
86
86
|
const diff = now - updatedAt;
|
|
@@ -96,13 +96,16 @@ function useAiConversations(options = {}) {
|
|
|
96
96
|
idGenerator = () => Math.random().toString(36).substring(2, 9),
|
|
97
97
|
storage = "localStorage",
|
|
98
98
|
storageKey = "yh-ui-ai-conversations",
|
|
99
|
-
pageSize = 20
|
|
99
|
+
pageSize = 20,
|
|
100
|
+
remoteSync,
|
|
101
|
+
autoSync = false,
|
|
102
|
+
syncInterval = 3e4
|
|
100
103
|
} = options;
|
|
101
104
|
let adapter = null;
|
|
102
105
|
if (storage === "localStorage") {
|
|
103
106
|
adapter = localStorageAdapter;
|
|
104
107
|
} else if (storage === "indexedDB") {
|
|
105
|
-
adapter = new
|
|
108
|
+
adapter = new AiIndexedDBAdapter();
|
|
106
109
|
} else if (storage && typeof storage === "object") {
|
|
107
110
|
adapter = storage;
|
|
108
111
|
}
|
|
@@ -225,6 +228,61 @@ function useAiConversations(options = {}) {
|
|
|
225
228
|
await adapter.removeItem(storageKey);
|
|
226
229
|
}
|
|
227
230
|
};
|
|
231
|
+
const isSyncing = (0, _vue.ref)(false);
|
|
232
|
+
const lastSyncTime = (0, _vue.ref)(0);
|
|
233
|
+
const syncError = (0, _vue.ref)(null);
|
|
234
|
+
const syncToRemote = async () => {
|
|
235
|
+
if (!remoteSync || isSyncing.value) return;
|
|
236
|
+
isSyncing.value = true;
|
|
237
|
+
syncError.value = null;
|
|
238
|
+
try {
|
|
239
|
+
await remoteSync.batchUpdate(conversations.value);
|
|
240
|
+
lastSyncTime.value = Date.now();
|
|
241
|
+
} catch (err) {
|
|
242
|
+
syncError.value = err instanceof Error ? err : new Error(String(err));
|
|
243
|
+
} finally {
|
|
244
|
+
isSyncing.value = false;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
const fetchFromRemote = async () => {
|
|
248
|
+
if (!remoteSync || isSyncing.value) return;
|
|
249
|
+
isSyncing.value = true;
|
|
250
|
+
syncError.value = null;
|
|
251
|
+
try {
|
|
252
|
+
const remoteConversations = await remoteSync.fetchConversations();
|
|
253
|
+
for (const rc of remoteConversations) {
|
|
254
|
+
if (!conversations.value.find(c => c.id === rc.id)) {
|
|
255
|
+
conversations.value.push(rc);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
conversations.value.sort((a, b) => {
|
|
259
|
+
if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;
|
|
260
|
+
return b.updatedAt - a.updatedAt;
|
|
261
|
+
});
|
|
262
|
+
lastSyncTime.value = Date.now();
|
|
263
|
+
await persist();
|
|
264
|
+
} catch (err) {
|
|
265
|
+
syncError.value = err instanceof Error ? err : new Error(String(err));
|
|
266
|
+
} finally {
|
|
267
|
+
isSyncing.value = false;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
let syncTimer = null;
|
|
271
|
+
const startSync = () => {
|
|
272
|
+
if (!remoteSync || syncTimer) return;
|
|
273
|
+
if (syncInterval > 0) {
|
|
274
|
+
syncTimer = setInterval(syncToRemote, syncInterval);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
const stopSync = () => {
|
|
278
|
+
if (syncTimer) {
|
|
279
|
+
clearInterval(syncTimer);
|
|
280
|
+
syncTimer = null;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
if (remoteSync && autoSync) {
|
|
284
|
+
startSync();
|
|
285
|
+
}
|
|
228
286
|
return {
|
|
229
287
|
/** 完整会话列表 */
|
|
230
288
|
conversations,
|
|
@@ -249,6 +307,15 @@ function useAiConversations(options = {}) {
|
|
|
249
307
|
/** 置顶/取消置顶 */
|
|
250
308
|
pinConversation,
|
|
251
309
|
/** 清空全部 */
|
|
252
|
-
clear
|
|
310
|
+
clear,
|
|
311
|
+
/** 远程同步状态 */
|
|
312
|
+
isSyncing,
|
|
313
|
+
lastSyncTime,
|
|
314
|
+
syncError,
|
|
315
|
+
/** 远程同步方法 */
|
|
316
|
+
syncToRemote,
|
|
317
|
+
fetchFromRemote,
|
|
318
|
+
startSync,
|
|
319
|
+
stopSync
|
|
253
320
|
};
|
|
254
321
|
}
|
|
@@ -18,7 +18,7 @@ export interface AiConversation {
|
|
|
18
18
|
*/
|
|
19
19
|
meta?: Record<string, unknown>;
|
|
20
20
|
}
|
|
21
|
-
export interface
|
|
21
|
+
export interface AiStorageAdapter {
|
|
22
22
|
getItem(key: string): string | null | Promise<string | null>;
|
|
23
23
|
setItem(key: string, value: string): void | Promise<void>;
|
|
24
24
|
removeItem(key: string): void | Promise<void>;
|
|
@@ -26,11 +26,11 @@ export interface StorageAdapter {
|
|
|
26
26
|
/**
|
|
27
27
|
* localStorage 适配器(默认)
|
|
28
28
|
*/
|
|
29
|
-
export declare const localStorageAdapter:
|
|
29
|
+
export declare const localStorageAdapter: AiStorageAdapter;
|
|
30
30
|
/**
|
|
31
31
|
* IndexedDB 适配器(适合大量数据持久化)
|
|
32
32
|
*/
|
|
33
|
-
export declare class
|
|
33
|
+
export declare class AiIndexedDBAdapter implements AiStorageAdapter {
|
|
34
34
|
private db;
|
|
35
35
|
private dbName;
|
|
36
36
|
private storeName;
|
|
@@ -45,6 +45,18 @@ export type ConversationGroup = {
|
|
|
45
45
|
label: string;
|
|
46
46
|
items: AiConversation[];
|
|
47
47
|
};
|
|
48
|
+
export interface RemoteSyncAdapter {
|
|
49
|
+
/** 获取所有会话 */
|
|
50
|
+
fetchConversations(): Promise<AiConversation[]>;
|
|
51
|
+
/** 创建会话 */
|
|
52
|
+
createConversation(conversation: AiConversation): Promise<AiConversation>;
|
|
53
|
+
/** 更新会话 */
|
|
54
|
+
updateConversation(id: string, updates: Partial<AiConversation>): Promise<AiConversation>;
|
|
55
|
+
/** 删除会话 */
|
|
56
|
+
deleteConversation(id: string): Promise<void>;
|
|
57
|
+
/** 批量更新(用于同步) */
|
|
58
|
+
batchUpdate(conversations: AiConversation[]): Promise<AiConversation[]>;
|
|
59
|
+
}
|
|
48
60
|
export interface UseAiConversationsOptions {
|
|
49
61
|
/** 初始化数据(或从后端的直出) */
|
|
50
62
|
initialConversations?: AiConversation[];
|
|
@@ -57,7 +69,7 @@ export interface UseAiConversationsOptions {
|
|
|
57
69
|
* - 'indexedDB': 使用 IndexedDB
|
|
58
70
|
* - StorageAdapter: 自定义适配器
|
|
59
71
|
*/
|
|
60
|
-
storage?: false | 'localStorage' | 'indexedDB' |
|
|
72
|
+
storage?: false | 'localStorage' | 'indexedDB' | AiStorageAdapter;
|
|
61
73
|
/** 持久化 key 前缀 */
|
|
62
74
|
storageKey?: string;
|
|
63
75
|
/**
|
|
@@ -65,6 +77,15 @@ export interface UseAiConversationsOptions {
|
|
|
65
77
|
* @default 20
|
|
66
78
|
*/
|
|
67
79
|
pageSize?: number;
|
|
80
|
+
/**
|
|
81
|
+
* 远程同步适配器(用于与后端同步)
|
|
82
|
+
* 设置后会自动进行本地与远程的数据同步
|
|
83
|
+
*/
|
|
84
|
+
remoteSync?: RemoteSyncAdapter;
|
|
85
|
+
/** 是否自动同步(仅在设置了 remoteSync 时有效) */
|
|
86
|
+
autoSync?: boolean;
|
|
87
|
+
/** 同步间隔(毫秒),0 表示实时同步 */
|
|
88
|
+
syncInterval?: number;
|
|
68
89
|
}
|
|
69
90
|
/**
|
|
70
91
|
* useAiConversations - 会话历史管理 Hook
|
|
@@ -145,4 +166,13 @@ export declare function useAiConversations(options?: UseAiConversationsOptions):
|
|
|
145
166
|
pinConversation: (id: string, pinned?: boolean) => Promise<void>;
|
|
146
167
|
/** 清空全部 */
|
|
147
168
|
clear: () => Promise<void>;
|
|
169
|
+
/** 远程同步状态 */
|
|
170
|
+
isSyncing: import("vue").Ref<boolean, boolean>;
|
|
171
|
+
lastSyncTime: import("vue").Ref<number, number>;
|
|
172
|
+
syncError: import("vue").Ref<Error | null, Error | null>;
|
|
173
|
+
/** 远程同步方法 */
|
|
174
|
+
syncToRemote: () => Promise<void>;
|
|
175
|
+
fetchFromRemote: () => Promise<void>;
|
|
176
|
+
startSync: () => void;
|
|
177
|
+
stopSync: () => void;
|
|
148
178
|
};
|
|
@@ -20,7 +20,7 @@ export const localStorageAdapter = {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
|
-
export class
|
|
23
|
+
export class AiIndexedDBAdapter {
|
|
24
24
|
db = null;
|
|
25
25
|
dbName;
|
|
26
26
|
storeName = "ai_conversations";
|
|
@@ -90,13 +90,16 @@ export function useAiConversations(options = {}) {
|
|
|
90
90
|
idGenerator = () => Math.random().toString(36).substring(2, 9),
|
|
91
91
|
storage = "localStorage",
|
|
92
92
|
storageKey = "yh-ui-ai-conversations",
|
|
93
|
-
pageSize = 20
|
|
93
|
+
pageSize = 20,
|
|
94
|
+
remoteSync,
|
|
95
|
+
autoSync = false,
|
|
96
|
+
syncInterval = 3e4
|
|
94
97
|
} = options;
|
|
95
98
|
let adapter = null;
|
|
96
99
|
if (storage === "localStorage") {
|
|
97
100
|
adapter = localStorageAdapter;
|
|
98
101
|
} else if (storage === "indexedDB") {
|
|
99
|
-
adapter = new
|
|
102
|
+
adapter = new AiIndexedDBAdapter();
|
|
100
103
|
} else if (storage && typeof storage === "object") {
|
|
101
104
|
adapter = storage;
|
|
102
105
|
}
|
|
@@ -212,6 +215,61 @@ export function useAiConversations(options = {}) {
|
|
|
212
215
|
await adapter.removeItem(storageKey);
|
|
213
216
|
}
|
|
214
217
|
};
|
|
218
|
+
const isSyncing = ref(false);
|
|
219
|
+
const lastSyncTime = ref(0);
|
|
220
|
+
const syncError = ref(null);
|
|
221
|
+
const syncToRemote = async () => {
|
|
222
|
+
if (!remoteSync || isSyncing.value) return;
|
|
223
|
+
isSyncing.value = true;
|
|
224
|
+
syncError.value = null;
|
|
225
|
+
try {
|
|
226
|
+
await remoteSync.batchUpdate(conversations.value);
|
|
227
|
+
lastSyncTime.value = Date.now();
|
|
228
|
+
} catch (err) {
|
|
229
|
+
syncError.value = err instanceof Error ? err : new Error(String(err));
|
|
230
|
+
} finally {
|
|
231
|
+
isSyncing.value = false;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const fetchFromRemote = async () => {
|
|
235
|
+
if (!remoteSync || isSyncing.value) return;
|
|
236
|
+
isSyncing.value = true;
|
|
237
|
+
syncError.value = null;
|
|
238
|
+
try {
|
|
239
|
+
const remoteConversations = await remoteSync.fetchConversations();
|
|
240
|
+
for (const rc of remoteConversations) {
|
|
241
|
+
if (!conversations.value.find((c) => c.id === rc.id)) {
|
|
242
|
+
conversations.value.push(rc);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
conversations.value.sort((a, b) => {
|
|
246
|
+
if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;
|
|
247
|
+
return b.updatedAt - a.updatedAt;
|
|
248
|
+
});
|
|
249
|
+
lastSyncTime.value = Date.now();
|
|
250
|
+
await persist();
|
|
251
|
+
} catch (err) {
|
|
252
|
+
syncError.value = err instanceof Error ? err : new Error(String(err));
|
|
253
|
+
} finally {
|
|
254
|
+
isSyncing.value = false;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
let syncTimer = null;
|
|
258
|
+
const startSync = () => {
|
|
259
|
+
if (!remoteSync || syncTimer) return;
|
|
260
|
+
if (syncInterval > 0) {
|
|
261
|
+
syncTimer = setInterval(syncToRemote, syncInterval);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
const stopSync = () => {
|
|
265
|
+
if (syncTimer) {
|
|
266
|
+
clearInterval(syncTimer);
|
|
267
|
+
syncTimer = null;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
if (remoteSync && autoSync) {
|
|
271
|
+
startSync();
|
|
272
|
+
}
|
|
215
273
|
return {
|
|
216
274
|
/** 完整会话列表 */
|
|
217
275
|
conversations,
|
|
@@ -236,6 +294,15 @@ export function useAiConversations(options = {}) {
|
|
|
236
294
|
/** 置顶/取消置顶 */
|
|
237
295
|
pinConversation,
|
|
238
296
|
/** 清空全部 */
|
|
239
|
-
clear
|
|
297
|
+
clear,
|
|
298
|
+
/** 远程同步状态 */
|
|
299
|
+
isSyncing,
|
|
300
|
+
lastSyncTime,
|
|
301
|
+
syncError,
|
|
302
|
+
/** 远程同步方法 */
|
|
303
|
+
syncToRemote,
|
|
304
|
+
fetchFromRemote,
|
|
305
|
+
startSync,
|
|
306
|
+
stopSync
|
|
240
307
|
};
|
|
241
308
|
}
|