@smart-cloud/ai-kit-ui 1.1.25 → 1.1.28
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/dist/ai-kit-ui.css +9 -0
- package/dist/index.cjs +9 -9
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -9
- package/package.json +2 -2
- package/src/ai-chatbot/AiChatbot.tsx +518 -120
- package/src/ai-chatbot/attachmentStorage.ts +172 -0
- package/src/styles/ai-kit-ui.css +9 -0
- package/src/useAiRun.ts +15 -4
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const DB_NAME = "ai-kit-chatbot-attachments";
|
|
2
|
+
const STORE_NAME = "attachments";
|
|
3
|
+
const DB_VERSION = 1;
|
|
4
|
+
|
|
5
|
+
export type StoredAttachment = {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
type: string;
|
|
9
|
+
size: number;
|
|
10
|
+
blob: Blob;
|
|
11
|
+
createdAt: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type AttachmentRecord = StoredAttachment;
|
|
15
|
+
|
|
16
|
+
let dbPromise: Promise<IDBDatabase | null> | null = null;
|
|
17
|
+
|
|
18
|
+
function isBrowserEnvironment(): boolean {
|
|
19
|
+
return (
|
|
20
|
+
typeof window !== "undefined" && typeof window.indexedDB !== "undefined"
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function getDatabase(): Promise<IDBDatabase | null> {
|
|
25
|
+
if (!isBrowserEnvironment()) return null;
|
|
26
|
+
|
|
27
|
+
if (!dbPromise) {
|
|
28
|
+
dbPromise = new Promise((resolve) => {
|
|
29
|
+
try {
|
|
30
|
+
const request = window.indexedDB.open(DB_NAME, DB_VERSION);
|
|
31
|
+
|
|
32
|
+
request.onerror = () => resolve(null);
|
|
33
|
+
request.onblocked = () => resolve(null);
|
|
34
|
+
request.onupgradeneeded = () => {
|
|
35
|
+
const db = request.result;
|
|
36
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
37
|
+
db.createObjectStore(STORE_NAME, { keyPath: "id" });
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
request.onsuccess = () => resolve(request.result);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.warn("[AiChatbot] IndexedDB is not available", error);
|
|
43
|
+
resolve(null);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const db = await dbPromise;
|
|
50
|
+
return db;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.warn("[AiChatbot] Failed to open attachment store", error);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function runTransaction<T>(
|
|
58
|
+
mode: IDBTransactionMode,
|
|
59
|
+
handler: (store: IDBObjectStore) => T,
|
|
60
|
+
): Promise<T | null> {
|
|
61
|
+
const db = await getDatabase();
|
|
62
|
+
if (!db) return null;
|
|
63
|
+
|
|
64
|
+
return new Promise<T | null>((resolve, reject) => {
|
|
65
|
+
try {
|
|
66
|
+
const tx = db.transaction(STORE_NAME, mode);
|
|
67
|
+
const store = tx.objectStore(STORE_NAME);
|
|
68
|
+
const result = handler(store);
|
|
69
|
+
tx.oncomplete = () => resolve(result);
|
|
70
|
+
tx.onerror = () => reject(tx.error);
|
|
71
|
+
tx.onabort = () => reject(tx.error);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
reject(error);
|
|
74
|
+
}
|
|
75
|
+
}).catch((error) => {
|
|
76
|
+
console.warn("[AiChatbot] Attachment store transaction failed", error);
|
|
77
|
+
return null;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function persistAttachmentBlob(
|
|
82
|
+
id: string,
|
|
83
|
+
blob: Blob,
|
|
84
|
+
meta: { name: string; type: string; size: number },
|
|
85
|
+
): Promise<string | null> {
|
|
86
|
+
const record: AttachmentRecord = {
|
|
87
|
+
id,
|
|
88
|
+
blob,
|
|
89
|
+
name: meta.name,
|
|
90
|
+
type: meta.type,
|
|
91
|
+
size: meta.size,
|
|
92
|
+
createdAt: Date.now(),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const result = await runTransaction("readwrite", (store) => {
|
|
96
|
+
store.put(record);
|
|
97
|
+
return id;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function loadAttachmentBlob(
|
|
104
|
+
id: string,
|
|
105
|
+
): Promise<StoredAttachment | null> {
|
|
106
|
+
const db = await getDatabase();
|
|
107
|
+
if (!db) return null;
|
|
108
|
+
|
|
109
|
+
return new Promise<StoredAttachment | null>((resolve, reject) => {
|
|
110
|
+
try {
|
|
111
|
+
const tx = db.transaction(STORE_NAME, "readonly");
|
|
112
|
+
const request = tx.objectStore(STORE_NAME).get(id);
|
|
113
|
+
request.onsuccess = () => {
|
|
114
|
+
const value = request.result as AttachmentRecord | undefined;
|
|
115
|
+
resolve(value ?? null);
|
|
116
|
+
};
|
|
117
|
+
request.onerror = () => reject(request.error);
|
|
118
|
+
tx.onabort = () => reject(tx.error);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
reject(error);
|
|
121
|
+
}
|
|
122
|
+
}).catch((error) => {
|
|
123
|
+
console.warn("[AiChatbot] Failed to load attachment", error);
|
|
124
|
+
return null;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function deleteAttachmentBlob(id: string): Promise<void> {
|
|
129
|
+
await runTransaction("readwrite", (store) => {
|
|
130
|
+
store.delete(id);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function clearAllAttachments(): Promise<void> {
|
|
135
|
+
await runTransaction("readwrite", (store) => {
|
|
136
|
+
store.clear();
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function cleanupDanglingAttachments(
|
|
141
|
+
validIds: Set<string>,
|
|
142
|
+
): Promise<void> {
|
|
143
|
+
const db = await getDatabase();
|
|
144
|
+
if (!db) return;
|
|
145
|
+
|
|
146
|
+
await new Promise<void>((resolve, reject) => {
|
|
147
|
+
try {
|
|
148
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
149
|
+
const store = tx.objectStore(STORE_NAME);
|
|
150
|
+
const request = store.getAllKeys();
|
|
151
|
+
|
|
152
|
+
request.onsuccess = () => {
|
|
153
|
+
const keys = request.result as IDBValidKey[];
|
|
154
|
+
for (const key of keys) {
|
|
155
|
+
const keyString = String(key);
|
|
156
|
+
if (!validIds.has(keyString)) {
|
|
157
|
+
store.delete(key);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
request.onerror = () => reject(request.error);
|
|
163
|
+
tx.oncomplete = () => resolve();
|
|
164
|
+
tx.onerror = () => reject(tx.error);
|
|
165
|
+
tx.onabort = () => reject(tx.error);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
reject(error);
|
|
168
|
+
}
|
|
169
|
+
}).catch((error) => {
|
|
170
|
+
console.warn("[AiChatbot] Failed to cleanup attachments", error);
|
|
171
|
+
});
|
|
172
|
+
}
|
package/src/styles/ai-kit-ui.css
CHANGED
|
@@ -763,6 +763,15 @@
|
|
|
763
763
|
border: 1px solid var(--ai-kit-color-border);
|
|
764
764
|
}
|
|
765
765
|
|
|
766
|
+
.ai-chat-scroll.ai-thumbs {
|
|
767
|
+
gap: var(--ai-kit-space-1);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.ai-chat-scroll .ai-thumbs .thumb {
|
|
771
|
+
width: 40px;
|
|
772
|
+
height: 40px;
|
|
773
|
+
}
|
|
774
|
+
|
|
766
775
|
.ai-thumbs img {
|
|
767
776
|
width: 100%;
|
|
768
777
|
height: 100%;
|
package/src/useAiRun.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ApiError } from "@aws-amplify/api";
|
|
1
2
|
import {
|
|
2
3
|
AiKitLanguageCode,
|
|
3
4
|
AiKitPlugin,
|
|
@@ -34,8 +35,19 @@ export type UseAiRunResult<T> = AiRunState<T> & {
|
|
|
34
35
|
};
|
|
35
36
|
|
|
36
37
|
function getErrorMessage(err: unknown): string {
|
|
37
|
-
if (err
|
|
38
|
-
|
|
38
|
+
if ((err as ApiError)?.response?.body) {
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse((err as ApiError)!.response!.body!).message;
|
|
41
|
+
} catch {
|
|
42
|
+
/* empty */
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (err instanceof Error) {
|
|
46
|
+
return err.message;
|
|
47
|
+
}
|
|
48
|
+
if (typeof err === "string") {
|
|
49
|
+
return err;
|
|
50
|
+
}
|
|
39
51
|
try {
|
|
40
52
|
return JSON.stringify(err);
|
|
41
53
|
} catch {
|
|
@@ -149,8 +161,7 @@ export function useAiRun<T>(): UseAiRunResult<T> {
|
|
|
149
161
|
setError(null);
|
|
150
162
|
return null;
|
|
151
163
|
}
|
|
152
|
-
|
|
153
|
-
return null;
|
|
164
|
+
throw new Error(getErrorMessage(err));
|
|
154
165
|
} finally {
|
|
155
166
|
setBusy(false);
|
|
156
167
|
setStatusEvent(null);
|