@langgraph-js/ui 1.4.0 → 1.6.0
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/.env +2 -0
- package/.env.example +2 -0
- package/dist/assets/index-7vem5Peg.css +1 -0
- package/dist/assets/index-CZ6k2dGe.js +235 -0
- package/dist/index.html +3 -5
- package/index.html +1 -3
- package/package.json +10 -2
- package/src/artifacts/ArtifactViewer.tsx +158 -0
- package/src/artifacts/ArtifactsContext.tsx +99 -0
- package/src/artifacts/SourceCodeViewer.tsx +15 -0
- package/src/chat/Chat.tsx +102 -27
- package/src/chat/FileUpload/index.ts +3 -7
- package/src/chat/components/FileList.tsx +16 -12
- package/src/chat/components/HistoryList.tsx +39 -137
- package/src/chat/components/JsonEditorPopup.tsx +57 -45
- package/src/chat/components/JsonToMessage/JsonToMessage.tsx +20 -14
- package/src/chat/components/JsonToMessage/JsonToMessageButton.tsx +9 -16
- package/src/chat/components/JsonToMessage/index.tsx +1 -1
- package/src/chat/components/MessageAI.tsx +5 -4
- package/src/chat/components/MessageBox.tsx +21 -19
- package/src/chat/components/MessageHuman.tsx +13 -10
- package/src/chat/components/MessageTool.tsx +71 -30
- package/src/chat/components/UsageMetadata.tsx +41 -22
- package/src/chat/context/ChatContext.tsx +14 -5
- package/src/chat/store/index.ts +25 -2
- package/src/chat/tools/ask_user_for_approve.tsx +80 -0
- package/src/chat/tools/create_artifacts.tsx +50 -0
- package/src/chat/tools/index.ts +5 -0
- package/src/chat/tools/update_plan.tsx +75 -0
- package/src/chat/tools/web_search_tool.tsx +89 -0
- package/src/graph/index.tsx +9 -6
- package/src/index.ts +1 -0
- package/src/login/Login.tsx +155 -47
- package/src/memory/BaseDB.ts +92 -0
- package/src/memory/db.ts +232 -0
- package/src/memory/fulltext-search.ts +191 -0
- package/src/memory/index.ts +4 -0
- package/src/memory/tools.ts +170 -0
- package/test/main.tsx +2 -2
- package/vite.config.ts +7 -1
- package/dist/assets/index-BWndsYW1.js +0 -214
- package/dist/assets/index-LcgERueJ.css +0 -1
- package/src/chat/chat.css +0 -406
- package/src/chat/components/FileList.css +0 -129
- package/src/chat/components/JsonEditorPopup.css +0 -81
- package/src/chat/components/JsonToMessage/JsonToMessage.css +0 -104
- package/src/chat/tools.ts +0 -33
- package/src/login/Login.css +0 -93
package/src/login/Login.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
|
-
import "./Login.css";
|
|
3
2
|
|
|
4
3
|
interface HeaderConfig {
|
|
5
4
|
key: string;
|
|
@@ -35,56 +34,165 @@ const Login: React.FC = () => {
|
|
|
35
34
|
};
|
|
36
35
|
|
|
37
36
|
return (
|
|
38
|
-
<div className="
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
37
|
+
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 flex items-center justify-center p-4">
|
|
38
|
+
<div className="w-full max-w-4xl bg-white rounded-2xl shadow-xl overflow-hidden">
|
|
39
|
+
<div className="grid md:grid-cols-2">
|
|
40
|
+
{/* 左侧品牌区域 */}
|
|
41
|
+
<div className="hidden md:block bg-gradient-to-br from-blue-600 to-blue-800 p-12 text-white">
|
|
42
|
+
<div className="h-full flex flex-col justify-between">
|
|
43
|
+
<div>
|
|
44
|
+
<h1 className="text-4xl font-bold mb-6">LangGraph UI</h1>
|
|
45
|
+
<p className="text-blue-100 text-lg leading-relaxed">专业的 LangGraph 可视化界面,让您的 AI 应用开发更加高效和直观。</p>
|
|
46
|
+
</div>
|
|
47
|
+
<div className="space-y-4">
|
|
48
|
+
<div className="flex items-center space-x-3">
|
|
49
|
+
<div className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center">
|
|
50
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
51
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
|
|
52
|
+
</svg>
|
|
53
|
+
</div>
|
|
54
|
+
<span>直观的可视化界面</span>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="flex items-center space-x-3">
|
|
57
|
+
<div className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center">
|
|
58
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
59
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
60
|
+
</svg>
|
|
61
|
+
</div>
|
|
62
|
+
<span>高效的开发体验</span>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="flex items-center space-x-3">
|
|
65
|
+
<div className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center">
|
|
66
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
67
|
+
<path
|
|
68
|
+
strokeLinecap="round"
|
|
69
|
+
strokeLinejoin="round"
|
|
70
|
+
strokeWidth="2"
|
|
71
|
+
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
|
72
|
+
/>
|
|
73
|
+
</svg>
|
|
74
|
+
</div>
|
|
75
|
+
<span>安全可靠的配置</span>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
57
78
|
</div>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
required
|
|
66
|
-
/>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{/* 右侧表单区域 */}
|
|
82
|
+
<div className="p-8 md:p-12">
|
|
83
|
+
<div className="md:hidden mb-8">
|
|
84
|
+
<h1 className="text-3xl font-bold text-gray-900">LangGraph UI</h1>
|
|
85
|
+
<p className="text-gray-600 mt-2">专业的 LangGraph 可视化界面</p>
|
|
67
86
|
</div>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
|
|
88
|
+
<form
|
|
89
|
+
onSubmit={(e) => {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
handleLogin();
|
|
92
|
+
}}
|
|
93
|
+
className="space-y-6"
|
|
94
|
+
>
|
|
95
|
+
<div>
|
|
96
|
+
<label htmlFor="api-url" className="block text-sm font-medium text-gray-700 mb-2">
|
|
97
|
+
API URL
|
|
98
|
+
</label>
|
|
99
|
+
<input
|
|
100
|
+
type="text"
|
|
101
|
+
id="api-url"
|
|
102
|
+
value={apiUrl}
|
|
103
|
+
onChange={(e) => setApiUrl(e.target.value)}
|
|
104
|
+
placeholder="例如: http://localhost:8123"
|
|
105
|
+
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div>
|
|
110
|
+
<label className="block text-sm font-medium text-gray-700">请求头配置</label>
|
|
111
|
+
{headers.map((header, index) => (
|
|
112
|
+
<div key={index} className="bg-gray-50 rounded-lg p-4">
|
|
113
|
+
<div className="grid grid-cols-2 gap-4">
|
|
114
|
+
<div>
|
|
115
|
+
<input
|
|
116
|
+
type="text"
|
|
117
|
+
id={`header-key-${index}`}
|
|
118
|
+
value={header.key}
|
|
119
|
+
onChange={(e) => updateHeader(index, "key", e.target.value)}
|
|
120
|
+
placeholder="例如: authorization"
|
|
121
|
+
required
|
|
122
|
+
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
<div>
|
|
126
|
+
<input
|
|
127
|
+
type="text"
|
|
128
|
+
id={`header-value-${index}`}
|
|
129
|
+
value={header.value}
|
|
130
|
+
onChange={(e) => updateHeader(index, "value", e.target.value)}
|
|
131
|
+
placeholder="例如: Bearer token;无则填 1"
|
|
132
|
+
required
|
|
133
|
+
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
|
134
|
+
/>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
{index > 0 && (
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
onClick={() => removeHeader(index)}
|
|
141
|
+
className="text-red-600 hover:text-red-700 text-sm font-medium flex items-center space-x-1 transition-colors"
|
|
142
|
+
>
|
|
143
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
144
|
+
<path
|
|
145
|
+
strokeLinecap="round"
|
|
146
|
+
strokeLinejoin="round"
|
|
147
|
+
strokeWidth="2"
|
|
148
|
+
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
149
|
+
/>
|
|
150
|
+
</svg>
|
|
151
|
+
<span>删除此请求头</span>
|
|
152
|
+
</button>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
))}
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div className="flex items-center space-x-3">
|
|
159
|
+
<input
|
|
160
|
+
type="checkbox"
|
|
161
|
+
id="with-credentials"
|
|
162
|
+
checked={withCredentials}
|
|
163
|
+
onChange={(e) => setWithCredentials(e.target.checked)}
|
|
164
|
+
className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
|
|
165
|
+
/>
|
|
166
|
+
<label htmlFor="with-credentials" className="text-sm text-gray-700">
|
|
167
|
+
启用 withCredentials(跨域请求时发送 Cookie)
|
|
168
|
+
</label>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<div className="flex flex-col sm:flex-row gap-4 pt-4">
|
|
172
|
+
<button
|
|
173
|
+
type="button"
|
|
174
|
+
onClick={addHeader}
|
|
175
|
+
className="flex-1 px-6 py-3 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors font-medium flex items-center justify-center space-x-2"
|
|
176
|
+
>
|
|
177
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
178
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
|
179
|
+
</svg>
|
|
180
|
+
<span>添加请求头</span>
|
|
181
|
+
</button>
|
|
182
|
+
<button
|
|
183
|
+
type="submit"
|
|
184
|
+
className="flex-1 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium flex items-center justify-center space-x-2"
|
|
185
|
+
>
|
|
186
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
187
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
|
|
188
|
+
</svg>
|
|
189
|
+
<span>保存配置</span>
|
|
190
|
+
</button>
|
|
191
|
+
</div>
|
|
192
|
+
</form>
|
|
73
193
|
</div>
|
|
74
|
-
))}
|
|
75
|
-
<div className="with-credentials-option">
|
|
76
|
-
<label>
|
|
77
|
-
<input type="checkbox" checked={withCredentials} onChange={(e) => setWithCredentials(e.target.checked)} />
|
|
78
|
-
启用 withCredentials(跨域请求时发送 Cookie)
|
|
79
|
-
</label>
|
|
80
|
-
</div>
|
|
81
|
-
<div className="button-group">
|
|
82
|
-
<button type="button" onClick={addHeader}>
|
|
83
|
-
添加请求头
|
|
84
|
-
</button>
|
|
85
|
-
<button type="submit">保存配置</button>
|
|
86
194
|
</div>
|
|
87
|
-
</
|
|
195
|
+
</div>
|
|
88
196
|
</div>
|
|
89
197
|
);
|
|
90
198
|
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { IDBPDatabase } from "idb";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 数据库记录的基本接口
|
|
5
|
+
*/
|
|
6
|
+
export interface BaseRecord {
|
|
7
|
+
id?: number;
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 数据库配置的基本接口
|
|
13
|
+
*/
|
|
14
|
+
export interface BaseDBConfig {
|
|
15
|
+
dbName: string;
|
|
16
|
+
dbVersion: number;
|
|
17
|
+
storeName: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 数据库操作的抽象基类
|
|
22
|
+
*/
|
|
23
|
+
export abstract class BaseDB<T extends BaseRecord> {
|
|
24
|
+
protected isInitialized: boolean = false;
|
|
25
|
+
protected config: BaseDBConfig;
|
|
26
|
+
|
|
27
|
+
constructor(config: BaseDBConfig) {
|
|
28
|
+
this.config = config;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 初始化数据库
|
|
33
|
+
*/
|
|
34
|
+
public abstract initialize(): Promise<void>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 添加记录
|
|
38
|
+
*/
|
|
39
|
+
public abstract insert(record: Partial<T>): Promise<number>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 更新记录
|
|
43
|
+
*/
|
|
44
|
+
public abstract update(id: number, record: Partial<T>): Promise<void>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 删除记录
|
|
48
|
+
*/
|
|
49
|
+
public abstract delete(id: number): Promise<void>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 查询记录
|
|
53
|
+
*/
|
|
54
|
+
public abstract query(
|
|
55
|
+
query: string,
|
|
56
|
+
options?: {
|
|
57
|
+
prefix?: boolean;
|
|
58
|
+
fields?: string[];
|
|
59
|
+
limit?: number;
|
|
60
|
+
fuzzy?: number;
|
|
61
|
+
}
|
|
62
|
+
): Promise<T[]>;
|
|
63
|
+
|
|
64
|
+
public abstract get(id: number): Promise<T | undefined>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 获取所有记录
|
|
68
|
+
*/
|
|
69
|
+
public abstract getAll(): Promise<T[]>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 定义向量类型
|
|
73
|
+
export type Vector = number[];
|
|
74
|
+
// 定义数据库记录类型
|
|
75
|
+
export interface MemoryRecord extends BaseRecord {
|
|
76
|
+
vector?: Vector | BigUint64Array;
|
|
77
|
+
text: string;
|
|
78
|
+
/**
|
|
79
|
+
* 记忆存储的路径,与 Linux 文件系统一致
|
|
80
|
+
* @example memory://user_id/path/to/memory
|
|
81
|
+
*/
|
|
82
|
+
path: string;
|
|
83
|
+
/**
|
|
84
|
+
* 引用文档的 path
|
|
85
|
+
*/
|
|
86
|
+
referencePath?: string;
|
|
87
|
+
type: string;
|
|
88
|
+
/**
|
|
89
|
+
* 记忆的标签,用于检索
|
|
90
|
+
*/
|
|
91
|
+
tags: string[];
|
|
92
|
+
}
|
package/src/memory/db.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { openDB, DBSchema, IDBPDatabase } from "idb";
|
|
2
|
+
import { BaseDB, BaseRecord, BaseDBConfig, MemoryRecord, Vector } from "./BaseDB";
|
|
3
|
+
|
|
4
|
+
type BinaryVector = number[];
|
|
5
|
+
|
|
6
|
+
// 定义数据库模式
|
|
7
|
+
interface VecDBSchema extends DBSchema {
|
|
8
|
+
vectors: {
|
|
9
|
+
key: number;
|
|
10
|
+
value: MemoryRecord;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 定义默认模型类型
|
|
15
|
+
const defaultModel = "default";
|
|
16
|
+
|
|
17
|
+
// 向量化文本的抽象基类
|
|
18
|
+
abstract class TextVectorizer {
|
|
19
|
+
protected model: string;
|
|
20
|
+
|
|
21
|
+
constructor(model: string = defaultModel) {
|
|
22
|
+
this.model = model;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
abstract vectorize(text: string): Promise<Vector>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class OpenAIVectorizer extends TextVectorizer {
|
|
29
|
+
private apiKey: string;
|
|
30
|
+
public apiEndpoint: string;
|
|
31
|
+
|
|
32
|
+
constructor(model: string = "text-embedding-ada-002", { apiKey, apiEndpoint }: { apiKey: string; apiEndpoint: string }) {
|
|
33
|
+
super(model);
|
|
34
|
+
this.apiKey = apiKey;
|
|
35
|
+
this.apiEndpoint = apiEndpoint;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async vectorize(text: string): Promise<Vector> {
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch(this.apiEndpoint, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
input: text,
|
|
48
|
+
model: this.model,
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new Error(`OpenAI API error: ${response.statusText}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const data = await response.json();
|
|
57
|
+
return data.data[0].embedding;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw new Error(`Error vectorizing text: ${error}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const cosineSimilarity = (vecA: Vector, vecB: Vector): number => {
|
|
64
|
+
const dotProduct = vecA.reduce((sum: number, val: number, index: number) => sum + val * vecB[index], 0);
|
|
65
|
+
const magnitudeA = Math.sqrt(vecA.reduce((sum: number, val: number) => sum + val * val, 0));
|
|
66
|
+
const magnitudeB = Math.sqrt(vecB.reduce((sum: number, val: number) => sum + val * val, 0));
|
|
67
|
+
return dotProduct / (magnitudeA * magnitudeB);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const binarizeVector = (vector: Vector, threshold: number | null = null): BinaryVector => {
|
|
71
|
+
if (threshold === null) {
|
|
72
|
+
const sorted = [...vector].sort((a, b) => a - b);
|
|
73
|
+
const mid = Math.floor(sorted.length / 2);
|
|
74
|
+
threshold = sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
75
|
+
}
|
|
76
|
+
return vector.map((val: number) => (val >= threshold! ? 1 : 0));
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
interface VecDBConfig extends BaseDBConfig {
|
|
80
|
+
vectorizer: TextVectorizer;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface QueryOptions {
|
|
84
|
+
limit?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class VecDB extends BaseDB<MemoryRecord> {
|
|
88
|
+
private vectorizer: TextVectorizer;
|
|
89
|
+
protected db!: IDBPDatabase<VecDBSchema>;
|
|
90
|
+
constructor(config: VecDBConfig) {
|
|
91
|
+
super(config);
|
|
92
|
+
this.vectorizer = config.vectorizer;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public async initialize(): Promise<void> {
|
|
96
|
+
if (this.isInitialized) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.db = await openDB<VecDBSchema>(this.config.dbName, this.config.dbVersion, {
|
|
101
|
+
upgrade(db) {
|
|
102
|
+
if (!db.objectStoreNames.contains("vectors")) {
|
|
103
|
+
db.createObjectStore("vectors", {
|
|
104
|
+
keyPath: "id",
|
|
105
|
+
autoIncrement: true,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
this.isInitialized = true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public async insert(data: MemoryRecord): Promise<number> {
|
|
114
|
+
if (!this.isInitialized) {
|
|
115
|
+
await this.initialize();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
let embedding = data.vector;
|
|
120
|
+
if (!embedding) {
|
|
121
|
+
embedding = await this.vectorizer.vectorize(data.text!);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const transaction = this.db.transaction("vectors", "readwrite");
|
|
125
|
+
const store = transaction.objectStore("vectors");
|
|
126
|
+
const { vector: _, ...rest } = data;
|
|
127
|
+
const record = { vector: embedding, ...rest };
|
|
128
|
+
const key = await store.add(record);
|
|
129
|
+
return Number(key);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
throw new Error(`Error inserting data: ${error}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public async update(key: number, data: MemoryRecord): Promise<void> {
|
|
136
|
+
if (!this.isInitialized) {
|
|
137
|
+
await this.initialize();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const transaction = this.db.transaction("vectors", "readwrite");
|
|
141
|
+
const store = transaction.objectStore("vectors");
|
|
142
|
+
const vector = data["vector"] as Vector;
|
|
143
|
+
const updatedData = { ...data, id: key, vector };
|
|
144
|
+
await store.put(updatedData);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public async delete(key: number): Promise<void> {
|
|
148
|
+
if (!this.isInitialized) {
|
|
149
|
+
await this.initialize();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const transaction = this.db.transaction("vectors", "readwrite");
|
|
153
|
+
const store = transaction.objectStore("vectors");
|
|
154
|
+
await store.delete(key);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public async query(queryText: string, { limit = 10 }: QueryOptions = {}): Promise<Array<MemoryRecord & { similarity: number }>> {
|
|
158
|
+
if (!this.isInitialized) {
|
|
159
|
+
await this.initialize();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const queryVector = await this.vectorizer.vectorize(queryText);
|
|
164
|
+
const transaction = this.db.transaction("vectors", "readonly");
|
|
165
|
+
const store = transaction.objectStore("vectors");
|
|
166
|
+
const vectors = await store.getAll();
|
|
167
|
+
|
|
168
|
+
const similarities = vectors.map((entry) => {
|
|
169
|
+
const vector = entry.vector as Vector;
|
|
170
|
+
const similarity = cosineSimilarity(queryVector, vector);
|
|
171
|
+
return { ...entry, similarity };
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
similarities.sort((a, b) => b.similarity - a.similarity);
|
|
175
|
+
return similarities.slice(0, limit);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
throw new Error(`Error querying vectors: ${error}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public async get(id: number): Promise<MemoryRecord | undefined> {
|
|
182
|
+
if (!this.isInitialized) {
|
|
183
|
+
await this.initialize();
|
|
184
|
+
}
|
|
185
|
+
return this.db.transaction("vectors", "readonly").objectStore("vectors").get(id);
|
|
186
|
+
}
|
|
187
|
+
public async getAll(): Promise<MemoryRecord[]> {
|
|
188
|
+
if (!this.isInitialized) {
|
|
189
|
+
await this.initialize();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const transaction = this.db.transaction("vectors", "readonly");
|
|
193
|
+
const store = transaction.objectStore("vectors");
|
|
194
|
+
return store.getAll();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 保留原有的 insertBinary 方法作为特殊方法
|
|
198
|
+
async insertBinary(data: MemoryRecord): Promise<number> {
|
|
199
|
+
if (!this.isInitialized) {
|
|
200
|
+
await this.initialize();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
let embedding = data["vector"] as Vector;
|
|
205
|
+
if (data.text) {
|
|
206
|
+
embedding = await this.vectorizer.vectorize(data.text);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const binaryEmbedding = binarizeVector(embedding);
|
|
210
|
+
const packedEmbedding = new BigUint64Array(new ArrayBuffer(Math.ceil(binaryEmbedding.length / 64) * 8));
|
|
211
|
+
for (let i = 0; i < binaryEmbedding.length; i++) {
|
|
212
|
+
const bitIndex = i % 64;
|
|
213
|
+
const arrayIndex = Math.floor(i / 64);
|
|
214
|
+
if (binaryEmbedding[i] === 1) {
|
|
215
|
+
packedEmbedding[arrayIndex] |= 1n << BigInt(bitIndex);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const transaction = this.db.transaction("vectors", "readwrite");
|
|
220
|
+
const store = transaction.objectStore("vectors");
|
|
221
|
+
const { vector: _, ...rest } = data;
|
|
222
|
+
const record = { vector: packedEmbedding, ...rest };
|
|
223
|
+
const key = await store.add(record);
|
|
224
|
+
return Number(key);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
throw new Error(`Error inserting binary data: ${error}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export { VecDB, TextVectorizer, OpenAIVectorizer };
|
|
232
|
+
export type { Vector, BinaryVector, MemoryRecord, VecDBConfig };
|