xiaozuoassistant 0.1.53 → 0.1.54
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/config.json +1 -0
- package/dist/client/assets/{browser-ponyfill-CdFOpSMG.js → browser-ponyfill-Dd_NZzSH.js} +1 -1
- package/dist/client/assets/index-DJdLRlkB.css +1 -0
- package/dist/client/assets/index-DKrBHrNq.js +163 -0
- package/dist/client/index.html +2 -2
- package/dist/client/locales/en/translation.json +9 -0
- package/dist/client/locales/zh/translation.json +9 -0
- package/dist/server/config/loader.js +3 -0
- package/dist/server/core/memories/manager.js +24 -6
- package/dist/server/core/memories/structured.js +73 -10
- package/dist/server/index.js +25 -1
- package/package.json +1 -1
- package/public/locales/en/translation.json +9 -0
- package/public/locales/zh/translation.json +9 -0
- package/dist/client/assets/index-ZHPWrPl3.css +0 -1
- package/dist/client/assets/index-hkDzKLlN.js +0 -163
package/dist/client/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🍇</text></svg>" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>xiaozuoAssistant</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DKrBHrNq.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DJdLRlkB.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
|
@@ -53,6 +53,15 @@
|
|
|
53
53
|
"modelName": "Model Name",
|
|
54
54
|
"temperature": "Temperature"
|
|
55
55
|
},
|
|
56
|
+
"identity": {
|
|
57
|
+
"title": "User identity",
|
|
58
|
+
"userId": "UserId",
|
|
59
|
+
"userIdHint": "Used to load identity profile and memories for this userId.",
|
|
60
|
+
"name": "Name",
|
|
61
|
+
"role": "Role",
|
|
62
|
+
"company": "Company",
|
|
63
|
+
"email": "Email"
|
|
64
|
+
},
|
|
56
65
|
"prompt": {
|
|
57
66
|
"description": "Define how xiaozuoAssistant behaves and responds.",
|
|
58
67
|
"placeholder": "You are a helpful AI assistant..."
|
|
@@ -53,6 +53,15 @@
|
|
|
53
53
|
"modelName": "模型名称",
|
|
54
54
|
"temperature": "温度 (Temperature)"
|
|
55
55
|
},
|
|
56
|
+
"identity": {
|
|
57
|
+
"title": "用户身份",
|
|
58
|
+
"userId": "UserId",
|
|
59
|
+
"userIdHint": "用于加载该 UserId 对应的身份资料与记忆。",
|
|
60
|
+
"name": "姓名",
|
|
61
|
+
"role": "岗位",
|
|
62
|
+
"company": "公司",
|
|
63
|
+
"email": "邮箱"
|
|
64
|
+
},
|
|
56
65
|
"prompt": {
|
|
57
66
|
"description": "定义 xiaozuoAssistant 的行为和响应方式。",
|
|
58
67
|
"placeholder": "你是一个有用的 AI 助手..."
|
|
@@ -15,6 +15,8 @@ try {
|
|
|
15
15
|
loadedConfig.scheduler.sessionRetentionDays = 5;
|
|
16
16
|
if (!loadedConfig.workspace)
|
|
17
17
|
loadedConfig.workspace = process.cwd();
|
|
18
|
+
if (!loadedConfig.userId)
|
|
19
|
+
loadedConfig.userId = 'default';
|
|
18
20
|
// Ensure LLM config exists
|
|
19
21
|
if (!loadedConfig.llm) {
|
|
20
22
|
loadedConfig.llm = {
|
|
@@ -49,6 +51,7 @@ catch (error) {
|
|
|
49
51
|
console.warn('Failed to load config.json, creating a new one with defaults');
|
|
50
52
|
loadedConfig = {
|
|
51
53
|
server: { port: 3001, host: 'localhost' },
|
|
54
|
+
userId: 'default',
|
|
52
55
|
llm: {
|
|
53
56
|
provider: 'qwen',
|
|
54
57
|
apiKey: '',
|
|
@@ -2,6 +2,7 @@ import { ShortTermMemory } from './short-term.js';
|
|
|
2
2
|
import { VectorMemory } from './vector.js';
|
|
3
3
|
import { StructuredMemory } from './structured.js';
|
|
4
4
|
import { brain } from '../brain.js';
|
|
5
|
+
import { config } from '../../config/loader.js';
|
|
5
6
|
export class MemoryManager {
|
|
6
7
|
constructor() {
|
|
7
8
|
this.shortTerm = ShortTermMemory.getInstance();
|
|
@@ -40,7 +41,8 @@ export class MemoryManager {
|
|
|
40
41
|
// For now, we only embed user messages or significant assistant responses
|
|
41
42
|
// to avoid cluttering.
|
|
42
43
|
if (message.content.length > 10) { // Simple filter
|
|
43
|
-
|
|
44
|
+
const userId = config.userId || 'default';
|
|
45
|
+
this.vector.addMemory(message.content, 'recent', { sessionId, role: message.role, userId })
|
|
44
46
|
.catch(err => console.error('Background vector add failed:', err));
|
|
45
47
|
}
|
|
46
48
|
// 3. Extract Facts (Structured) - This usually requires an LLM call to extract
|
|
@@ -60,18 +62,20 @@ export class MemoryManager {
|
|
|
60
62
|
return this.shortTerm.getMessages(sessionId);
|
|
61
63
|
}
|
|
62
64
|
// --- Retrieval ---
|
|
63
|
-
async getRelevantContext(query, sessionId) {
|
|
65
|
+
async getRelevantContext(query, sessionId, userId) {
|
|
66
|
+
const uid = userId || config.userId || 'default';
|
|
64
67
|
// 1. Get Short-term context (recent messages)
|
|
65
68
|
const history = await this.getHistory(sessionId);
|
|
66
69
|
const recentMessages = history.slice(-10).map(m => `${m.role}: ${m.content}`).join('\n');
|
|
67
70
|
// 2. Search Vector Memory (Recent & Long-term)
|
|
68
|
-
const vectorResults = await this.vector.search(query, undefined,
|
|
69
|
-
const
|
|
71
|
+
const vectorResults = await this.vector.search(query, undefined, 10);
|
|
72
|
+
const vectorFiltered = vectorResults.filter(r => (r.metadata?.userId || 'default') === uid).slice(0, 3);
|
|
73
|
+
const vectorContext = vectorFiltered.map(r => `[Memory]: ${r.text}`).join('\n');
|
|
70
74
|
// 3. Get User Profile (Structured)
|
|
71
|
-
const profile = await this.structured.getUserProfile();
|
|
75
|
+
const profile = await this.structured.getUserProfile(uid);
|
|
72
76
|
const profileContext = Object.entries(profile).map(([k, v]) => `[User Info] ${k}: ${v}`).join('\n');
|
|
73
77
|
return `
|
|
74
|
-
=== User
|
|
78
|
+
=== User Identity (userId: ${uid}) ===
|
|
75
79
|
${profileContext}
|
|
76
80
|
|
|
77
81
|
=== Relevant Memories ===
|
|
@@ -81,6 +85,20 @@ ${vectorContext}
|
|
|
81
85
|
${recentMessages}
|
|
82
86
|
`;
|
|
83
87
|
}
|
|
88
|
+
getUserProfile(userId) {
|
|
89
|
+
const uid = userId || config.userId || 'default';
|
|
90
|
+
return this.structured.getUserProfile(uid);
|
|
91
|
+
}
|
|
92
|
+
updateUserProfile(userId, patch) {
|
|
93
|
+
const uid = userId || config.userId || 'default';
|
|
94
|
+
for (const [k, v] of Object.entries(patch || {})) {
|
|
95
|
+
const key = String(k).trim();
|
|
96
|
+
if (!key)
|
|
97
|
+
continue;
|
|
98
|
+
const value = v === null || v === undefined ? '' : String(v);
|
|
99
|
+
this.structured.updateUserProfile(uid, key, value);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
84
102
|
// --- Maintenance ---
|
|
85
103
|
// Managed by Scheduler
|
|
86
104
|
async runMaintenance(input) {
|
|
@@ -28,15 +28,18 @@ export class StructuredMemory {
|
|
|
28
28
|
timestamp INTEGER
|
|
29
29
|
)
|
|
30
30
|
`).run();
|
|
31
|
-
// User Profile: specialized facts
|
|
31
|
+
// User Profile: specialized facts (multi-user)
|
|
32
32
|
this.db.prepare(`
|
|
33
33
|
CREATE TABLE IF NOT EXISTS user_profile (
|
|
34
34
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
35
|
-
|
|
35
|
+
user_id TEXT NOT NULL,
|
|
36
|
+
key TEXT NOT NULL,
|
|
36
37
|
value TEXT NOT NULL,
|
|
37
|
-
updated_at INTEGER
|
|
38
|
+
updated_at INTEGER,
|
|
39
|
+
UNIQUE(user_id, key)
|
|
38
40
|
)
|
|
39
41
|
`).run();
|
|
42
|
+
this.migrateUserProfileIfNeeded();
|
|
40
43
|
// Graph nodes (Entities)
|
|
41
44
|
this.db.prepare(`
|
|
42
45
|
CREATE TABLE IF NOT EXISTS entities (
|
|
@@ -59,6 +62,66 @@ export class StructuredMemory {
|
|
|
59
62
|
)
|
|
60
63
|
`).run();
|
|
61
64
|
}
|
|
65
|
+
migrateUserProfileIfNeeded() {
|
|
66
|
+
try {
|
|
67
|
+
const columns = this.db.prepare(`PRAGMA table_info(user_profile)`).all();
|
|
68
|
+
const hasUserId = columns.some(c => c?.name === 'user_id');
|
|
69
|
+
let hasUniqueUserIdKey = false;
|
|
70
|
+
try {
|
|
71
|
+
const indexes = this.db.prepare('PRAGMA index_list(user_profile)').all();
|
|
72
|
+
for (const idx of indexes) {
|
|
73
|
+
if (!idx?.unique)
|
|
74
|
+
continue;
|
|
75
|
+
const cols = this.db.prepare(`PRAGMA index_info(${idx.name})`).all();
|
|
76
|
+
const names = cols.map(c => c?.name).filter(Boolean);
|
|
77
|
+
if (names.length === 2 && names.includes('user_id') && names.includes('key')) {
|
|
78
|
+
hasUniqueUserIdKey = true;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
hasUniqueUserIdKey = false;
|
|
85
|
+
}
|
|
86
|
+
if (hasUserId && hasUniqueUserIdKey)
|
|
87
|
+
return;
|
|
88
|
+
this.db.prepare(`
|
|
89
|
+
CREATE TABLE IF NOT EXISTS user_profile_v2 (
|
|
90
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
91
|
+
user_id TEXT NOT NULL,
|
|
92
|
+
key TEXT NOT NULL,
|
|
93
|
+
value TEXT NOT NULL,
|
|
94
|
+
updated_at INTEGER,
|
|
95
|
+
UNIQUE(user_id, key)
|
|
96
|
+
)
|
|
97
|
+
`).run();
|
|
98
|
+
if (!hasUserId) {
|
|
99
|
+
const rows = this.db.prepare('SELECT key, value, updated_at FROM user_profile').all();
|
|
100
|
+
const ins = this.db.prepare('INSERT OR REPLACE INTO user_profile_v2 (user_id, key, value, updated_at) VALUES (?, ?, ?, ?)');
|
|
101
|
+
const tx = this.db.transaction(() => {
|
|
102
|
+
for (const row of rows) {
|
|
103
|
+
ins.run('default', row.key, row.value, row.updated_at ?? Date.now());
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
tx();
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const rows = this.db.prepare('SELECT user_id, key, value, updated_at FROM user_profile').all();
|
|
110
|
+
const ins = this.db.prepare('INSERT OR REPLACE INTO user_profile_v2 (user_id, key, value, updated_at) VALUES (?, ?, ?, ?)');
|
|
111
|
+
const tx = this.db.transaction(() => {
|
|
112
|
+
for (const row of rows) {
|
|
113
|
+
ins.run(row.user_id, row.key, row.value, row.updated_at ?? Date.now());
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
tx();
|
|
117
|
+
}
|
|
118
|
+
this.db.prepare('DROP TABLE IF EXISTS user_profile').run();
|
|
119
|
+
this.db.prepare('ALTER TABLE user_profile_v2 RENAME TO user_profile').run();
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// ignore
|
|
123
|
+
}
|
|
124
|
+
}
|
|
62
125
|
addFact(category, key, value, source = 'user') {
|
|
63
126
|
const stmt = this.db.prepare(`
|
|
64
127
|
INSERT INTO facts (category, key, value, source, timestamp)
|
|
@@ -72,16 +135,16 @@ export class StructuredMemory {
|
|
|
72
135
|
}
|
|
73
136
|
return this.db.prepare('SELECT * FROM facts').all();
|
|
74
137
|
}
|
|
75
|
-
updateUserProfile(key, value) {
|
|
138
|
+
updateUserProfile(userId, key, value) {
|
|
76
139
|
const stmt = this.db.prepare(`
|
|
77
|
-
INSERT INTO user_profile (key, value, updated_at)
|
|
78
|
-
VALUES (?, ?, ?)
|
|
79
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
|
|
140
|
+
INSERT INTO user_profile (user_id, key, value, updated_at)
|
|
141
|
+
VALUES (?, ?, ?, ?)
|
|
142
|
+
ON CONFLICT(user_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
|
|
80
143
|
`);
|
|
81
|
-
stmt.run(key, value, Date.now());
|
|
144
|
+
stmt.run(userId || 'default', key, value, Date.now());
|
|
82
145
|
}
|
|
83
|
-
getUserProfile() {
|
|
84
|
-
const rows = this.db.prepare('SELECT key, value FROM user_profile').all();
|
|
146
|
+
getUserProfile(userId) {
|
|
147
|
+
const rows = this.db.prepare('SELECT key, value FROM user_profile WHERE user_id = ?').all(userId || 'default');
|
|
85
148
|
const profile = {};
|
|
86
149
|
rows.forEach((row) => {
|
|
87
150
|
profile[row.key] = row.value;
|
package/dist/server/index.js
CHANGED
|
@@ -176,6 +176,7 @@ app.post('/api/fs/list', async (req, res) => {
|
|
|
176
176
|
// 获取当前配置
|
|
177
177
|
app.get('/api/config', async (req, res) => {
|
|
178
178
|
res.json({
|
|
179
|
+
userId: config.userId,
|
|
179
180
|
apiKey: config.llm.apiKey,
|
|
180
181
|
baseURL: config.llm.baseURL,
|
|
181
182
|
model: config.llm.model,
|
|
@@ -189,9 +190,11 @@ app.get('/api/config', async (req, res) => {
|
|
|
189
190
|
// 更新配置并写入 config.json
|
|
190
191
|
app.post('/api/config', async (req, res) => {
|
|
191
192
|
try {
|
|
192
|
-
const { apiKey, baseURL, model, temperature, systemPrompt, memoryMaintenanceCron, sessionRetentionDays, workspace } = req.body;
|
|
193
|
+
const { userId, apiKey, baseURL, model, temperature, systemPrompt, memoryMaintenanceCron, sessionRetentionDays, workspace } = req.body;
|
|
193
194
|
// 更新配置对象
|
|
194
195
|
const newConfig = { ...config };
|
|
196
|
+
if (userId !== undefined)
|
|
197
|
+
newConfig.userId = String(userId || 'default').trim() || 'default';
|
|
195
198
|
if (apiKey !== undefined)
|
|
196
199
|
newConfig.llm.apiKey = apiKey;
|
|
197
200
|
if (baseURL !== undefined)
|
|
@@ -257,6 +260,27 @@ app.post('/api/config', async (req, res) => {
|
|
|
257
260
|
res.status(500).json({ status: 'error', message: error.message });
|
|
258
261
|
}
|
|
259
262
|
});
|
|
263
|
+
app.get('/api/user/profile', async (req, res) => {
|
|
264
|
+
try {
|
|
265
|
+
const userId = String(req.query.userId || config.userId || 'default');
|
|
266
|
+
const profile = memory.getUserProfile(userId);
|
|
267
|
+
res.json({ userId, profile });
|
|
268
|
+
}
|
|
269
|
+
catch (e) {
|
|
270
|
+
res.status(500).json({ error: String(e?.message || e) });
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
app.post('/api/user/profile', async (req, res) => {
|
|
274
|
+
try {
|
|
275
|
+
const userId = String(req.body?.userId || config.userId || 'default');
|
|
276
|
+
const profile = req.body?.profile || {};
|
|
277
|
+
memory.updateUserProfile(userId, profile);
|
|
278
|
+
res.json({ status: 'success' });
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
res.status(500).json({ error: String(e?.message || e) });
|
|
282
|
+
}
|
|
283
|
+
});
|
|
260
284
|
// 手动触发记忆维护
|
|
261
285
|
app.post('/api/scheduler/run', async (req, res) => {
|
|
262
286
|
try {
|
package/package.json
CHANGED
|
@@ -53,6 +53,15 @@
|
|
|
53
53
|
"modelName": "Model Name",
|
|
54
54
|
"temperature": "Temperature"
|
|
55
55
|
},
|
|
56
|
+
"identity": {
|
|
57
|
+
"title": "User identity",
|
|
58
|
+
"userId": "UserId",
|
|
59
|
+
"userIdHint": "Used to load identity profile and memories for this userId.",
|
|
60
|
+
"name": "Name",
|
|
61
|
+
"role": "Role",
|
|
62
|
+
"company": "Company",
|
|
63
|
+
"email": "Email"
|
|
64
|
+
},
|
|
56
65
|
"prompt": {
|
|
57
66
|
"description": "Define how xiaozuoAssistant behaves and responds.",
|
|
58
67
|
"placeholder": "You are a helpful AI assistant..."
|
|
@@ -53,6 +53,15 @@
|
|
|
53
53
|
"modelName": "模型名称",
|
|
54
54
|
"temperature": "温度 (Temperature)"
|
|
55
55
|
},
|
|
56
|
+
"identity": {
|
|
57
|
+
"title": "用户身份",
|
|
58
|
+
"userId": "UserId",
|
|
59
|
+
"userIdHint": "用于加载该 UserId 对应的身份资料与记忆。",
|
|
60
|
+
"name": "姓名",
|
|
61
|
+
"role": "岗位",
|
|
62
|
+
"company": "公司",
|
|
63
|
+
"email": "邮箱"
|
|
64
|
+
},
|
|
56
65
|
"prompt": {
|
|
57
66
|
"description": "定义 xiaozuoAssistant 的行为和响应方式。",
|
|
58
67
|
"placeholder": "你是一个有用的 AI 助手..."
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.top-0{top:0}.z-10{z-index:10}.z-50{z-index:50}.z-\[60\]{z-index:60}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.flex{display:flex}.table{display:table}.h-16{height:4rem}.h-2{height:.5rem}.h-4{height:1rem}.h-8{height:2rem}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[200px\]{max-height:200px}.max-h-\[70vh\]{max-height:70vh}.max-h-\[90vh\]{max-height:90vh}.min-h-\[120px\]{min-height:120px}.min-h-\[300px\]{min-height:300px}.min-h-\[50px\]{min-height:50px}.w-16{width:4rem}.w-28{width:7rem}.w-8{width:2rem}.w-\[260px\]{width:260px}.w-full{width:100%}.w-screen{width:100vw}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-\[46vw\]{max-width:46vw}.max-w-\[85\%\]{max-width:85%}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-bl-none{border-bottom-left-radius:0}.rounded-br-none{border-bottom-right-radius:0}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-none{border-style:none}.border-black\/10{border-color:#0000001a}.border-blue-100{--tw-border-opacity: 1;border-color:rgb(219 234 254 / var(--tw-border-opacity, 1))}.border-blue-600{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity, 1))}.border-gray-100{--tw-border-opacity: 1;border-color:rgb(243 244 246 / var(--tw-border-opacity, 1))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-gray-800{--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.border-green-200{--tw-border-opacity: 1;border-color:rgb(187 247 208 / var(--tw-border-opacity, 1))}.border-red-200{--tw-border-opacity: 1;border-color:rgb(254 202 202 / var(--tw-border-opacity, 1))}.bg-black\/20{background-color:#0003}.bg-black\/50{background-color:#00000080}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-green-50{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity, 1))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-teal-600{--tw-bg-opacity: 1;background-color:rgb(13 148 136 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-white\/80{background-color:#fffc}.fill-current{fill:currentColor}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-20{padding-top:5rem;padding-bottom:5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-2{padding-bottom:.5rem}.pt-2{padding-top:.5rem}.pt-6{padding-top:1.5rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.text-2xl{font-size:1.5rem;line-height:2rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-\[15px\]{font-size:15px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-relaxed{line-height:1.625}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.text-green-700{--tw-text-opacity: 1;color:rgb(21 128 61 / var(--tw-text-opacity, 1))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.accent-blue-600{accent-color:#2563eb}.opacity-0{opacity:0}.opacity-50{opacity:.5}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-md{--tw-backdrop-blur: blur(12px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}:root{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;line-height:1.5;font-weight:400;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.custom-scrollbar::-webkit-scrollbar{width:5px}.custom-scrollbar::-webkit-scrollbar-track{background:transparent}.custom-scrollbar::-webkit-scrollbar-thumb{background:#9b9b9b33;border-radius:10px}.custom-scrollbar::-webkit-scrollbar-thumb:hover{background:#9b9b9b66}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.selection\:bg-blue-100 *::-moz-selection{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.selection\:bg-blue-100 *::selection{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.selection\:text-blue-900 *::-moz-selection{--tw-text-opacity: 1;color:rgb(30 58 138 / var(--tw-text-opacity, 1))}.selection\:text-blue-900 *::selection{--tw-text-opacity: 1;color:rgb(30 58 138 / var(--tw-text-opacity, 1))}.selection\:bg-blue-100::-moz-selection{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.selection\:bg-blue-100::selection{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.selection\:text-blue-900::-moz-selection{--tw-text-opacity: 1;color:rgb(30 58 138 / var(--tw-text-opacity, 1))}.selection\:text-blue-900::selection{--tw-text-opacity: 1;color:rgb(30 58 138 / var(--tw-text-opacity, 1))}.placeholder\:text-gray-400::-moz-placeholder{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.placeholder\:text-gray-400::placeholder{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.focus-within\:border-blue-400:focus-within{--tw-border-opacity: 1;border-color:rgb(96 165 250 / var(--tw-border-opacity, 1))}.focus-within\:ring-2:focus-within{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-within\:ring-blue-100:focus-within{--tw-ring-opacity: 1;--tw-ring-color: rgb(219 234 254 / var(--tw-ring-opacity, 1))}.hover\:bg-blue-50:hover{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.hover\:bg-blue-700:hover{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-200:hover{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-800:hover{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-800\/50:hover{background-color:#1f293780}.hover\:bg-red-50:hover{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.hover\:bg-teal-700:hover{--tw-bg-opacity: 1;background-color:rgb(15 118 110 / var(--tw-bg-opacity, 1))}.hover\:text-blue-300:hover{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.hover\:text-blue-700:hover{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity, 1))}.hover\:text-gray-600:hover{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.hover\:text-gray-800:hover{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.hover\:text-red-400:hover{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-0:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-70:disabled{opacity:.7}.group:hover .group-hover\:rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:border-gray-600{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.group:hover .group-hover\:text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:text-yellow-600{--tw-text-opacity: 1;color:rgb(202 138 4 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:opacity-100{opacity:1}
|