@burtson-labs/bandit-engine 2.1.4 → 2.1.7
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/CHANGELOG.md +23 -0
- package/dist/ProjectsPane-E5D72PUG.mjs +866 -0
- package/dist/ProjectsPane-E5D72PUG.mjs.map +1 -0
- package/dist/{SchedulesTab-YZG3P2GW.mjs → SchedulesTab-AKJ6WFZV.mjs} +5 -4
- package/dist/SchedulesTab-AKJ6WFZV.mjs.map +1 -0
- package/dist/chat-MI4IL3WK.mjs +19 -0
- package/dist/chunk-2HFSBPOE.mjs +163 -0
- package/dist/chunk-2HFSBPOE.mjs.map +1 -0
- package/dist/{chunk-W2D454CZ.mjs → chunk-4NHPLHXU.mjs} +3550 -4872
- package/dist/chunk-4NHPLHXU.mjs.map +1 -0
- package/dist/{chunk-PSI7RQ54.mjs → chunk-4NUQYUZH.mjs} +7 -2
- package/dist/chunk-4NUQYUZH.mjs.map +1 -0
- package/dist/{chunk-62H3OLTV.mjs → chunk-5URP2E5V.mjs} +20 -26
- package/dist/chunk-5URP2E5V.mjs.map +1 -0
- package/dist/chunk-HS62C62A.mjs +22 -0
- package/dist/chunk-HS62C62A.mjs.map +1 -0
- package/dist/{chunk-4LLCLYLH.mjs → chunk-K7SQWDNF.mjs} +1 -1
- package/dist/{chunk-4LLCLYLH.mjs.map → chunk-K7SQWDNF.mjs.map} +1 -1
- package/dist/{chunk-6CSFKF34.mjs → chunk-KWOUXBSF.mjs} +3 -3
- package/dist/chunk-MAKXJD7A.mjs +233 -0
- package/dist/chunk-MAKXJD7A.mjs.map +1 -0
- package/dist/chunk-SQJ4OKWM.mjs +3567 -0
- package/dist/chunk-SQJ4OKWM.mjs.map +1 -0
- package/dist/chunk-WBKTQJNX.mjs +21 -0
- package/dist/chunk-WBKTQJNX.mjs.map +1 -0
- package/dist/image-compare-NW5VCP4Q.mjs +74 -0
- package/dist/image-compare-NW5VCP4Q.mjs.map +1 -0
- package/dist/image-edit-acknowledgement-RZPQYKYJ.mjs +54 -0
- package/dist/image-edit-acknowledgement-RZPQYKYJ.mjs.map +1 -0
- package/dist/index.d.mts +18 -3
- package/dist/index.d.ts +18 -3
- package/dist/index.js +8173 -4832
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +367 -338
- package/dist/index.mjs.map +1 -1
- package/dist/modelStore-YNANSWCN.mjs +9 -0
- package/dist/{public-ByjnoHLe.d.mts → public-Tq2Pmw9m.d.mts} +44 -2
- package/dist/{public-ByjnoHLe.d.ts → public-Tq2Pmw9m.d.ts} +44 -2
- package/dist/public-types.d.mts +1 -1
- package/dist/public-types.d.ts +1 -1
- package/dist/{schedule-confirm-card-N4TKLEHL.mjs → schedule-confirm-card-D4K76HEA.mjs} +8 -5
- package/dist/schedule-confirm-card-D4K76HEA.mjs.map +1 -0
- package/docs/package-settings.md +25 -0
- package/package.json +1 -1
- package/dist/SchedulesTab-YZG3P2GW.mjs.map +0 -1
- package/dist/chat-W7MDFESE.mjs +0 -15
- package/dist/chunk-62H3OLTV.mjs.map +0 -1
- package/dist/chunk-LMAQWR7Z.mjs +0 -388
- package/dist/chunk-LMAQWR7Z.mjs.map +0 -1
- package/dist/chunk-PSI7RQ54.mjs.map +0 -1
- package/dist/chunk-W2D454CZ.mjs.map +0 -1
- package/dist/modelStore-FIVOSLFY.mjs +0 -9
- package/dist/schedule-confirm-card-N4TKLEHL.mjs.map +0 -1
- /package/dist/{chat-W7MDFESE.mjs.map → chat-MI4IL3WK.mjs.map} +0 -0
- /package/dist/{chunk-6CSFKF34.mjs.map → chunk-KWOUXBSF.mjs.map} +0 -0
- /package/dist/{modelStore-FIVOSLFY.mjs.map → modelStore-YNANSWCN.mjs.map} +0 -0
|
@@ -0,0 +1,3567 @@
|
|
|
1
|
+
import {
|
|
2
|
+
indexedDBService_default,
|
|
3
|
+
useModelStore,
|
|
4
|
+
usePreferencesStore
|
|
5
|
+
} from "./chunk-KWOUXBSF.mjs";
|
|
6
|
+
import {
|
|
7
|
+
Archive,
|
|
8
|
+
Braces,
|
|
9
|
+
Check,
|
|
10
|
+
Edit,
|
|
11
|
+
File,
|
|
12
|
+
FileCode,
|
|
13
|
+
FileImage,
|
|
14
|
+
FilePdf,
|
|
15
|
+
FileText,
|
|
16
|
+
Folder,
|
|
17
|
+
GripVertical,
|
|
18
|
+
Home,
|
|
19
|
+
Inbox,
|
|
20
|
+
LogOut,
|
|
21
|
+
MailOpen,
|
|
22
|
+
MessageSquarePlus,
|
|
23
|
+
MoreVertical,
|
|
24
|
+
Plus,
|
|
25
|
+
Search,
|
|
26
|
+
Settings,
|
|
27
|
+
Sheet,
|
|
28
|
+
Trash
|
|
29
|
+
} from "./chunk-5URP2E5V.mjs";
|
|
30
|
+
import {
|
|
31
|
+
usePackageSettingsStore
|
|
32
|
+
} from "./chunk-K7SQWDNF.mjs";
|
|
33
|
+
import {
|
|
34
|
+
AUTH_TOKEN_CHANGED_EVENT,
|
|
35
|
+
TOKEN_KEY,
|
|
36
|
+
authenticationService,
|
|
37
|
+
readPersistedToken,
|
|
38
|
+
useAuthenticationStore
|
|
39
|
+
} from "./chunk-S3DWWUBD.mjs";
|
|
40
|
+
import {
|
|
41
|
+
debugLogger
|
|
42
|
+
} from "./chunk-NI7BKLC7.mjs";
|
|
43
|
+
|
|
44
|
+
// src/store/aiQueryStore.ts
|
|
45
|
+
import { create } from "zustand";
|
|
46
|
+
var DB_NAME = "ai-query-db";
|
|
47
|
+
var STORE_NAME = "ai-query";
|
|
48
|
+
var DB_VERSION = 1;
|
|
49
|
+
var STORAGE_KEY = "singleton";
|
|
50
|
+
var storeConfigs = [{ name: STORE_NAME }];
|
|
51
|
+
async function saveStateToDB(state) {
|
|
52
|
+
await indexedDBService_default.put(
|
|
53
|
+
DB_NAME,
|
|
54
|
+
DB_VERSION,
|
|
55
|
+
STORE_NAME,
|
|
56
|
+
state,
|
|
57
|
+
storeConfigs,
|
|
58
|
+
STORAGE_KEY
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
async function loadStateFromDB() {
|
|
62
|
+
let timeoutId;
|
|
63
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
64
|
+
timeoutId = window.setTimeout(() => resolve(void 0), 1200);
|
|
65
|
+
});
|
|
66
|
+
try {
|
|
67
|
+
const getPromise = indexedDBService_default.get(DB_NAME, DB_VERSION, STORE_NAME, STORAGE_KEY, storeConfigs).catch(() => void 0);
|
|
68
|
+
return await Promise.race([getPromise, timeoutPromise]);
|
|
69
|
+
} finally {
|
|
70
|
+
if (timeoutId !== void 0) {
|
|
71
|
+
window.clearTimeout(timeoutId);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
var useAIQueryStore = create((set, get) => ({
|
|
76
|
+
inputValue: "",
|
|
77
|
+
response: "",
|
|
78
|
+
previousQuestion: "",
|
|
79
|
+
position: { x: window.innerWidth / 2 - 300, y: window.innerHeight - 350 },
|
|
80
|
+
componentStatus: "Idle",
|
|
81
|
+
history: [],
|
|
82
|
+
apiKey: "",
|
|
83
|
+
hydrated: false,
|
|
84
|
+
setInputValue: (value) => {
|
|
85
|
+
set({ inputValue: value });
|
|
86
|
+
saveStateToDB({ inputValue: value });
|
|
87
|
+
},
|
|
88
|
+
setResponse: (response) => {
|
|
89
|
+
set({ response });
|
|
90
|
+
saveStateToDB({ response });
|
|
91
|
+
},
|
|
92
|
+
setPreviousQuestion: (question) => {
|
|
93
|
+
set({ previousQuestion: question });
|
|
94
|
+
saveStateToDB({ previousQuestion: question });
|
|
95
|
+
},
|
|
96
|
+
setPosition: (position) => {
|
|
97
|
+
set({ position });
|
|
98
|
+
saveStateToDB({ position });
|
|
99
|
+
},
|
|
100
|
+
setComponentStatus: (status) => set({ componentStatus: status }),
|
|
101
|
+
addHistory: (entry) => {
|
|
102
|
+
const newHistory = [...get().history, entry];
|
|
103
|
+
set({ history: newHistory });
|
|
104
|
+
saveStateToDB({ history: newHistory });
|
|
105
|
+
},
|
|
106
|
+
clearHistory: () => {
|
|
107
|
+
set({ history: [] });
|
|
108
|
+
saveStateToDB({ history: [] });
|
|
109
|
+
},
|
|
110
|
+
setApiKey: (key) => {
|
|
111
|
+
set({ apiKey: key });
|
|
112
|
+
saveStateToDB({ apiKey: key });
|
|
113
|
+
},
|
|
114
|
+
setHydrated: () => set({ hydrated: true }),
|
|
115
|
+
reset: () => {
|
|
116
|
+
const resetState = {
|
|
117
|
+
inputValue: "",
|
|
118
|
+
response: "",
|
|
119
|
+
previousQuestion: "",
|
|
120
|
+
position: { x: window.innerWidth / 2 - 300, y: window.innerHeight - 350 },
|
|
121
|
+
componentStatus: "Idle",
|
|
122
|
+
history: [],
|
|
123
|
+
apiKey: ""
|
|
124
|
+
};
|
|
125
|
+
set(resetState);
|
|
126
|
+
saveStateToDB(resetState);
|
|
127
|
+
},
|
|
128
|
+
hydrate: async () => {
|
|
129
|
+
try {
|
|
130
|
+
const storedState = await loadStateFromDB();
|
|
131
|
+
if (storedState) {
|
|
132
|
+
set({
|
|
133
|
+
inputValue: storedState.inputValue ?? "",
|
|
134
|
+
response: storedState.response ?? "",
|
|
135
|
+
previousQuestion: storedState.previousQuestion ?? "",
|
|
136
|
+
position: storedState.position ?? {
|
|
137
|
+
x: window.innerWidth / 2 - 300,
|
|
138
|
+
y: window.innerHeight - 350
|
|
139
|
+
},
|
|
140
|
+
componentStatus: "Idle",
|
|
141
|
+
history: storedState.history ?? [],
|
|
142
|
+
apiKey: storedState.apiKey ?? "",
|
|
143
|
+
hydrated: true
|
|
144
|
+
});
|
|
145
|
+
} else {
|
|
146
|
+
set({ hydrated: true });
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
set({ hydrated: true });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}));
|
|
153
|
+
|
|
154
|
+
// src/store/conversationStore.ts
|
|
155
|
+
import { create as create2 } from "zustand";
|
|
156
|
+
|
|
157
|
+
// src/utils/id.ts
|
|
158
|
+
var cryptoOf = () => globalThis.crypto;
|
|
159
|
+
function newId() {
|
|
160
|
+
const c = cryptoOf();
|
|
161
|
+
if (typeof c?.randomUUID === "function") return c.randomUUID();
|
|
162
|
+
const bytes = new Uint8Array(16);
|
|
163
|
+
if (typeof c?.getRandomValues === "function") {
|
|
164
|
+
c.getRandomValues(bytes);
|
|
165
|
+
} else {
|
|
166
|
+
for (let i = 0; i < bytes.length; i += 1) bytes[i] = Math.floor(Math.random() * 256);
|
|
167
|
+
}
|
|
168
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
169
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
170
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
171
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/util/conversationMigration.ts
|
|
175
|
+
var DB_NAME2 = "bandit-conversations";
|
|
176
|
+
var STORE_NAME2 = "conversations";
|
|
177
|
+
var DB_VERSION2 = 1;
|
|
178
|
+
var storeConfigs2 = [{ name: STORE_NAME2, keyPath: "id" }];
|
|
179
|
+
async function migrateConversationsToV2() {
|
|
180
|
+
try {
|
|
181
|
+
debugLogger.info("Starting conversation migration to V2 format");
|
|
182
|
+
const conversations = await indexedDBService_default.getAll(
|
|
183
|
+
DB_NAME2,
|
|
184
|
+
DB_VERSION2,
|
|
185
|
+
STORE_NAME2,
|
|
186
|
+
storeConfigs2
|
|
187
|
+
);
|
|
188
|
+
if (!conversations || conversations.length === 0) {
|
|
189
|
+
debugLogger.info("No conversations found, migration not needed");
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
let migratedCount = 0;
|
|
193
|
+
const now = /* @__PURE__ */ new Date();
|
|
194
|
+
for (const conversation of conversations) {
|
|
195
|
+
const needsMigration = !conversation.hasOwnProperty("createdAt") || !conversation.hasOwnProperty("updatedAt");
|
|
196
|
+
if (needsMigration) {
|
|
197
|
+
const migratedConversation = {
|
|
198
|
+
...conversation,
|
|
199
|
+
// projectId remains undefined (ungrouped)
|
|
200
|
+
createdAt: now,
|
|
201
|
+
// We can't know the real creation date
|
|
202
|
+
updatedAt: now
|
|
203
|
+
};
|
|
204
|
+
await indexedDBService_default.put(
|
|
205
|
+
DB_NAME2,
|
|
206
|
+
DB_VERSION2,
|
|
207
|
+
STORE_NAME2,
|
|
208
|
+
migratedConversation,
|
|
209
|
+
storeConfigs2
|
|
210
|
+
);
|
|
211
|
+
migratedCount++;
|
|
212
|
+
debugLogger.debug("Migrated conversation", {
|
|
213
|
+
id: conversation.id,
|
|
214
|
+
name: conversation.name
|
|
215
|
+
});
|
|
216
|
+
} else if ("createdAt" in conversation && conversation.createdAt && typeof conversation.createdAt === "string") {
|
|
217
|
+
const fixedConversation = {
|
|
218
|
+
...conversation,
|
|
219
|
+
createdAt: new Date(conversation.createdAt),
|
|
220
|
+
updatedAt: new Date(
|
|
221
|
+
conversation.updatedAt || conversation.createdAt
|
|
222
|
+
)
|
|
223
|
+
};
|
|
224
|
+
await indexedDBService_default.put(
|
|
225
|
+
DB_NAME2,
|
|
226
|
+
DB_VERSION2,
|
|
227
|
+
STORE_NAME2,
|
|
228
|
+
fixedConversation,
|
|
229
|
+
storeConfigs2
|
|
230
|
+
);
|
|
231
|
+
migratedCount++;
|
|
232
|
+
debugLogger.debug("Fixed date format for conversation", {
|
|
233
|
+
id: conversation.id,
|
|
234
|
+
name: conversation.name
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (migratedCount > 0) {
|
|
239
|
+
debugLogger.info("Conversation migration completed", {
|
|
240
|
+
totalConversations: conversations.length,
|
|
241
|
+
migratedCount
|
|
242
|
+
});
|
|
243
|
+
} else {
|
|
244
|
+
debugLogger.info("All conversations already up to date");
|
|
245
|
+
}
|
|
246
|
+
return true;
|
|
247
|
+
} catch (error) {
|
|
248
|
+
debugLogger.error("Failed to migrate conversations", { error });
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async function validateConversationStructure() {
|
|
253
|
+
try {
|
|
254
|
+
const conversations = await indexedDBService_default.getAll(
|
|
255
|
+
DB_NAME2,
|
|
256
|
+
DB_VERSION2,
|
|
257
|
+
STORE_NAME2,
|
|
258
|
+
storeConfigs2
|
|
259
|
+
);
|
|
260
|
+
if (!conversations) {
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
for (const conversation of conversations) {
|
|
264
|
+
const isValid = typeof conversation.id === "string" && typeof conversation.name === "string" && typeof conversation.model === "string" && Array.isArray(conversation.history) && (conversation.projectId === void 0 || typeof conversation.projectId === "string") && (conversation.createdAt === void 0 || conversation.createdAt instanceof Date) && (conversation.updatedAt === void 0 || conversation.updatedAt instanceof Date);
|
|
265
|
+
if (!isValid) {
|
|
266
|
+
debugLogger.warn("Invalid conversation structure detected", {
|
|
267
|
+
conversationId: conversation.id,
|
|
268
|
+
structure: {
|
|
269
|
+
hasId: typeof conversation.id === "string",
|
|
270
|
+
hasName: typeof conversation.name === "string",
|
|
271
|
+
hasModel: typeof conversation.model === "string",
|
|
272
|
+
hasHistory: Array.isArray(conversation.history),
|
|
273
|
+
projectIdValid: conversation.projectId === void 0 || typeof conversation.projectId === "string",
|
|
274
|
+
createdAtValid: conversation.createdAt === void 0 || conversation.createdAt instanceof Date,
|
|
275
|
+
updatedAtValid: conversation.updatedAt === void 0 || conversation.updatedAt instanceof Date
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
debugLogger.info("All conversations have valid structure", {
|
|
282
|
+
count: conversations.length
|
|
283
|
+
});
|
|
284
|
+
return true;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
debugLogger.error("Failed to validate conversation structure", { error });
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async function runConversationMigrations() {
|
|
291
|
+
try {
|
|
292
|
+
debugLogger.info("Running conversation migrations");
|
|
293
|
+
const migrationSuccess = await migrateConversationsToV2();
|
|
294
|
+
if (!migrationSuccess) {
|
|
295
|
+
debugLogger.error("Conversation migration failed");
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
const validationSuccess = await validateConversationStructure();
|
|
299
|
+
if (!validationSuccess) {
|
|
300
|
+
debugLogger.warn("Conversation validation failed, but continuing");
|
|
301
|
+
}
|
|
302
|
+
debugLogger.info("Conversation migrations completed successfully");
|
|
303
|
+
return true;
|
|
304
|
+
} catch (error) {
|
|
305
|
+
debugLogger.error("Migration process failed", { error });
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/store/conversationSyncEvents.ts
|
|
311
|
+
var CONVERSATION_UPSERT_EVENT = "bandit-conversation-upsert";
|
|
312
|
+
var CONVERSATION_DELETE_EVENT = "bandit-conversation-delete";
|
|
313
|
+
var PROJECT_UPSERT_EVENT = "bandit-project-upsert";
|
|
314
|
+
var PROJECT_DELETE_EVENT = "bandit-project-delete";
|
|
315
|
+
|
|
316
|
+
// src/store/conversationStore.ts
|
|
317
|
+
var DB_NAME3 = "bandit-conversations";
|
|
318
|
+
var STORE_NAME3 = "conversations";
|
|
319
|
+
var DB_VERSION3 = 1;
|
|
320
|
+
var storeConfigs3 = [{ name: STORE_NAME3, keyPath: "id" }];
|
|
321
|
+
var EMOJI_REGEX = /\p{Extended_Pictographic}/gu;
|
|
322
|
+
var VARIATION_SELECTORS_REGEX = /[\uFE0F\uFE0E]/g;
|
|
323
|
+
var ZERO_WIDTH_JOINER_REGEX = /\u200D/g;
|
|
324
|
+
function sanitizeConversationName(input, maxLength = 60) {
|
|
325
|
+
if (!input) return "Untitled Conversation";
|
|
326
|
+
const withoutEmoji = input.replace(EMOJI_REGEX, "").replace(VARIATION_SELECTORS_REGEX, "").replace(ZERO_WIDTH_JOINER_REGEX, "");
|
|
327
|
+
const normalized = withoutEmoji.normalize("NFC").trim().replace(/\s+/g, " ");
|
|
328
|
+
if (!normalized) {
|
|
329
|
+
return "Untitled Conversation";
|
|
330
|
+
}
|
|
331
|
+
const limited = Array.from(normalized).slice(0, maxLength).join("");
|
|
332
|
+
return limited || "Untitled Conversation";
|
|
333
|
+
}
|
|
334
|
+
async function loadConversations() {
|
|
335
|
+
const raw = await indexedDBService_default.getAll(
|
|
336
|
+
DB_NAME3,
|
|
337
|
+
DB_VERSION3,
|
|
338
|
+
STORE_NAME3,
|
|
339
|
+
storeConfigs3
|
|
340
|
+
);
|
|
341
|
+
return (raw || []).map(normalizeConversation);
|
|
342
|
+
}
|
|
343
|
+
async function saveConversation(conversation) {
|
|
344
|
+
if (conversation.incognito) return;
|
|
345
|
+
await indexedDBService_default.put(DB_NAME3, DB_VERSION3, STORE_NAME3, conversation, storeConfigs3);
|
|
346
|
+
}
|
|
347
|
+
async function deleteConversationFromDB(id) {
|
|
348
|
+
await indexedDBService_default.delete(DB_NAME3, DB_VERSION3, STORE_NAME3, id, storeConfigs3);
|
|
349
|
+
}
|
|
350
|
+
var useConversationStore = create2((set, get) => ({
|
|
351
|
+
conversations: [],
|
|
352
|
+
currentId: null,
|
|
353
|
+
_hasHydrated: false,
|
|
354
|
+
hydrate: async () => {
|
|
355
|
+
try {
|
|
356
|
+
await runConversationMigrations();
|
|
357
|
+
const conversations = await loadConversations();
|
|
358
|
+
set({ conversations, _hasHydrated: true });
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.error("Failed to hydrate conversations:", error);
|
|
361
|
+
set({ conversations: [], _hasHydrated: true });
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
createConversation: (firstMessage, projectId) => {
|
|
365
|
+
const id = newId();
|
|
366
|
+
const { selectedModel } = useModelStore.getState();
|
|
367
|
+
const sanitizedFirstMessage = sanitizeConversationName(firstMessage, 60);
|
|
368
|
+
const name = sanitizedFirstMessage;
|
|
369
|
+
const now = /* @__PURE__ */ new Date();
|
|
370
|
+
const newConv = normalizeConversation({
|
|
371
|
+
id,
|
|
372
|
+
name,
|
|
373
|
+
history: [],
|
|
374
|
+
model: selectedModel,
|
|
375
|
+
projectId,
|
|
376
|
+
createdAt: now,
|
|
377
|
+
updatedAt: now,
|
|
378
|
+
version: 0
|
|
379
|
+
});
|
|
380
|
+
set((state) => {
|
|
381
|
+
const updatedConversations = [...state.conversations, newConv];
|
|
382
|
+
saveConversation(newConv);
|
|
383
|
+
emitConversationUpsert(id);
|
|
384
|
+
return {
|
|
385
|
+
conversations: updatedConversations,
|
|
386
|
+
currentId: id
|
|
387
|
+
};
|
|
388
|
+
});
|
|
389
|
+
},
|
|
390
|
+
createNewConversation: (projectId) => {
|
|
391
|
+
const { selectedModel } = useModelStore.getState();
|
|
392
|
+
const id = newId();
|
|
393
|
+
const now = /* @__PURE__ */ new Date();
|
|
394
|
+
const newConv = normalizeConversation({
|
|
395
|
+
id,
|
|
396
|
+
name: "New Conversation",
|
|
397
|
+
history: [],
|
|
398
|
+
model: selectedModel,
|
|
399
|
+
projectId,
|
|
400
|
+
createdAt: now,
|
|
401
|
+
updatedAt: now,
|
|
402
|
+
version: 0
|
|
403
|
+
});
|
|
404
|
+
set((state) => {
|
|
405
|
+
const updatedConversations = [...state.conversations, newConv];
|
|
406
|
+
saveConversation(newConv);
|
|
407
|
+
emitConversationUpsert(id);
|
|
408
|
+
return {
|
|
409
|
+
conversations: updatedConversations,
|
|
410
|
+
currentId: id
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
},
|
|
414
|
+
toggleIncognito: () => {
|
|
415
|
+
const { conversations, currentId } = get();
|
|
416
|
+
const current = conversations.find((c) => c.id === currentId);
|
|
417
|
+
if (current?.incognito) {
|
|
418
|
+
useAIQueryStore.getState().reset();
|
|
419
|
+
set((state) => ({
|
|
420
|
+
conversations: state.conversations.filter((c) => !c.incognito),
|
|
421
|
+
currentId: null
|
|
422
|
+
}));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const { selectedModel } = useModelStore.getState();
|
|
426
|
+
const now = /* @__PURE__ */ new Date();
|
|
427
|
+
const newConv = normalizeConversation({
|
|
428
|
+
id: newId(),
|
|
429
|
+
name: "Temporary chat",
|
|
430
|
+
history: [],
|
|
431
|
+
model: selectedModel,
|
|
432
|
+
createdAt: now,
|
|
433
|
+
updatedAt: now,
|
|
434
|
+
version: 0,
|
|
435
|
+
incognito: true
|
|
436
|
+
});
|
|
437
|
+
set((state) => ({
|
|
438
|
+
conversations: [...state.conversations, newConv],
|
|
439
|
+
currentId: newConv.id
|
|
440
|
+
}));
|
|
441
|
+
},
|
|
442
|
+
deleteConversation: (id) => {
|
|
443
|
+
const runHydrate = async () => {
|
|
444
|
+
try {
|
|
445
|
+
await get().hydrate();
|
|
446
|
+
} catch (error) {
|
|
447
|
+
debugLogger.warn("conversationStore: rehydrate after delete failed", {
|
|
448
|
+
error: error instanceof Error ? error.message : String(error)
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
set((state) => {
|
|
453
|
+
const filtered = state.conversations.filter((c) => c.id !== id);
|
|
454
|
+
const isDeletingCurrent = state.currentId === id;
|
|
455
|
+
if (isDeletingCurrent) {
|
|
456
|
+
useAIQueryStore.getState().reset();
|
|
457
|
+
}
|
|
458
|
+
deleteConversationFromDB(id).then(runHydrate).catch((error) => {
|
|
459
|
+
debugLogger.error("Failed to delete conversation from DB", {
|
|
460
|
+
error,
|
|
461
|
+
conversationId: id
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
emitConversationDelete(id);
|
|
465
|
+
return {
|
|
466
|
+
conversations: filtered,
|
|
467
|
+
currentId: isDeletingCurrent ? null : state.currentId
|
|
468
|
+
};
|
|
469
|
+
});
|
|
470
|
+
},
|
|
471
|
+
switchConversation: (id) => {
|
|
472
|
+
const { conversations } = get();
|
|
473
|
+
const { setSelectedModel } = useModelStore.getState();
|
|
474
|
+
const selectedModel = conversations.find((c) => c.id === id)?.model;
|
|
475
|
+
selectedModel && setSelectedModel(selectedModel);
|
|
476
|
+
set({ currentId: id });
|
|
477
|
+
},
|
|
478
|
+
addToCurrent: (entry) => {
|
|
479
|
+
const { currentId, conversations } = get();
|
|
480
|
+
if (!currentId) return;
|
|
481
|
+
set(() => {
|
|
482
|
+
const updatedConversations = conversations.map(
|
|
483
|
+
(c) => c.id === currentId ? normalizeConversation({
|
|
484
|
+
...c,
|
|
485
|
+
history: [...c.history, entry],
|
|
486
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
487
|
+
}) : c
|
|
488
|
+
);
|
|
489
|
+
const updatedConv = updatedConversations.find((c) => c.id === currentId);
|
|
490
|
+
if (updatedConv) {
|
|
491
|
+
saveConversation(updatedConv);
|
|
492
|
+
emitConversationUpsert(updatedConv.id);
|
|
493
|
+
}
|
|
494
|
+
return { conversations: updatedConversations };
|
|
495
|
+
});
|
|
496
|
+
},
|
|
497
|
+
renameConversation: (id, newName) => {
|
|
498
|
+
set((state) => {
|
|
499
|
+
const sanitized = sanitizeConversationName(newName);
|
|
500
|
+
const updatedConversations = state.conversations.map(
|
|
501
|
+
(c) => c.id === id ? normalizeConversation({ ...c, name: sanitized, updatedAt: /* @__PURE__ */ new Date() }) : c
|
|
502
|
+
);
|
|
503
|
+
const updatedConv = updatedConversations.find((c) => c.id === id);
|
|
504
|
+
if (updatedConv) {
|
|
505
|
+
saveConversation(updatedConv);
|
|
506
|
+
emitConversationUpsert(updatedConv.id);
|
|
507
|
+
}
|
|
508
|
+
return { conversations: updatedConversations };
|
|
509
|
+
});
|
|
510
|
+
},
|
|
511
|
+
replaceLastAnswer: (answer, images, memoryUpdated, sourceFiles, cancelled) => {
|
|
512
|
+
const { currentId, conversations } = get();
|
|
513
|
+
if (!currentId) return;
|
|
514
|
+
set(() => {
|
|
515
|
+
const updatedConversations = conversations.map((c) => {
|
|
516
|
+
if (c.id === currentId && c.history.length > 0) {
|
|
517
|
+
const updatedHistory = [...c.history];
|
|
518
|
+
const existingImages = updatedHistory[updatedHistory.length - 1].images;
|
|
519
|
+
const nextImages = Array.isArray(images) && images.length > 0 ? [...images] : Array.isArray(existingImages) && existingImages.length > 0 ? [...existingImages] : existingImages;
|
|
520
|
+
updatedHistory[updatedHistory.length - 1] = {
|
|
521
|
+
...updatedHistory[updatedHistory.length - 1],
|
|
522
|
+
answer,
|
|
523
|
+
memoryUpdated,
|
|
524
|
+
images: nextImages,
|
|
525
|
+
sourceFiles: sourceFiles ?? updatedHistory[updatedHistory.length - 1].sourceFiles,
|
|
526
|
+
cancelled: cancelled ?? updatedHistory[updatedHistory.length - 1].cancelled,
|
|
527
|
+
placeholder: false,
|
|
528
|
+
rawQuestion: void 0
|
|
529
|
+
};
|
|
530
|
+
return normalizeConversation({
|
|
531
|
+
...c,
|
|
532
|
+
history: updatedHistory,
|
|
533
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
return c;
|
|
537
|
+
});
|
|
538
|
+
const updatedConv = updatedConversations.find((c) => c.id === currentId);
|
|
539
|
+
if (updatedConv) {
|
|
540
|
+
saveConversation(updatedConv);
|
|
541
|
+
emitConversationUpsert(updatedConv.id);
|
|
542
|
+
}
|
|
543
|
+
return { conversations: updatedConversations };
|
|
544
|
+
});
|
|
545
|
+
},
|
|
546
|
+
setLastToolTrail: (toolsUsed) => {
|
|
547
|
+
const { currentId } = get();
|
|
548
|
+
if (!currentId) return;
|
|
549
|
+
set((state) => ({
|
|
550
|
+
conversations: state.conversations.map((c) => {
|
|
551
|
+
if (c.id !== currentId || c.history.length === 0) return c;
|
|
552
|
+
const history = [...c.history];
|
|
553
|
+
history[history.length - 1] = {
|
|
554
|
+
...history[history.length - 1],
|
|
555
|
+
toolsUsed: [...toolsUsed]
|
|
556
|
+
};
|
|
557
|
+
return { ...c, history };
|
|
558
|
+
})
|
|
559
|
+
}));
|
|
560
|
+
},
|
|
561
|
+
markEntryMemoryUpdated: (conversationId, entryIndex) => {
|
|
562
|
+
set((state) => {
|
|
563
|
+
let updatedConv;
|
|
564
|
+
const updatedConversations = state.conversations.map((c) => {
|
|
565
|
+
const entry = c.id === conversationId ? c.history[entryIndex] : void 0;
|
|
566
|
+
if (!entry || entry.placeholder || entry.memoryUpdated) return c;
|
|
567
|
+
const history = [...c.history];
|
|
568
|
+
history[entryIndex] = { ...entry, memoryUpdated: true };
|
|
569
|
+
updatedConv = normalizeConversation({ ...c, history, updatedAt: /* @__PURE__ */ new Date() });
|
|
570
|
+
return updatedConv;
|
|
571
|
+
});
|
|
572
|
+
if (!updatedConv) return state;
|
|
573
|
+
saveConversation(updatedConv);
|
|
574
|
+
emitConversationUpsert(updatedConv.id);
|
|
575
|
+
return { conversations: updatedConversations };
|
|
576
|
+
});
|
|
577
|
+
},
|
|
578
|
+
setCurrent: (id) => set({ currentId: id }),
|
|
579
|
+
moveConversationToProject: (conversationId, projectId) => {
|
|
580
|
+
set((state) => {
|
|
581
|
+
const updatedConversations = state.conversations.map(
|
|
582
|
+
(c) => c.id === conversationId ? normalizeConversation({
|
|
583
|
+
...c,
|
|
584
|
+
projectId: projectId || void 0,
|
|
585
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
586
|
+
}) : c
|
|
587
|
+
);
|
|
588
|
+
const updatedConv = updatedConversations.find((c) => c.id === conversationId);
|
|
589
|
+
if (updatedConv) {
|
|
590
|
+
saveConversation(updatedConv);
|
|
591
|
+
}
|
|
592
|
+
return { conversations: updatedConversations };
|
|
593
|
+
});
|
|
594
|
+
},
|
|
595
|
+
clearAllConversations: async () => {
|
|
596
|
+
try {
|
|
597
|
+
await indexedDBService_default.clear(DB_NAME3, DB_VERSION3, STORE_NAME3, storeConfigs3);
|
|
598
|
+
set({
|
|
599
|
+
conversations: [],
|
|
600
|
+
currentId: null
|
|
601
|
+
});
|
|
602
|
+
debugLogger.info("All conversations cleared successfully");
|
|
603
|
+
} catch (error) {
|
|
604
|
+
debugLogger.error("Failed to clear all conversations", { error });
|
|
605
|
+
throw error;
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
getConversationsByProject: (projectId) => {
|
|
609
|
+
const { conversations } = get();
|
|
610
|
+
if (projectId === null) {
|
|
611
|
+
return conversations.filter((c) => !c.projectId);
|
|
612
|
+
}
|
|
613
|
+
return conversations.filter((c) => c.projectId === projectId);
|
|
614
|
+
},
|
|
615
|
+
applyRemoteConversations: async (incoming) => {
|
|
616
|
+
const normalized = incoming.map(normalizeConversation);
|
|
617
|
+
const toPersist = [];
|
|
618
|
+
set((state) => {
|
|
619
|
+
const next = new Map(state.conversations.map((c) => [c.id, c]));
|
|
620
|
+
for (const conversation of normalized) {
|
|
621
|
+
const existing = next.get(conversation.id);
|
|
622
|
+
if (existing) {
|
|
623
|
+
const existingUpdatedAt = existing.updatedAt ? existing.updatedAt.getTime() : 0;
|
|
624
|
+
const incomingUpdatedAt = conversation.updatedAt ? conversation.updatedAt.getTime() : 0;
|
|
625
|
+
const existingHistoryLength = existing.history?.length ?? 0;
|
|
626
|
+
const incomingHistoryLength = conversation.history?.length ?? 0;
|
|
627
|
+
const incomingHasMoreHistory = incomingHistoryLength > existingHistoryLength;
|
|
628
|
+
const incomingIsNewer = incomingUpdatedAt > existingUpdatedAt;
|
|
629
|
+
if (incomingIsNewer && incomingHistoryLength < existingHistoryLength) {
|
|
630
|
+
debugLogger.info(
|
|
631
|
+
"applyRemoteConversations: preserving local history over shorter incoming",
|
|
632
|
+
{
|
|
633
|
+
conversationId: conversation.id,
|
|
634
|
+
existingHistoryLength,
|
|
635
|
+
incomingHistoryLength,
|
|
636
|
+
existingUpdatedAt,
|
|
637
|
+
incomingUpdatedAt
|
|
638
|
+
}
|
|
639
|
+
);
|
|
640
|
+
conversation.history = existing.history;
|
|
641
|
+
}
|
|
642
|
+
if (!incomingHasMoreHistory && !incomingIsNewer) {
|
|
643
|
+
debugLogger.info("applyRemoteConversations: skipping stale conversation", {
|
|
644
|
+
conversationId: conversation.id,
|
|
645
|
+
existingHistoryLength,
|
|
646
|
+
incomingHistoryLength,
|
|
647
|
+
existingUpdatedAt,
|
|
648
|
+
incomingUpdatedAt
|
|
649
|
+
});
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
if (Array.isArray(existing.history) && Array.isArray(conversation.history)) {
|
|
653
|
+
const mergedHistory = conversation.history.map((incomingEntry, index) => {
|
|
654
|
+
const existingEntry = existing.history[index];
|
|
655
|
+
if (!existingEntry) {
|
|
656
|
+
return incomingEntry;
|
|
657
|
+
}
|
|
658
|
+
const mergedImagesSource = Array.isArray(incomingEntry.images) && incomingEntry.images.length > 0 ? incomingEntry.images : existingEntry.images;
|
|
659
|
+
const mergedImages = Array.isArray(mergedImagesSource) && mergedImagesSource.length > 0 ? [...mergedImagesSource] : mergedImagesSource;
|
|
660
|
+
return {
|
|
661
|
+
...existingEntry,
|
|
662
|
+
...incomingEntry,
|
|
663
|
+
images: mergedImages,
|
|
664
|
+
placeholder: incomingEntry.placeholder ?? existingEntry.placeholder,
|
|
665
|
+
rawQuestion: incomingEntry.rawQuestion ?? existingEntry.rawQuestion
|
|
666
|
+
};
|
|
667
|
+
});
|
|
668
|
+
conversation.history = mergedHistory;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
next.set(conversation.id, conversation);
|
|
672
|
+
toPersist.push(conversation);
|
|
673
|
+
}
|
|
674
|
+
return { conversations: Array.from(next.values()) };
|
|
675
|
+
});
|
|
676
|
+
if (toPersist.length > 0) {
|
|
677
|
+
try {
|
|
678
|
+
await Promise.all(
|
|
679
|
+
toPersist.map((conversation) => saveConversation(conversation))
|
|
680
|
+
);
|
|
681
|
+
} catch (error) {
|
|
682
|
+
debugLogger.error("Failed to persist remote conversations", {
|
|
683
|
+
error,
|
|
684
|
+
conversationIds: toPersist.map((c) => c.id)
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
removeConversationsByIds: async (ids) => {
|
|
690
|
+
if (!ids.length) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
await Promise.all(ids.map((id) => deleteConversationFromDB(id)));
|
|
695
|
+
} catch (error) {
|
|
696
|
+
debugLogger.error("Failed to remove conversations from IndexedDB", { error, ids });
|
|
697
|
+
}
|
|
698
|
+
set((state) => {
|
|
699
|
+
const filtered = state.conversations.filter((c) => !ids.includes(c.id));
|
|
700
|
+
const isCurrentDeleted = state.currentId ? ids.includes(state.currentId) : false;
|
|
701
|
+
ids.forEach((id) => emitConversationDelete(id));
|
|
702
|
+
return {
|
|
703
|
+
conversations: filtered,
|
|
704
|
+
currentId: isCurrentDeleted ? null : state.currentId
|
|
705
|
+
};
|
|
706
|
+
});
|
|
707
|
+
try {
|
|
708
|
+
await get().hydrate();
|
|
709
|
+
} catch (error) {
|
|
710
|
+
debugLogger.warn("conversationStore: hydrate failed after remote delete", {
|
|
711
|
+
error: error instanceof Error ? error.message : String(error)
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}));
|
|
716
|
+
function normalizeConversation(conversation) {
|
|
717
|
+
const ensureDate = (value) => {
|
|
718
|
+
if (!value) return void 0;
|
|
719
|
+
if (value instanceof Date) return value;
|
|
720
|
+
const parsed = new Date(value);
|
|
721
|
+
return Number.isNaN(parsed.getTime()) ? void 0 : parsed;
|
|
722
|
+
};
|
|
723
|
+
return {
|
|
724
|
+
...conversation,
|
|
725
|
+
name: sanitizeConversationName(conversation.name),
|
|
726
|
+
createdAt: ensureDate(conversation.createdAt) ?? /* @__PURE__ */ new Date(),
|
|
727
|
+
updatedAt: ensureDate(conversation.updatedAt) ?? /* @__PURE__ */ new Date(),
|
|
728
|
+
summaryGeneratedAt: ensureDate(conversation.summaryGeneratedAt),
|
|
729
|
+
history: Array.isArray(conversation.history) ? conversation.history.map((turn) => ({
|
|
730
|
+
...turn
|
|
731
|
+
})) : []
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
function emitConversationUpsert(id) {
|
|
735
|
+
if (typeof window === "undefined") return;
|
|
736
|
+
window.dispatchEvent(new CustomEvent(CONVERSATION_UPSERT_EVENT, { detail: id }));
|
|
737
|
+
}
|
|
738
|
+
function emitConversationDelete(id) {
|
|
739
|
+
if (typeof window === "undefined") return;
|
|
740
|
+
window.dispatchEvent(new CustomEvent(CONVERSATION_DELETE_EVENT, { detail: id }));
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// src/store/projectStore.ts
|
|
744
|
+
import { create as create3 } from "zustand";
|
|
745
|
+
var DB_NAME4 = "bandit-projects";
|
|
746
|
+
var STORE_NAME4 = "projects";
|
|
747
|
+
var DB_VERSION4 = 1;
|
|
748
|
+
var storeConfigs4 = [{ name: STORE_NAME4, keyPath: "id" }];
|
|
749
|
+
var DEFAULT_COLORS = [
|
|
750
|
+
"#2196F3",
|
|
751
|
+
// Blue
|
|
752
|
+
"#4CAF50",
|
|
753
|
+
// Green
|
|
754
|
+
"#FF9800",
|
|
755
|
+
// Orange
|
|
756
|
+
"#9C27B0",
|
|
757
|
+
// Purple
|
|
758
|
+
"#F44336",
|
|
759
|
+
// Red
|
|
760
|
+
"#00BCD4",
|
|
761
|
+
// Cyan
|
|
762
|
+
"#FFEB3B",
|
|
763
|
+
// Yellow
|
|
764
|
+
"#795548",
|
|
765
|
+
// Brown
|
|
766
|
+
"#607D8B",
|
|
767
|
+
// Blue Grey
|
|
768
|
+
"#E91E63"
|
|
769
|
+
// Pink
|
|
770
|
+
];
|
|
771
|
+
async function loadProjects() {
|
|
772
|
+
try {
|
|
773
|
+
const projects = await indexedDBService_default.getAll(
|
|
774
|
+
DB_NAME4,
|
|
775
|
+
DB_VERSION4,
|
|
776
|
+
STORE_NAME4,
|
|
777
|
+
storeConfigs4
|
|
778
|
+
);
|
|
779
|
+
return (projects || []).map(normalizeProject).sort((a, b) => a.order - b.order);
|
|
780
|
+
} catch (error) {
|
|
781
|
+
debugLogger.error("Failed to load projects", { error });
|
|
782
|
+
return [];
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
async function saveProject(project) {
|
|
786
|
+
try {
|
|
787
|
+
await indexedDBService_default.put(DB_NAME4, DB_VERSION4, STORE_NAME4, project, storeConfigs4);
|
|
788
|
+
debugLogger.info("Project saved", {
|
|
789
|
+
projectId: project.id,
|
|
790
|
+
projectName: project.name
|
|
791
|
+
});
|
|
792
|
+
} catch (error) {
|
|
793
|
+
debugLogger.error("Failed to save project", { projectId: project.id, error });
|
|
794
|
+
throw error;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
async function deleteProjectFromDB(id) {
|
|
798
|
+
try {
|
|
799
|
+
await indexedDBService_default.delete(DB_NAME4, DB_VERSION4, STORE_NAME4, id, storeConfigs4);
|
|
800
|
+
debugLogger.info("Project deleted from DB", { projectId: id });
|
|
801
|
+
} catch (error) {
|
|
802
|
+
debugLogger.error("Failed to delete project from DB", { projectId: id, error });
|
|
803
|
+
throw error;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
var useProjectStore = create3((set, get) => ({
|
|
807
|
+
projects: [],
|
|
808
|
+
_hasHydrated: false,
|
|
809
|
+
hydrate: async () => {
|
|
810
|
+
try {
|
|
811
|
+
const projects = await loadProjects();
|
|
812
|
+
set({ projects, _hasHydrated: true });
|
|
813
|
+
debugLogger.info("Project store hydrated", { projectCount: projects.length });
|
|
814
|
+
} catch (error) {
|
|
815
|
+
debugLogger.error("Failed to hydrate project store", { error });
|
|
816
|
+
set({ projects: [], _hasHydrated: true });
|
|
817
|
+
}
|
|
818
|
+
},
|
|
819
|
+
createProject: async (name, description, color) => {
|
|
820
|
+
const { projects } = get();
|
|
821
|
+
const id = newId();
|
|
822
|
+
const now = /* @__PURE__ */ new Date();
|
|
823
|
+
const projectColor = color || DEFAULT_COLORS[projects.length % DEFAULT_COLORS.length];
|
|
824
|
+
const newProject = normalizeProject({
|
|
825
|
+
id,
|
|
826
|
+
name: name.trim(),
|
|
827
|
+
description: description?.trim(),
|
|
828
|
+
color: projectColor,
|
|
829
|
+
createdAt: now,
|
|
830
|
+
updatedAt: now,
|
|
831
|
+
order: projects.length,
|
|
832
|
+
version: 0
|
|
833
|
+
});
|
|
834
|
+
await saveProject(newProject);
|
|
835
|
+
set((state) => ({
|
|
836
|
+
projects: [...state.projects, newProject]
|
|
837
|
+
}));
|
|
838
|
+
emitProjectUpsert(id);
|
|
839
|
+
debugLogger.info("Project created", { projectId: id, projectName: name });
|
|
840
|
+
return newProject;
|
|
841
|
+
},
|
|
842
|
+
deleteProject: async (id) => {
|
|
843
|
+
await deleteProjectFromDB(id);
|
|
844
|
+
set((state) => ({
|
|
845
|
+
projects: state.projects.filter((p) => p.id !== id)
|
|
846
|
+
}));
|
|
847
|
+
debugLogger.info("Project deleted", { projectId: id });
|
|
848
|
+
emitProjectDelete(id);
|
|
849
|
+
},
|
|
850
|
+
renameProject: async (id, newName, description) => {
|
|
851
|
+
const { projects } = get();
|
|
852
|
+
const project = projects.find((p) => p.id === id);
|
|
853
|
+
if (!project) {
|
|
854
|
+
throw new Error(`Project with id ${id} not found`);
|
|
855
|
+
}
|
|
856
|
+
const updatedProject = normalizeProject({
|
|
857
|
+
...project,
|
|
858
|
+
name: newName.trim(),
|
|
859
|
+
description: description?.trim(),
|
|
860
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
861
|
+
});
|
|
862
|
+
await saveProject(updatedProject);
|
|
863
|
+
set((state) => ({
|
|
864
|
+
projects: state.projects.map((p) => p.id === id ? updatedProject : p)
|
|
865
|
+
}));
|
|
866
|
+
debugLogger.info("Project renamed", { projectId: id, newName });
|
|
867
|
+
emitProjectUpsert(id);
|
|
868
|
+
},
|
|
869
|
+
updateProjectColor: async (id, color) => {
|
|
870
|
+
const { projects } = get();
|
|
871
|
+
const project = projects.find((p) => p.id === id);
|
|
872
|
+
if (!project) {
|
|
873
|
+
throw new Error(`Project with id ${id} not found`);
|
|
874
|
+
}
|
|
875
|
+
const updatedProject = normalizeProject({
|
|
876
|
+
...project,
|
|
877
|
+
color,
|
|
878
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
879
|
+
});
|
|
880
|
+
await saveProject(updatedProject);
|
|
881
|
+
set((state) => ({
|
|
882
|
+
projects: state.projects.map((p) => p.id === id ? updatedProject : p)
|
|
883
|
+
}));
|
|
884
|
+
debugLogger.info("Project color updated", { projectId: id, color });
|
|
885
|
+
emitProjectUpsert(id);
|
|
886
|
+
},
|
|
887
|
+
updateProjectInstructions: async (id, instructions) => {
|
|
888
|
+
const { projects } = get();
|
|
889
|
+
const project = projects.find((p) => p.id === id);
|
|
890
|
+
if (!project) {
|
|
891
|
+
throw new Error(`Project with id ${id} not found`);
|
|
892
|
+
}
|
|
893
|
+
const updatedProject = normalizeProject({
|
|
894
|
+
...project,
|
|
895
|
+
instructions: instructions.trim() || void 0,
|
|
896
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
897
|
+
});
|
|
898
|
+
await saveProject(updatedProject);
|
|
899
|
+
set((state) => ({
|
|
900
|
+
projects: state.projects.map((p) => p.id === id ? updatedProject : p)
|
|
901
|
+
}));
|
|
902
|
+
debugLogger.info("Project instructions updated", { projectId: id });
|
|
903
|
+
emitProjectUpsert(id);
|
|
904
|
+
},
|
|
905
|
+
reorderProjects: async (projectIds) => {
|
|
906
|
+
const { projects } = get();
|
|
907
|
+
const reorderedProjects = projectIds.map((id, index) => {
|
|
908
|
+
const project = projects.find((p) => p.id === id);
|
|
909
|
+
if (!project) return null;
|
|
910
|
+
return normalizeProject({ ...project, order: index, updatedAt: /* @__PURE__ */ new Date() });
|
|
911
|
+
}).filter(Boolean);
|
|
912
|
+
await Promise.all(reorderedProjects.map(saveProject));
|
|
913
|
+
set({ projects: reorderedProjects });
|
|
914
|
+
debugLogger.info("Projects reordered", { projectCount: reorderedProjects.length });
|
|
915
|
+
reorderedProjects.forEach((project) => emitProjectUpsert(project.id));
|
|
916
|
+
},
|
|
917
|
+
applyRemoteProjects: async (incoming) => {
|
|
918
|
+
const normalized = incoming.map(normalizeProject);
|
|
919
|
+
try {
|
|
920
|
+
await Promise.all(normalized.map((project) => saveProject(project)));
|
|
921
|
+
} catch (error) {
|
|
922
|
+
debugLogger.error("Failed to persist remote projects", { error });
|
|
923
|
+
}
|
|
924
|
+
set((state) => {
|
|
925
|
+
const next = new Map(state.projects.map((p) => [p.id, p]));
|
|
926
|
+
for (const project of normalized) {
|
|
927
|
+
next.set(project.id, project);
|
|
928
|
+
}
|
|
929
|
+
return { projects: Array.from(next.values()).sort((a, b) => a.order - b.order) };
|
|
930
|
+
});
|
|
931
|
+
},
|
|
932
|
+
removeProjectsByIds: async (ids) => {
|
|
933
|
+
if (!ids.length) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
try {
|
|
937
|
+
await Promise.all(ids.map((id) => deleteProjectFromDB(id)));
|
|
938
|
+
} catch (error) {
|
|
939
|
+
debugLogger.error("Failed to delete projects from IndexedDB", { error, ids });
|
|
940
|
+
}
|
|
941
|
+
set((state) => ({
|
|
942
|
+
projects: state.projects.filter((p) => !ids.includes(p.id))
|
|
943
|
+
}));
|
|
944
|
+
ids.forEach((id) => emitProjectDelete(id));
|
|
945
|
+
}
|
|
946
|
+
}));
|
|
947
|
+
function normalizeProject(project) {
|
|
948
|
+
const ensureDate = (value) => {
|
|
949
|
+
if (!value) return /* @__PURE__ */ new Date();
|
|
950
|
+
if (value instanceof Date) return value;
|
|
951
|
+
const parsed = new Date(value);
|
|
952
|
+
return Number.isNaN(parsed.getTime()) ? /* @__PURE__ */ new Date() : parsed;
|
|
953
|
+
};
|
|
954
|
+
return {
|
|
955
|
+
...project,
|
|
956
|
+
createdAt: ensureDate(project.createdAt),
|
|
957
|
+
updatedAt: ensureDate(project.updatedAt),
|
|
958
|
+
lastActivityAt: project.lastActivityAt ? ensureDate(project.lastActivityAt) : void 0
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
function emitProjectUpsert(id) {
|
|
962
|
+
if (typeof window === "undefined") return;
|
|
963
|
+
window.dispatchEvent(new CustomEvent(PROJECT_UPSERT_EVENT, { detail: id }));
|
|
964
|
+
}
|
|
965
|
+
function emitProjectDelete(id) {
|
|
966
|
+
if (typeof window === "undefined") return;
|
|
967
|
+
window.dispatchEvent(new CustomEvent(PROJECT_DELETE_EVENT, { detail: id }));
|
|
968
|
+
}
|
|
969
|
+
var PROJECT_COLORS = DEFAULT_COLORS;
|
|
970
|
+
|
|
971
|
+
// src/types/featureFlags.ts
|
|
972
|
+
var DEFAULT_TIER_FEATURES = {
|
|
973
|
+
basic: {
|
|
974
|
+
memory: true,
|
|
975
|
+
documentKnowledge: false,
|
|
976
|
+
moodAdaptation: false,
|
|
977
|
+
chatSuggestions: true,
|
|
978
|
+
tts: false,
|
|
979
|
+
stt: false,
|
|
980
|
+
adminDashboardEnabled: false,
|
|
981
|
+
limitedAdminDashboard: false,
|
|
982
|
+
semanticSearchSimple: true,
|
|
983
|
+
semanticSearchPremium: false,
|
|
984
|
+
advancedSearch: false,
|
|
985
|
+
// No advanced features for basic
|
|
986
|
+
advancedMemories: false
|
|
987
|
+
// No advanced features for basic
|
|
988
|
+
},
|
|
989
|
+
premium: {
|
|
990
|
+
memory: true,
|
|
991
|
+
documentKnowledge: true,
|
|
992
|
+
moodAdaptation: true,
|
|
993
|
+
chatSuggestions: true,
|
|
994
|
+
tts: true,
|
|
995
|
+
stt: true,
|
|
996
|
+
adminDashboardEnabled: false,
|
|
997
|
+
limitedAdminDashboard: true,
|
|
998
|
+
// Premium gets limited admin access
|
|
999
|
+
semanticSearchSimple: true,
|
|
1000
|
+
semanticSearchPremium: false,
|
|
1001
|
+
// No premium search for limited admin
|
|
1002
|
+
advancedSearch: false,
|
|
1003
|
+
// No vector DB search yet
|
|
1004
|
+
advancedMemories: false
|
|
1005
|
+
// No vector DB memories yet
|
|
1006
|
+
},
|
|
1007
|
+
pro: {
|
|
1008
|
+
memory: true,
|
|
1009
|
+
documentKnowledge: true,
|
|
1010
|
+
moodAdaptation: true,
|
|
1011
|
+
chatSuggestions: true,
|
|
1012
|
+
tts: true,
|
|
1013
|
+
stt: true,
|
|
1014
|
+
adminDashboardEnabled: false,
|
|
1015
|
+
limitedAdminDashboard: true,
|
|
1016
|
+
semanticSearchSimple: true,
|
|
1017
|
+
semanticSearchPremium: true,
|
|
1018
|
+
advancedSearch: true,
|
|
1019
|
+
// Pro gets vector DB search
|
|
1020
|
+
advancedMemories: true
|
|
1021
|
+
// Pro gets vector DB memories
|
|
1022
|
+
},
|
|
1023
|
+
team: {
|
|
1024
|
+
memory: true,
|
|
1025
|
+
documentKnowledge: true,
|
|
1026
|
+
moodAdaptation: true,
|
|
1027
|
+
chatSuggestions: true,
|
|
1028
|
+
tts: true,
|
|
1029
|
+
stt: true,
|
|
1030
|
+
adminDashboardEnabled: true,
|
|
1031
|
+
// Full admin access
|
|
1032
|
+
limitedAdminDashboard: true,
|
|
1033
|
+
semanticSearchSimple: true,
|
|
1034
|
+
semanticSearchPremium: true,
|
|
1035
|
+
advancedSearch: true,
|
|
1036
|
+
// Team gets vector DB search
|
|
1037
|
+
advancedMemories: true
|
|
1038
|
+
// Team gets vector DB memories
|
|
1039
|
+
},
|
|
1040
|
+
trial: {
|
|
1041
|
+
memory: true,
|
|
1042
|
+
documentKnowledge: true,
|
|
1043
|
+
moodAdaptation: true,
|
|
1044
|
+
chatSuggestions: true,
|
|
1045
|
+
tts: true,
|
|
1046
|
+
stt: true,
|
|
1047
|
+
adminDashboardEnabled: false,
|
|
1048
|
+
limitedAdminDashboard: true,
|
|
1049
|
+
// Trial gets limited admin like pro
|
|
1050
|
+
semanticSearchSimple: true,
|
|
1051
|
+
semanticSearchPremium: true,
|
|
1052
|
+
// Trial gets pro-level search
|
|
1053
|
+
advancedSearch: true,
|
|
1054
|
+
// Trial gets pro-level features
|
|
1055
|
+
advancedMemories: true
|
|
1056
|
+
// Trial gets pro-level features
|
|
1057
|
+
},
|
|
1058
|
+
expired: {
|
|
1059
|
+
memory: false,
|
|
1060
|
+
documentKnowledge: false,
|
|
1061
|
+
moodAdaptation: false,
|
|
1062
|
+
chatSuggestions: false,
|
|
1063
|
+
tts: false,
|
|
1064
|
+
stt: false,
|
|
1065
|
+
adminDashboardEnabled: false,
|
|
1066
|
+
limitedAdminDashboard: false,
|
|
1067
|
+
semanticSearchSimple: false,
|
|
1068
|
+
semanticSearchPremium: false,
|
|
1069
|
+
// Expired users get no features
|
|
1070
|
+
advancedSearch: false,
|
|
1071
|
+
// No advanced features
|
|
1072
|
+
advancedMemories: false
|
|
1073
|
+
// No advanced features
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
var OSS_DEFAULT_FEATURES = {
|
|
1077
|
+
memory: true,
|
|
1078
|
+
documentKnowledge: true,
|
|
1079
|
+
moodAdaptation: true,
|
|
1080
|
+
chatSuggestions: true,
|
|
1081
|
+
tts: true,
|
|
1082
|
+
stt: true,
|
|
1083
|
+
adminDashboardEnabled: true,
|
|
1084
|
+
limitedAdminDashboard: true,
|
|
1085
|
+
semanticSearchSimple: true,
|
|
1086
|
+
semanticSearchPremium: true,
|
|
1087
|
+
advancedSearch: true,
|
|
1088
|
+
// OSS gets all features
|
|
1089
|
+
advancedMemories: true
|
|
1090
|
+
// OSS gets all features
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1093
|
+
// src/contexts/FeatureFlagContext.tsx
|
|
1094
|
+
import {
|
|
1095
|
+
createContext,
|
|
1096
|
+
useContext,
|
|
1097
|
+
useEffect,
|
|
1098
|
+
useState,
|
|
1099
|
+
useCallback
|
|
1100
|
+
} from "react";
|
|
1101
|
+
import { jsx } from "react/jsx-runtime";
|
|
1102
|
+
var readSessionToken = (jwtStorageKey) => {
|
|
1103
|
+
const fromStorage = () => {
|
|
1104
|
+
try {
|
|
1105
|
+
return localStorage.getItem(jwtStorageKey);
|
|
1106
|
+
} catch {
|
|
1107
|
+
return null;
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
if (jwtStorageKey && jwtStorageKey !== TOKEN_KEY) {
|
|
1111
|
+
return fromStorage() ?? authenticationService.getToken();
|
|
1112
|
+
}
|
|
1113
|
+
return authenticationService.getToken() ?? fromStorage();
|
|
1114
|
+
};
|
|
1115
|
+
var FeatureFlagContext = createContext(
|
|
1116
|
+
void 0
|
|
1117
|
+
);
|
|
1118
|
+
function decodeJWT(token) {
|
|
1119
|
+
try {
|
|
1120
|
+
const parts = token.split(".");
|
|
1121
|
+
if (parts.length !== 3) return null;
|
|
1122
|
+
const payload = parts[1];
|
|
1123
|
+
const decoded = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
1124
|
+
return JSON.parse(decoded);
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
debugLogger.error("Failed to decode JWT:", { error });
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
function isJWTExpired(payload) {
|
|
1131
|
+
if (!payload.exp) return false;
|
|
1132
|
+
return Date.now() >= payload.exp * 1e3;
|
|
1133
|
+
}
|
|
1134
|
+
var coerceNumber = (value, fallback) => {
|
|
1135
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1136
|
+
return value;
|
|
1137
|
+
}
|
|
1138
|
+
if (typeof value === "string") {
|
|
1139
|
+
const parsed = Number(value);
|
|
1140
|
+
if (Number.isFinite(parsed)) {
|
|
1141
|
+
return parsed;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return fallback;
|
|
1145
|
+
};
|
|
1146
|
+
var coerceBoolean = (value, fallback) => {
|
|
1147
|
+
if (typeof value === "boolean") {
|
|
1148
|
+
return value;
|
|
1149
|
+
}
|
|
1150
|
+
if (typeof value === "string") {
|
|
1151
|
+
return value.toLowerCase() === "true";
|
|
1152
|
+
}
|
|
1153
|
+
if (typeof value === "number") {
|
|
1154
|
+
return value !== 0;
|
|
1155
|
+
}
|
|
1156
|
+
return fallback;
|
|
1157
|
+
};
|
|
1158
|
+
var FeatureFlagProvider = ({
|
|
1159
|
+
config,
|
|
1160
|
+
children
|
|
1161
|
+
}) => {
|
|
1162
|
+
const [evaluation, setEvaluation] = useState(null);
|
|
1163
|
+
const [currentTier, setCurrentTier] = useState(
|
|
1164
|
+
config.subscriptionType
|
|
1165
|
+
);
|
|
1166
|
+
const evaluateFeatures = useCallback(() => {
|
|
1167
|
+
const {
|
|
1168
|
+
subscriptionType,
|
|
1169
|
+
rolesClaimKey = "roles",
|
|
1170
|
+
subscriptionTypeClaimKey = "subscriptionType",
|
|
1171
|
+
isSubscribedClaimKey = "isSubscribed",
|
|
1172
|
+
jwtStorageKey = "bandit-jwt",
|
|
1173
|
+
featureMatrix = {},
|
|
1174
|
+
adminRole = "admin",
|
|
1175
|
+
debug = false
|
|
1176
|
+
} = config;
|
|
1177
|
+
let jwtFound = false;
|
|
1178
|
+
let jwtValid = false;
|
|
1179
|
+
let rolesExtracted = [];
|
|
1180
|
+
let isAdmin = false;
|
|
1181
|
+
let jwtSubscriptionType;
|
|
1182
|
+
let jwtIsSubscribed = true;
|
|
1183
|
+
let jwtTrialUsage;
|
|
1184
|
+
let effectiveTier = currentTier || subscriptionType || "oss";
|
|
1185
|
+
if (typeof window !== "undefined") {
|
|
1186
|
+
try {
|
|
1187
|
+
const token = readSessionToken(jwtStorageKey);
|
|
1188
|
+
if (token) {
|
|
1189
|
+
jwtFound = true;
|
|
1190
|
+
const payload = decodeJWT(token);
|
|
1191
|
+
if (payload && !isJWTExpired(payload)) {
|
|
1192
|
+
jwtValid = true;
|
|
1193
|
+
const roles = payload[rolesClaimKey];
|
|
1194
|
+
if (Array.isArray(roles)) {
|
|
1195
|
+
rolesExtracted = roles.filter(
|
|
1196
|
+
(role) => typeof role === "string"
|
|
1197
|
+
);
|
|
1198
|
+
isAdmin = rolesExtracted.includes(adminRole);
|
|
1199
|
+
}
|
|
1200
|
+
const jwtSubType = payload[subscriptionTypeClaimKey];
|
|
1201
|
+
if (typeof jwtSubType === "string" && ["basic", "premium", "pro", "team", "trial", "expired"].includes(jwtSubType)) {
|
|
1202
|
+
jwtSubscriptionType = jwtSubType;
|
|
1203
|
+
effectiveTier = jwtSubscriptionType;
|
|
1204
|
+
}
|
|
1205
|
+
if (Object.prototype.hasOwnProperty.call(payload, isSubscribedClaimKey)) {
|
|
1206
|
+
jwtIsSubscribed = coerceBoolean(
|
|
1207
|
+
payload[isSubscribedClaimKey],
|
|
1208
|
+
jwtIsSubscribed
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
if (jwtSubscriptionType === "trial") {
|
|
1212
|
+
const chatRequestsUsedRaw = payload.chatRequestsUsed ?? payload.ChatRequestsUsed;
|
|
1213
|
+
const chatRequestsLimitRaw = payload.chatRequestsLimit ?? payload.ChatRequestsLimit;
|
|
1214
|
+
const generateRequestsUsedRaw = payload.generateRequestsUsed ?? payload.GenerateRequestsUsed;
|
|
1215
|
+
const generateRequestsLimitRaw = payload.generateRequestsLimit ?? payload.GenerateRequestsLimit;
|
|
1216
|
+
jwtTrialUsage = {
|
|
1217
|
+
chatRequestsUsed: coerceNumber(chatRequestsUsedRaw, 0),
|
|
1218
|
+
chatRequestsLimit: coerceNumber(chatRequestsLimitRaw, 40),
|
|
1219
|
+
generateRequestsUsed: coerceNumber(generateRequestsUsedRaw, 0),
|
|
1220
|
+
generateRequestsLimit: coerceNumber(generateRequestsLimitRaw, 1e3)
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
if (debug) {
|
|
1227
|
+
debugLogger.error("Error processing JWT for feature flags:", { error });
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
let finalFeatures;
|
|
1232
|
+
if (!subscriptionType && !currentTier && !jwtSubscriptionType) {
|
|
1233
|
+
finalFeatures = { ...OSS_DEFAULT_FEATURES, ...featureMatrix };
|
|
1234
|
+
effectiveTier = "oss";
|
|
1235
|
+
} else if (isAdmin) {
|
|
1236
|
+
finalFeatures = { ...OSS_DEFAULT_FEATURES };
|
|
1237
|
+
} else {
|
|
1238
|
+
const tierFeatures = effectiveTier !== "oss" ? DEFAULT_TIER_FEATURES[effectiveTier] : OSS_DEFAULT_FEATURES;
|
|
1239
|
+
finalFeatures = { ...tierFeatures, ...featureMatrix };
|
|
1240
|
+
}
|
|
1241
|
+
const result = {
|
|
1242
|
+
tier: effectiveTier,
|
|
1243
|
+
isAdmin,
|
|
1244
|
+
isSubscribed: jwtIsSubscribed,
|
|
1245
|
+
trialUsage: jwtTrialUsage,
|
|
1246
|
+
features: finalFeatures,
|
|
1247
|
+
metadata: {
|
|
1248
|
+
jwtFound,
|
|
1249
|
+
jwtValid,
|
|
1250
|
+
rolesExtracted,
|
|
1251
|
+
subscriptionTypeFromJWT: jwtSubscriptionType,
|
|
1252
|
+
isSubscribedFromJWT: jwtIsSubscribed,
|
|
1253
|
+
trialUsageFromJWT: jwtTrialUsage,
|
|
1254
|
+
evaluatedAt: Date.now()
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
if (debug) {
|
|
1258
|
+
debugLogger.info("Feature flag evaluation:", result);
|
|
1259
|
+
}
|
|
1260
|
+
return result;
|
|
1261
|
+
}, [config, currentTier]);
|
|
1262
|
+
const refreshEvaluation = useCallback(() => {
|
|
1263
|
+
const newEvaluation = evaluateFeatures();
|
|
1264
|
+
setEvaluation(newEvaluation);
|
|
1265
|
+
}, [evaluateFeatures]);
|
|
1266
|
+
useEffect(() => {
|
|
1267
|
+
refreshEvaluation();
|
|
1268
|
+
const handleStorageChange = (e) => {
|
|
1269
|
+
if (e.key === config.jwtStorageKey || e.key === null) {
|
|
1270
|
+
refreshEvaluation();
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
const handleTierUpdate = (e) => {
|
|
1274
|
+
if (e.detail?.tier) {
|
|
1275
|
+
setCurrentTier(e.detail.tier);
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
const handleSessionChange = () => refreshEvaluation();
|
|
1279
|
+
window.addEventListener("storage", handleStorageChange);
|
|
1280
|
+
window.addEventListener(AUTH_TOKEN_CHANGED_EVENT, handleSessionChange);
|
|
1281
|
+
window.addEventListener("bandit:tier-updated", handleTierUpdate);
|
|
1282
|
+
return () => {
|
|
1283
|
+
window.removeEventListener("storage", handleStorageChange);
|
|
1284
|
+
window.removeEventListener(AUTH_TOKEN_CHANGED_EVENT, handleSessionChange);
|
|
1285
|
+
window.removeEventListener(
|
|
1286
|
+
"bandit:tier-updated",
|
|
1287
|
+
handleTierUpdate
|
|
1288
|
+
);
|
|
1289
|
+
};
|
|
1290
|
+
}, [refreshEvaluation, config.jwtStorageKey]);
|
|
1291
|
+
const contextValue = {
|
|
1292
|
+
hasFeature: (feature) => {
|
|
1293
|
+
return evaluation?.features[feature] ?? false;
|
|
1294
|
+
},
|
|
1295
|
+
getEvaluation: () => evaluation,
|
|
1296
|
+
refreshEvaluation,
|
|
1297
|
+
isAdmin: () => evaluation?.isAdmin ?? false,
|
|
1298
|
+
getCurrentTier: () => evaluation?.tier ?? "oss",
|
|
1299
|
+
updateTier: (tier) => {
|
|
1300
|
+
setCurrentTier(tier);
|
|
1301
|
+
},
|
|
1302
|
+
isOSSMode: () => {
|
|
1303
|
+
if (!evaluation) {
|
|
1304
|
+
return true;
|
|
1305
|
+
}
|
|
1306
|
+
return evaluation.tier === "oss";
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
return /* @__PURE__ */ jsx(FeatureFlagContext.Provider, { value: contextValue, children });
|
|
1310
|
+
};
|
|
1311
|
+
var useFeatureFlag = () => {
|
|
1312
|
+
const context = useContext(FeatureFlagContext);
|
|
1313
|
+
if (context === void 0) {
|
|
1314
|
+
throw new Error("useFeatureFlag must be used within a FeatureFlagProvider");
|
|
1315
|
+
}
|
|
1316
|
+
return context;
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
// src/hooks/useFeatures.ts
|
|
1320
|
+
import { useContext as useContext2 } from "react";
|
|
1321
|
+
var useFeatures = () => {
|
|
1322
|
+
const featureFlag = useContext2(FeatureFlagContext);
|
|
1323
|
+
if (!featureFlag) {
|
|
1324
|
+
return {
|
|
1325
|
+
hasFeature: () => true,
|
|
1326
|
+
hasMemory: () => true,
|
|
1327
|
+
hasDocumentKnowledge: () => true,
|
|
1328
|
+
hasMoodAdaptation: () => true,
|
|
1329
|
+
hasChatSuggestions: () => true,
|
|
1330
|
+
hasTTS: () => true,
|
|
1331
|
+
hasSTT: () => true,
|
|
1332
|
+
hasAdminDashboard: () => true,
|
|
1333
|
+
hasLimitedAdminDashboard: () => true,
|
|
1334
|
+
hasSimpleSearch: () => true,
|
|
1335
|
+
hasPremiumSearch: () => true,
|
|
1336
|
+
hasAdvancedSearch: () => true,
|
|
1337
|
+
hasAdvancedMemories: () => true,
|
|
1338
|
+
isAdmin: () => false,
|
|
1339
|
+
getCurrentTier: () => "oss",
|
|
1340
|
+
isOSSMode: () => true,
|
|
1341
|
+
isSubscribed: () => true,
|
|
1342
|
+
isPremiumTier: () => false,
|
|
1343
|
+
isProTier: () => false,
|
|
1344
|
+
isTrialTier: () => false,
|
|
1345
|
+
isTeamTier: () => false,
|
|
1346
|
+
isExpiredTier: () => false,
|
|
1347
|
+
needsUpgrade: () => false,
|
|
1348
|
+
getTrialUsage: () => void 0,
|
|
1349
|
+
isTrialLimitReached: () => false,
|
|
1350
|
+
getTrialRemainingRequests: () => null,
|
|
1351
|
+
updateTier: () => void 0,
|
|
1352
|
+
refreshFeatures: () => void 0,
|
|
1353
|
+
getFullEvaluation: () => null
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
return {
|
|
1357
|
+
// Core feature checking
|
|
1358
|
+
hasFeature: (feature) => featureFlag.hasFeature(feature),
|
|
1359
|
+
// Convenience methods for common features
|
|
1360
|
+
hasMemory: () => featureFlag.hasFeature("memory"),
|
|
1361
|
+
hasDocumentKnowledge: () => featureFlag.hasFeature("documentKnowledge"),
|
|
1362
|
+
hasMoodAdaptation: () => featureFlag.hasFeature("moodAdaptation"),
|
|
1363
|
+
hasChatSuggestions: () => featureFlag.hasFeature("chatSuggestions"),
|
|
1364
|
+
hasTTS: () => featureFlag.hasFeature("tts"),
|
|
1365
|
+
hasSTT: () => featureFlag.hasFeature("stt"),
|
|
1366
|
+
hasAdminDashboard: () => featureFlag.hasFeature("adminDashboardEnabled"),
|
|
1367
|
+
hasLimitedAdminDashboard: () => featureFlag.hasFeature("limitedAdminDashboard"),
|
|
1368
|
+
hasSimpleSearch: () => featureFlag.hasFeature("semanticSearchSimple"),
|
|
1369
|
+
hasPremiumSearch: () => featureFlag.hasFeature("semanticSearchPremium"),
|
|
1370
|
+
hasAdvancedSearch: () => featureFlag.hasFeature("advancedSearch"),
|
|
1371
|
+
hasAdvancedMemories: () => featureFlag.hasFeature("advancedMemories"),
|
|
1372
|
+
// Admin and tier checking
|
|
1373
|
+
isAdmin: () => featureFlag.isAdmin(),
|
|
1374
|
+
getCurrentTier: () => featureFlag.getCurrentTier(),
|
|
1375
|
+
isOSSMode: () => featureFlag.isOSSMode(),
|
|
1376
|
+
isSubscribed: () => featureFlag.getEvaluation()?.isSubscribed ?? true,
|
|
1377
|
+
// Tier comparison helpers
|
|
1378
|
+
isPremiumTier: () => {
|
|
1379
|
+
const tier = featureFlag.getCurrentTier();
|
|
1380
|
+
return tier === "premium" || tier === "pro" || tier === "team" || tier === "trial";
|
|
1381
|
+
},
|
|
1382
|
+
isProTier: () => {
|
|
1383
|
+
const tier = featureFlag.getCurrentTier();
|
|
1384
|
+
return tier === "pro" || tier === "team" || tier === "trial";
|
|
1385
|
+
},
|
|
1386
|
+
isTrialTier: () => featureFlag.getCurrentTier() === "trial",
|
|
1387
|
+
isTeamTier: () => featureFlag.getCurrentTier() === "team",
|
|
1388
|
+
isExpiredTier: () => featureFlag.getCurrentTier() === "expired",
|
|
1389
|
+
// Upgrade checking
|
|
1390
|
+
needsUpgrade: (feature, requiredTier) => {
|
|
1391
|
+
if (featureFlag.isAdmin() || featureFlag.isOSSMode()) {
|
|
1392
|
+
return false;
|
|
1393
|
+
}
|
|
1394
|
+
if (!featureFlag.hasFeature(feature)) {
|
|
1395
|
+
return requiredTier || "premium";
|
|
1396
|
+
}
|
|
1397
|
+
return false;
|
|
1398
|
+
},
|
|
1399
|
+
// Trial usage tracking
|
|
1400
|
+
getTrialUsage: () => featureFlag.getEvaluation()?.trialUsage,
|
|
1401
|
+
isTrialLimitReached: (type) => {
|
|
1402
|
+
const usage = featureFlag.getEvaluation()?.trialUsage;
|
|
1403
|
+
if (!usage) return false;
|
|
1404
|
+
if (type === "chat") {
|
|
1405
|
+
return usage.chatRequestsUsed >= usage.chatRequestsLimit;
|
|
1406
|
+
} else {
|
|
1407
|
+
return usage.generateRequestsUsed >= usage.generateRequestsLimit;
|
|
1408
|
+
}
|
|
1409
|
+
},
|
|
1410
|
+
getTrialRemainingRequests: (type) => {
|
|
1411
|
+
const usage = featureFlag.getEvaluation()?.trialUsage;
|
|
1412
|
+
if (!usage) return null;
|
|
1413
|
+
if (type === "chat") {
|
|
1414
|
+
return Math.max(0, usage.chatRequestsLimit - usage.chatRequestsUsed);
|
|
1415
|
+
} else {
|
|
1416
|
+
return Math.max(0, usage.generateRequestsLimit - usage.generateRequestsUsed);
|
|
1417
|
+
}
|
|
1418
|
+
},
|
|
1419
|
+
// Management
|
|
1420
|
+
updateTier: (tier) => featureFlag.updateTier(tier),
|
|
1421
|
+
refreshFeatures: () => featureFlag.refreshEvaluation(),
|
|
1422
|
+
// Full evaluation access
|
|
1423
|
+
getFullEvaluation: () => featureFlag.getEvaluation()
|
|
1424
|
+
};
|
|
1425
|
+
};
|
|
1426
|
+
var useFeatureVisibility = () => {
|
|
1427
|
+
const features = useFeatures();
|
|
1428
|
+
return {
|
|
1429
|
+
// UI visibility helpers
|
|
1430
|
+
showMemoryToggle: () => features.hasMemory() || features.isOSSMode(),
|
|
1431
|
+
showDocumentUpload: () => features.hasDocumentKnowledge() || features.isOSSMode(),
|
|
1432
|
+
showMoodSettings: () => features.hasMoodAdaptation() || features.isOSSMode(),
|
|
1433
|
+
showSuggestions: () => features.hasChatSuggestions() || features.isOSSMode(),
|
|
1434
|
+
showVoiceControls: () => features.hasTTS() || features.hasSTT() || features.isOSSMode(),
|
|
1435
|
+
showAdminPanel: () => features.hasAdminDashboard() || features.isAdmin(),
|
|
1436
|
+
showLimitedAdminPanel: () => features.hasLimitedAdminDashboard() || features.isOSSMode(),
|
|
1437
|
+
showAdvancedSearch: () => features.hasPremiumSearch() || features.isOSSMode(),
|
|
1438
|
+
// Upgrade prompts
|
|
1439
|
+
shouldShowUpgradePrompt: (feature) => {
|
|
1440
|
+
return !features.isOSSMode() && !features.isAdmin() && !features.hasFeature(feature);
|
|
1441
|
+
},
|
|
1442
|
+
getUpgradeMessage: (feature) => {
|
|
1443
|
+
const tier = features.needsUpgrade(feature);
|
|
1444
|
+
if (!tier) return null;
|
|
1445
|
+
const featureNames = {
|
|
1446
|
+
memory: "Conversation Memory",
|
|
1447
|
+
documentKnowledge: "Document Knowledge",
|
|
1448
|
+
moodAdaptation: "Mood Adaptation",
|
|
1449
|
+
chatSuggestions: "Chat Suggestions",
|
|
1450
|
+
tts: "Text-to-Speech",
|
|
1451
|
+
stt: "Speech-to-Text",
|
|
1452
|
+
adminDashboardEnabled: "Admin Dashboard",
|
|
1453
|
+
limitedAdminDashboard: "Limited Admin Dashboard",
|
|
1454
|
+
semanticSearchSimple: "Simple Search",
|
|
1455
|
+
semanticSearchPremium: "Premium Search",
|
|
1456
|
+
advancedSearch: "Advanced Vector Search",
|
|
1457
|
+
advancedMemories: "Advanced Vector Memories"
|
|
1458
|
+
};
|
|
1459
|
+
return `${featureNames[feature]} requires ${tier} subscription`;
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
};
|
|
1463
|
+
|
|
1464
|
+
// src/chat/chat-tokens.tsx
|
|
1465
|
+
import React2 from "react";
|
|
1466
|
+
import { Box, IconButton } from "@mui/material";
|
|
1467
|
+
import { alpha } from "@mui/material/styles";
|
|
1468
|
+
|
|
1469
|
+
// ../../node_modules/@burtson-labs/ui/dist/tokens.js
|
|
1470
|
+
var light = {
|
|
1471
|
+
background: "#fbfafc",
|
|
1472
|
+
foreground: "#17131c",
|
|
1473
|
+
surface: "#ffffff",
|
|
1474
|
+
"surface-raised": "#ffffff",
|
|
1475
|
+
"surface-muted": "#f7f4f9",
|
|
1476
|
+
card: "#ffffff",
|
|
1477
|
+
"card-foreground": "#17131c",
|
|
1478
|
+
popover: "#ffffff",
|
|
1479
|
+
"popover-foreground": "#17131c",
|
|
1480
|
+
primary: "#8f0bc7",
|
|
1481
|
+
"primary-foreground": "#ffffff",
|
|
1482
|
+
secondary: "#f1edf4",
|
|
1483
|
+
"secondary-foreground": "#29212f",
|
|
1484
|
+
muted: "#f4f1f6",
|
|
1485
|
+
"muted-foreground": "#6c6472",
|
|
1486
|
+
accent: "#f3e7fb",
|
|
1487
|
+
"accent-foreground": "#561071",
|
|
1488
|
+
destructive: "#c92d3f",
|
|
1489
|
+
"destructive-foreground": "#ffffff",
|
|
1490
|
+
success: "#0e7a56",
|
|
1491
|
+
"success-foreground": "#ffffff",
|
|
1492
|
+
warning: "#9b5c08",
|
|
1493
|
+
"warning-foreground": "#ffffff",
|
|
1494
|
+
info: "#2563eb",
|
|
1495
|
+
"info-foreground": "#ffffff",
|
|
1496
|
+
recording: "#d6293e",
|
|
1497
|
+
"recording-foreground": "#ffffff",
|
|
1498
|
+
border: "#e5dfe8",
|
|
1499
|
+
"border-strong": "#cfc5d5",
|
|
1500
|
+
input: "#d8cfdd",
|
|
1501
|
+
ring: "#a60ee5",
|
|
1502
|
+
brand: "#a60ee5",
|
|
1503
|
+
"brand-hover": "#8f0bc7",
|
|
1504
|
+
"brand-soft": "#f5e8fc",
|
|
1505
|
+
"brand-soft-foreground": "#5b1476",
|
|
1506
|
+
code: "#18141d",
|
|
1507
|
+
"code-foreground": "#f5f0f7"
|
|
1508
|
+
};
|
|
1509
|
+
var dark = {
|
|
1510
|
+
background: "#0d0d12",
|
|
1511
|
+
foreground: "#f2eef5",
|
|
1512
|
+
surface: "#121218",
|
|
1513
|
+
"surface-raised": "#18171f",
|
|
1514
|
+
"surface-muted": "#17151c",
|
|
1515
|
+
card: "#14131a",
|
|
1516
|
+
"card-foreground": "#f2eef5",
|
|
1517
|
+
popover: "#18171f",
|
|
1518
|
+
"popover-foreground": "#f2eef5",
|
|
1519
|
+
primary: "#8f0bc7",
|
|
1520
|
+
"primary-foreground": "#ffffff",
|
|
1521
|
+
secondary: "#211e28",
|
|
1522
|
+
"secondary-foreground": "#e7e0eb",
|
|
1523
|
+
muted: "#1b1921",
|
|
1524
|
+
"muted-foreground": "#a49ca9",
|
|
1525
|
+
accent: "#291734",
|
|
1526
|
+
"accent-foreground": "#efd7fa",
|
|
1527
|
+
destructive: "#ef5b65",
|
|
1528
|
+
"destructive-foreground": "#1b090b",
|
|
1529
|
+
success: "#36c58d",
|
|
1530
|
+
"success-foreground": "#07160f",
|
|
1531
|
+
warning: "#f3b24c",
|
|
1532
|
+
"warning-foreground": "#201304",
|
|
1533
|
+
info: "#6ea8ff",
|
|
1534
|
+
"info-foreground": "#071226",
|
|
1535
|
+
recording: "#f2616d",
|
|
1536
|
+
"recording-foreground": "#1b090b",
|
|
1537
|
+
border: "#2a2732",
|
|
1538
|
+
"border-strong": "#403a49",
|
|
1539
|
+
input: "#34303d",
|
|
1540
|
+
ring: "#c65ef1",
|
|
1541
|
+
brand: "#c65ef1",
|
|
1542
|
+
"brand-hover": "#d384f3",
|
|
1543
|
+
"brand-soft": "#291734",
|
|
1544
|
+
"brand-soft-foreground": "#efd7fa",
|
|
1545
|
+
code: "#09090d",
|
|
1546
|
+
"code-foreground": "#f5f0f7"
|
|
1547
|
+
};
|
|
1548
|
+
var radius = {
|
|
1549
|
+
xs: "0.375rem",
|
|
1550
|
+
sm: "0.5rem",
|
|
1551
|
+
md: "0.625rem",
|
|
1552
|
+
lg: "0.75rem",
|
|
1553
|
+
xl: "1rem",
|
|
1554
|
+
full: "9999px"
|
|
1555
|
+
};
|
|
1556
|
+
var shadow = {
|
|
1557
|
+
xs: "0 1px 2px rgb(10 8 14 / 0.05)",
|
|
1558
|
+
sm: "0 1px 3px rgb(10 8 14 / 0.08), 0 1px 2px rgb(10 8 14 / 0.05)",
|
|
1559
|
+
md: "0 10px 30px rgb(10 8 14 / 0.10), 0 2px 8px rgb(10 8 14 / 0.06)",
|
|
1560
|
+
focus: "0 0 0 3px color-mix(in srgb, var(--ring) 22%, transparent)"
|
|
1561
|
+
};
|
|
1562
|
+
var fontSans = 'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
1563
|
+
var fontMono = '"JetBrains Mono", "SFMono-Regular", Consolas, "Liberation Mono", monospace';
|
|
1564
|
+
var duration = {
|
|
1565
|
+
fast: 120,
|
|
1566
|
+
standard: 180,
|
|
1567
|
+
emphasis: 240,
|
|
1568
|
+
exit: 140
|
|
1569
|
+
};
|
|
1570
|
+
var easing = {
|
|
1571
|
+
standard: "cubic-bezier(0.2, 0, 0, 1)",
|
|
1572
|
+
emphasized: "cubic-bezier(0.16, 1, 0.3, 1)",
|
|
1573
|
+
exit: "cubic-bezier(0.4, 0, 1, 1)"
|
|
1574
|
+
};
|
|
1575
|
+
var elevation = {
|
|
1576
|
+
0: "none",
|
|
1577
|
+
1: shadow.xs,
|
|
1578
|
+
2: shadow.sm,
|
|
1579
|
+
3: shadow.md
|
|
1580
|
+
};
|
|
1581
|
+
function parseHex(hex) {
|
|
1582
|
+
const h = hex.replace("#", "").trim();
|
|
1583
|
+
const full = h.length === 3 ? [...h].map((c) => c + c).join("") : h.slice(0, 6);
|
|
1584
|
+
if (!/^[0-9a-f]{6}$/i.test(full)) throw new Error(`Not a hex colour: ${hex}`);
|
|
1585
|
+
return [
|
|
1586
|
+
0,
|
|
1587
|
+
2,
|
|
1588
|
+
4
|
|
1589
|
+
].map((i) => parseInt(full.slice(i, i + 2), 16));
|
|
1590
|
+
}
|
|
1591
|
+
var toHex = (rgb) => "#" + rgb.map((v) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, "0")).join("");
|
|
1592
|
+
function mix(a, b, t) {
|
|
1593
|
+
const x = parseHex(a);
|
|
1594
|
+
const y = parseHex(b);
|
|
1595
|
+
return toHex([
|
|
1596
|
+
0,
|
|
1597
|
+
1,
|
|
1598
|
+
2
|
|
1599
|
+
].map((i) => x[i] + (y[i] - x[i]) * t));
|
|
1600
|
+
}
|
|
1601
|
+
function luminance(hex) {
|
|
1602
|
+
const c = parseHex(hex).map((v) => {
|
|
1603
|
+
const s = v / 255;
|
|
1604
|
+
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
1605
|
+
});
|
|
1606
|
+
return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
|
|
1607
|
+
}
|
|
1608
|
+
function contrast(a, b) {
|
|
1609
|
+
const [hi, lo] = [luminance(a), luminance(b)].sort((m, n) => n - m);
|
|
1610
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
1611
|
+
}
|
|
1612
|
+
function ensureContrast(color, against, ratio, target) {
|
|
1613
|
+
let c = color;
|
|
1614
|
+
for (let i = 0; i < 20 && contrast(c, against) < ratio; i++) c = mix(c, target, 0.1);
|
|
1615
|
+
return c;
|
|
1616
|
+
}
|
|
1617
|
+
function accentTokens(accent, mode) {
|
|
1618
|
+
const p = mode === "dark" ? dark : light;
|
|
1619
|
+
const white = "#ffffff";
|
|
1620
|
+
const onFill = contrast(accent, white) >= 2.6 ? white : "#111111";
|
|
1621
|
+
const primary = onFill === white ? ensureContrast(accent, white, 4.5, "#000000") : ensureContrast(accent, "#111111", 4.5, white);
|
|
1622
|
+
const brand = mode === "dark" ? ensureContrast(mix(accent, white, 0.12), p.background, 4.5, white) : ensureContrast(accent, p.background, 4.5, "#000000");
|
|
1623
|
+
const brandHover = mode === "dark" ? mix(brand, white, 0.18) : mix(brand, "#000000", 0.14);
|
|
1624
|
+
const soft = mode === "dark" ? mix(p.background, accent, 0.16) : mix(white, accent, 0.1);
|
|
1625
|
+
const softFg = mode === "dark" ? mix(accent, white, 0.72) : mix(accent, "#000000", 0.55);
|
|
1626
|
+
return {
|
|
1627
|
+
primary,
|
|
1628
|
+
"primary-foreground": onFill,
|
|
1629
|
+
brand,
|
|
1630
|
+
"brand-hover": brandHover,
|
|
1631
|
+
"brand-soft": soft,
|
|
1632
|
+
"brand-soft-foreground": ensureContrast(softFg, soft, 4.5, mode === "dark" ? white : "#000000"),
|
|
1633
|
+
accent: soft,
|
|
1634
|
+
"accent-foreground": ensureContrast(softFg, soft, 4.5, mode === "dark" ? white : "#000000"),
|
|
1635
|
+
ring: brand
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// src/chat/chat-tokens.tsx
|
|
1640
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1641
|
+
function chatTokens(theme) {
|
|
1642
|
+
const own = theme.palette.bandit;
|
|
1643
|
+
if (own) return own;
|
|
1644
|
+
const p = theme.palette;
|
|
1645
|
+
const chat = p.chat;
|
|
1646
|
+
const bg = chat?.shell ?? p.background.default;
|
|
1647
|
+
const fg = chat?.fileText ?? p.text.primary;
|
|
1648
|
+
const muted = chat?.badge ?? p.action.hover;
|
|
1649
|
+
const border = p.divider;
|
|
1650
|
+
const kit = p.mode === "dark" ? dark : light;
|
|
1651
|
+
return {
|
|
1652
|
+
surface: {
|
|
1653
|
+
base: bg,
|
|
1654
|
+
raised: chat?.input ?? p.background.paper,
|
|
1655
|
+
subtle: chat?.file ?? p.background.paper,
|
|
1656
|
+
interactive: muted,
|
|
1657
|
+
interactiveHover: chat?.badgeHover ?? p.action.selected,
|
|
1658
|
+
overlay: p.background.paper,
|
|
1659
|
+
terminal: p.grey[900],
|
|
1660
|
+
terminalForeground: p.grey[100]
|
|
1661
|
+
},
|
|
1662
|
+
composer: {
|
|
1663
|
+
surface: chat?.input ?? p.background.paper,
|
|
1664
|
+
border,
|
|
1665
|
+
borderFocus: p.primary.main,
|
|
1666
|
+
foreground: fg,
|
|
1667
|
+
placeholder: p.text.secondary
|
|
1668
|
+
},
|
|
1669
|
+
control: {
|
|
1670
|
+
surface: muted,
|
|
1671
|
+
surfaceHover: chat?.badgeHover ?? p.action.selected,
|
|
1672
|
+
foreground: fg,
|
|
1673
|
+
foregroundMuted: p.text.secondary,
|
|
1674
|
+
primarySurface: p.primary.main,
|
|
1675
|
+
primaryForeground: p.primary.contrastText
|
|
1676
|
+
},
|
|
1677
|
+
attachment: {
|
|
1678
|
+
surface: chat?.file ?? muted,
|
|
1679
|
+
border,
|
|
1680
|
+
foreground: fg,
|
|
1681
|
+
icon: p.text.secondary
|
|
1682
|
+
},
|
|
1683
|
+
conversation: {
|
|
1684
|
+
user: { surface: muted, foreground: fg, border },
|
|
1685
|
+
assistant: { surface: "transparent", foreground: fg, border: "transparent" },
|
|
1686
|
+
border,
|
|
1687
|
+
divider: border,
|
|
1688
|
+
meta: chat?.caption ?? p.text.secondary,
|
|
1689
|
+
code: { surface: p.grey[900], foreground: p.grey[100] }
|
|
1690
|
+
},
|
|
1691
|
+
agent: {
|
|
1692
|
+
active: p.primary.main,
|
|
1693
|
+
idle: p.text.secondary,
|
|
1694
|
+
surface: alpha(p.primary.main, 0.12)
|
|
1695
|
+
},
|
|
1696
|
+
engine: { accent: p.primary.main, surface: alpha(p.primary.main, 0.12) },
|
|
1697
|
+
tool: { surface: muted, border, foreground: fg },
|
|
1698
|
+
knowledge: { accent: p.info.main, surface: alpha(p.info.main, 0.1) },
|
|
1699
|
+
workspace: { surface: p.background.paper, border },
|
|
1700
|
+
status: {
|
|
1701
|
+
recording: kit.recording,
|
|
1702
|
+
transcribing: p.info.main,
|
|
1703
|
+
thinking: p.primary.main,
|
|
1704
|
+
streaming: p.primary.main,
|
|
1705
|
+
syncing: p.info.main,
|
|
1706
|
+
temporary: p.warning.main,
|
|
1707
|
+
degraded: p.warning.main,
|
|
1708
|
+
disconnected: p.text.secondary,
|
|
1709
|
+
available: p.success.main,
|
|
1710
|
+
premium: p.primary.main,
|
|
1711
|
+
locked: p.text.secondary,
|
|
1712
|
+
info: p.info.main,
|
|
1713
|
+
success: p.success.main,
|
|
1714
|
+
warning: p.warning.main,
|
|
1715
|
+
error: p.error.main
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
var CODE_EXT = /* @__PURE__ */ new Set([
|
|
1720
|
+
"js",
|
|
1721
|
+
"jsx",
|
|
1722
|
+
"ts",
|
|
1723
|
+
"tsx",
|
|
1724
|
+
"py",
|
|
1725
|
+
"java",
|
|
1726
|
+
"c",
|
|
1727
|
+
"cpp",
|
|
1728
|
+
"cs",
|
|
1729
|
+
"go",
|
|
1730
|
+
"rb",
|
|
1731
|
+
"rs",
|
|
1732
|
+
"php",
|
|
1733
|
+
"sh",
|
|
1734
|
+
"sql",
|
|
1735
|
+
"html",
|
|
1736
|
+
"css"
|
|
1737
|
+
]);
|
|
1738
|
+
function fileIconFor(filename) {
|
|
1739
|
+
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
1740
|
+
if (ext === "json" || ext === "yaml" || ext === "yml" || ext === "toml") return Braces;
|
|
1741
|
+
if (ext === "pdf") return FilePdf;
|
|
1742
|
+
if (["png", "jpg", "jpeg", "gif", "webp", "svg", "heic"].includes(ext))
|
|
1743
|
+
return FileImage;
|
|
1744
|
+
if (["csv", "xlsx", "xls", "tsv"].includes(ext)) return Sheet;
|
|
1745
|
+
if (["zip", "gz", "tar", "7z", "rar"].includes(ext)) return Archive;
|
|
1746
|
+
if (CODE_EXT.has(ext)) return FileCode;
|
|
1747
|
+
if (["md", "txt", "docx", "doc", "rtf"].includes(ext)) return FileText;
|
|
1748
|
+
return File;
|
|
1749
|
+
}
|
|
1750
|
+
var monoFont = fontMono;
|
|
1751
|
+
function fileExtensionLabel(filename) {
|
|
1752
|
+
const parts = filename.split(".");
|
|
1753
|
+
return parts.length > 1 ? (parts.pop() ?? "").slice(0, 4).toUpperCase() : "";
|
|
1754
|
+
}
|
|
1755
|
+
var ComposerControl = React2.forwardRef(
|
|
1756
|
+
function ComposerControl2({
|
|
1757
|
+
visualSize,
|
|
1758
|
+
hitSize,
|
|
1759
|
+
surface,
|
|
1760
|
+
surfaceHover,
|
|
1761
|
+
foreground,
|
|
1762
|
+
ring,
|
|
1763
|
+
children,
|
|
1764
|
+
sx,
|
|
1765
|
+
...rest
|
|
1766
|
+
}, ref) {
|
|
1767
|
+
const hit = Math.max(hitSize ?? visualSize, visualSize);
|
|
1768
|
+
return /* @__PURE__ */ jsx2(
|
|
1769
|
+
IconButton,
|
|
1770
|
+
{
|
|
1771
|
+
ref,
|
|
1772
|
+
...rest,
|
|
1773
|
+
disableRipple: true,
|
|
1774
|
+
sx: [
|
|
1775
|
+
(t) => ({
|
|
1776
|
+
width: hit,
|
|
1777
|
+
height: hit,
|
|
1778
|
+
p: 0,
|
|
1779
|
+
borderRadius: "50%",
|
|
1780
|
+
color: foreground,
|
|
1781
|
+
bgcolor: "transparent",
|
|
1782
|
+
"@media (pointer: coarse)": {
|
|
1783
|
+
width: Math.max(hit, 44),
|
|
1784
|
+
height: Math.max(hit, 44)
|
|
1785
|
+
},
|
|
1786
|
+
"& .bandit-control-circle": {
|
|
1787
|
+
width: visualSize,
|
|
1788
|
+
height: visualSize,
|
|
1789
|
+
borderRadius: "50%",
|
|
1790
|
+
display: "grid",
|
|
1791
|
+
placeItems: "center",
|
|
1792
|
+
bgcolor: surface,
|
|
1793
|
+
boxShadow: ring ? `0 0 0 2px ${ring}` : "none",
|
|
1794
|
+
transition: t.transitions.create(["background-color", "box-shadow"], {
|
|
1795
|
+
duration: t.transitions.duration.shorter
|
|
1796
|
+
})
|
|
1797
|
+
},
|
|
1798
|
+
"&:hover": { bgcolor: "transparent" },
|
|
1799
|
+
"&:hover .bandit-control-circle": { bgcolor: surfaceHover },
|
|
1800
|
+
"&.Mui-focusVisible .bandit-control-circle": {
|
|
1801
|
+
boxShadow: `0 0 0 2px ${t.palette.background.default}, 0 0 0 4px ${t.palette.bandit?.composer.borderFocus ?? t.palette.primary.main}`
|
|
1802
|
+
},
|
|
1803
|
+
"&.Mui-disabled": { opacity: 0.5 }
|
|
1804
|
+
}),
|
|
1805
|
+
...Array.isArray(sx) ? sx : sx ? [sx] : []
|
|
1806
|
+
],
|
|
1807
|
+
children: /* @__PURE__ */ jsx2(Box, { component: "span", className: "bandit-control-circle", "aria-hidden": false, children })
|
|
1808
|
+
}
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
);
|
|
1812
|
+
function appBarTokens(theme) {
|
|
1813
|
+
const s = chatTokens(theme);
|
|
1814
|
+
return {
|
|
1815
|
+
background: s.surface.base,
|
|
1816
|
+
border: s.conversation.border,
|
|
1817
|
+
icon: s.control.foreground,
|
|
1818
|
+
iconHover: s.surface.interactive,
|
|
1819
|
+
menuBackground: s.surface.overlay,
|
|
1820
|
+
menuText: s.control.foreground
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
function responseTokens(theme) {
|
|
1824
|
+
const s = chatTokens(theme);
|
|
1825
|
+
return {
|
|
1826
|
+
aiText: s.conversation.assistant.foreground,
|
|
1827
|
+
userBubble: s.conversation.user.surface,
|
|
1828
|
+
memoryText: s.status.success
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
// src/chat/sidebar/sidebarState.ts
|
|
1833
|
+
import { create as create4 } from "zustand";
|
|
1834
|
+
var SIDEBAR_WIDTH = 300;
|
|
1835
|
+
var SIDEBAR_COLLAPSED_KEY = "bandit.sidebar.collapsed";
|
|
1836
|
+
var SIDEBAR_DEFAULT_OPEN_MIN_WIDTH = 1024;
|
|
1837
|
+
var CHAT_VIEW = { kind: "chat" };
|
|
1838
|
+
var readPersistedCollapsed = () => {
|
|
1839
|
+
try {
|
|
1840
|
+
const raw = localStorage.getItem(SIDEBAR_COLLAPSED_KEY);
|
|
1841
|
+
return raw === null ? null : raw === "1";
|
|
1842
|
+
} catch {
|
|
1843
|
+
return null;
|
|
1844
|
+
}
|
|
1845
|
+
};
|
|
1846
|
+
var writePersistedCollapsed = (collapsed) => {
|
|
1847
|
+
try {
|
|
1848
|
+
localStorage.setItem(SIDEBAR_COLLAPSED_KEY, collapsed ? "1" : "0");
|
|
1849
|
+
} catch {
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1852
|
+
var initialSidebarOpen = (viewportWidth = typeof window === "undefined" ? 0 : window.innerWidth) => {
|
|
1853
|
+
const persisted = readPersistedCollapsed();
|
|
1854
|
+
if (persisted !== null) return !persisted;
|
|
1855
|
+
return viewportWidth >= SIDEBAR_DEFAULT_OPEN_MIN_WIDTH;
|
|
1856
|
+
};
|
|
1857
|
+
function viewFromSearch(search) {
|
|
1858
|
+
const params = new URLSearchParams(search);
|
|
1859
|
+
const projectId = params.get("project");
|
|
1860
|
+
if (projectId) return { kind: "project", projectId };
|
|
1861
|
+
if (params.get("view") === "projects") return { kind: "projects" };
|
|
1862
|
+
return CHAT_VIEW;
|
|
1863
|
+
}
|
|
1864
|
+
function searchWithView(search, view) {
|
|
1865
|
+
const params = new URLSearchParams(search);
|
|
1866
|
+
params.delete("view");
|
|
1867
|
+
params.delete("project");
|
|
1868
|
+
if (view.kind === "projects") params.set("view", "projects");
|
|
1869
|
+
if (view.kind === "project") params.set("project", view.projectId);
|
|
1870
|
+
const next = params.toString();
|
|
1871
|
+
return next ? `?${next}` : "";
|
|
1872
|
+
}
|
|
1873
|
+
var sameView = (a, b) => a.kind === b.kind && (a.kind !== "project" || b.kind !== "project" || a.projectId === b.projectId);
|
|
1874
|
+
var useSidebarStore = create4((set) => ({
|
|
1875
|
+
view: CHAT_VIEW,
|
|
1876
|
+
searchFocusRequest: 0,
|
|
1877
|
+
setView: (view) => set({ view }),
|
|
1878
|
+
showChat: () => set({ view: CHAT_VIEW }),
|
|
1879
|
+
showProjects: () => set({ view: { kind: "projects" } }),
|
|
1880
|
+
openProject: (projectId) => set({ view: { kind: "project", projectId } }),
|
|
1881
|
+
requestSearchFocus: () => set((state) => ({ searchFocusRequest: state.searchFocusRequest + 1 }))
|
|
1882
|
+
}));
|
|
1883
|
+
function syncViewWithUrl() {
|
|
1884
|
+
if (typeof window === "undefined") return () => {
|
|
1885
|
+
};
|
|
1886
|
+
const store = useSidebarStore;
|
|
1887
|
+
store.getState().setView(viewFromSearch(window.location.search));
|
|
1888
|
+
const onPop = () => store.getState().setView(viewFromSearch(window.location.search));
|
|
1889
|
+
window.addEventListener("popstate", onPop);
|
|
1890
|
+
const unsubscribe = store.subscribe((state, previous) => {
|
|
1891
|
+
if (sameView(state.view, previous.view)) return;
|
|
1892
|
+
const current = viewFromSearch(window.location.search);
|
|
1893
|
+
if (sameView(current, state.view)) return;
|
|
1894
|
+
const search = searchWithView(window.location.search, state.view);
|
|
1895
|
+
const url = `${window.location.pathname}${search}${window.location.hash}`;
|
|
1896
|
+
window.history.pushState(window.history.state, "", url);
|
|
1897
|
+
});
|
|
1898
|
+
return () => {
|
|
1899
|
+
window.removeEventListener("popstate", onPop);
|
|
1900
|
+
unsubscribe();
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
var useSidebarLayout = () => usePackageSettingsStore((state) => state.settings?.navigation?.sidebar ?? "classic");
|
|
1904
|
+
var useSectionsLayout = () => useSidebarLayout() === "sections";
|
|
1905
|
+
|
|
1906
|
+
// src/services/branding/brandingService.ts
|
|
1907
|
+
var BrandingService = class {
|
|
1908
|
+
DB_NAME = "banditConfig";
|
|
1909
|
+
DB_VERSION = 1;
|
|
1910
|
+
STORE_NAME = "config";
|
|
1911
|
+
STORE_CONFIGS = [{ name: "config", keyPath: "id" }];
|
|
1912
|
+
async getBranding() {
|
|
1913
|
+
try {
|
|
1914
|
+
const data = await indexedDBService_default.get(
|
|
1915
|
+
this.DB_NAME,
|
|
1916
|
+
this.DB_VERSION,
|
|
1917
|
+
this.STORE_NAME,
|
|
1918
|
+
"main",
|
|
1919
|
+
this.STORE_CONFIGS
|
|
1920
|
+
);
|
|
1921
|
+
if (data?.branding && (data.branding.logoBase64 || data.branding.hasTransparentLogo !== void 0 || data.branding.brandingText)) {
|
|
1922
|
+
return {
|
|
1923
|
+
logoBase64: data.branding.logoBase64,
|
|
1924
|
+
hasTransparentLogo: data.branding.hasTransparentLogo,
|
|
1925
|
+
brandingText: data.branding.brandingText
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
return null;
|
|
1929
|
+
} catch (error) {
|
|
1930
|
+
throw error;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
async setBrandingFromConfig(config) {
|
|
1934
|
+
try {
|
|
1935
|
+
const existing = await indexedDBService_default.get(
|
|
1936
|
+
this.DB_NAME,
|
|
1937
|
+
this.DB_VERSION,
|
|
1938
|
+
this.STORE_NAME,
|
|
1939
|
+
"main",
|
|
1940
|
+
this.STORE_CONFIGS
|
|
1941
|
+
) || { id: "main" };
|
|
1942
|
+
if (existing.branding?.userSaved) {
|
|
1943
|
+
debugLogger.debug(
|
|
1944
|
+
"\u{1F6E1}\uFE0F BrandingService: User branding detected, blocking CDN config override"
|
|
1945
|
+
);
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
const hasUserBranding = existing.branding && (existing.branding.logoBase64 || existing.branding.brandingText || existing.branding.theme || existing.branding.hasTransparentLogo !== void 0);
|
|
1949
|
+
if (hasUserBranding) {
|
|
1950
|
+
debugLogger.debug(
|
|
1951
|
+
"\u{1F6E1}\uFE0F BrandingService: Detected user branding data, blocking CDN config override"
|
|
1952
|
+
);
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
debugLogger.debug(
|
|
1956
|
+
"\u{1F527} BrandingService: No user branding detected, applying CDN config"
|
|
1957
|
+
);
|
|
1958
|
+
const branding = config.branding || {};
|
|
1959
|
+
const updated = {
|
|
1960
|
+
...existing,
|
|
1961
|
+
branding: {
|
|
1962
|
+
...existing.branding,
|
|
1963
|
+
...branding
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
await indexedDBService_default.put(
|
|
1967
|
+
this.DB_NAME,
|
|
1968
|
+
this.DB_VERSION,
|
|
1969
|
+
this.STORE_NAME,
|
|
1970
|
+
updated,
|
|
1971
|
+
this.STORE_CONFIGS
|
|
1972
|
+
);
|
|
1973
|
+
} catch (error) {
|
|
1974
|
+
throw error;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
};
|
|
1978
|
+
var brandingService = new BrandingService();
|
|
1979
|
+
var brandingService_default = brandingService;
|
|
1980
|
+
|
|
1981
|
+
// src/hooks/useSafeNavigate.ts
|
|
1982
|
+
import { useRef } from "react";
|
|
1983
|
+
import { useNavigate } from "react-router-dom";
|
|
1984
|
+
var useSafeNavigate = () => {
|
|
1985
|
+
const warned = useRef(false);
|
|
1986
|
+
let navigate = null;
|
|
1987
|
+
try {
|
|
1988
|
+
navigate = useNavigate();
|
|
1989
|
+
} catch (error) {
|
|
1990
|
+
if (!warned.current) {
|
|
1991
|
+
debugLogger.warn("useSafeNavigate: no Router context; falling back to location", {
|
|
1992
|
+
error
|
|
1993
|
+
});
|
|
1994
|
+
warned.current = true;
|
|
1995
|
+
}
|
|
1996
|
+
navigate = null;
|
|
1997
|
+
}
|
|
1998
|
+
return (to) => {
|
|
1999
|
+
if (typeof window !== "undefined" && /^[a-z]+:\/\//i.test(to)) {
|
|
2000
|
+
window.location.href = to;
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
if (navigate) {
|
|
2004
|
+
navigate(to);
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
if (typeof window !== "undefined") {
|
|
2008
|
+
window.location.href = to;
|
|
2009
|
+
}
|
|
2010
|
+
};
|
|
2011
|
+
};
|
|
2012
|
+
|
|
2013
|
+
// src/chat/move-conversation-modal.tsx
|
|
2014
|
+
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
2015
|
+
import {
|
|
2016
|
+
Dialog,
|
|
2017
|
+
DialogTitle,
|
|
2018
|
+
DialogContent,
|
|
2019
|
+
DialogActions,
|
|
2020
|
+
Button,
|
|
2021
|
+
List,
|
|
2022
|
+
ListItem,
|
|
2023
|
+
ListItemButton,
|
|
2024
|
+
ListItemText,
|
|
2025
|
+
ListItemIcon,
|
|
2026
|
+
Typography,
|
|
2027
|
+
Avatar,
|
|
2028
|
+
Radio,
|
|
2029
|
+
Box as Box2,
|
|
2030
|
+
Divider
|
|
2031
|
+
} from "@mui/material";
|
|
2032
|
+
import { useTheme } from "@mui/material/styles";
|
|
2033
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
2034
|
+
var MoveConversationModal = ({
|
|
2035
|
+
open,
|
|
2036
|
+
onClose,
|
|
2037
|
+
conversations,
|
|
2038
|
+
currentProjectId = null
|
|
2039
|
+
}) => {
|
|
2040
|
+
const theme = useTheme();
|
|
2041
|
+
const { projects, _hasHydrated, hydrate } = useProjectStore();
|
|
2042
|
+
const { moveConversationToProject } = useConversationStore();
|
|
2043
|
+
const [selectedProjectId, setSelectedProjectId] = useState2(
|
|
2044
|
+
currentProjectId
|
|
2045
|
+
);
|
|
2046
|
+
useEffect2(() => {
|
|
2047
|
+
if (open && !_hasHydrated) {
|
|
2048
|
+
hydrate();
|
|
2049
|
+
}
|
|
2050
|
+
}, [open, _hasHydrated, hydrate]);
|
|
2051
|
+
useEffect2(() => {
|
|
2052
|
+
setSelectedProjectId(currentProjectId);
|
|
2053
|
+
}, [currentProjectId, open]);
|
|
2054
|
+
const handleMove = async () => {
|
|
2055
|
+
try {
|
|
2056
|
+
await Promise.all(
|
|
2057
|
+
conversations.map(
|
|
2058
|
+
(conv) => moveConversationToProject(conv.id, selectedProjectId)
|
|
2059
|
+
)
|
|
2060
|
+
);
|
|
2061
|
+
onClose();
|
|
2062
|
+
} catch (error) {
|
|
2063
|
+
debugLogger.error("Failed to move conversations", {
|
|
2064
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
const conversationCount = conversations.length;
|
|
2069
|
+
const isMultiple = conversationCount > 1;
|
|
2070
|
+
return /* @__PURE__ */ jsxs(
|
|
2071
|
+
Dialog,
|
|
2072
|
+
{
|
|
2073
|
+
open,
|
|
2074
|
+
onClose,
|
|
2075
|
+
maxWidth: "sm",
|
|
2076
|
+
fullWidth: true,
|
|
2077
|
+
PaperProps: {
|
|
2078
|
+
sx: {
|
|
2079
|
+
bgcolor: theme.palette.background.paper,
|
|
2080
|
+
backgroundImage: "none"
|
|
2081
|
+
}
|
|
2082
|
+
},
|
|
2083
|
+
children: [
|
|
2084
|
+
/* @__PURE__ */ jsxs(DialogTitle, { children: [
|
|
2085
|
+
"Move ",
|
|
2086
|
+
isMultiple ? `${conversationCount} Conversations` : "Conversation"
|
|
2087
|
+
] }),
|
|
2088
|
+
/* @__PURE__ */ jsxs(DialogContent, { sx: { px: 3 }, children: [
|
|
2089
|
+
/* @__PURE__ */ jsx3(Typography, { variant: "body2", color: "text.secondary", sx: { mb: 2 }, children: isMultiple ? `Select a project to move ${conversationCount} conversations to:` : `Select a project to move "${conversations[0]?.name}" to:` }),
|
|
2090
|
+
/* @__PURE__ */ jsxs(List, { children: [
|
|
2091
|
+
/* @__PURE__ */ jsx3(ListItem, { disablePadding: true, children: /* @__PURE__ */ jsxs(
|
|
2092
|
+
ListItemButton,
|
|
2093
|
+
{
|
|
2094
|
+
onClick: () => setSelectedProjectId(null),
|
|
2095
|
+
selected: selectedProjectId === null,
|
|
2096
|
+
children: [
|
|
2097
|
+
/* @__PURE__ */ jsx3(ListItemIcon, { children: /* @__PURE__ */ jsx3(
|
|
2098
|
+
Radio,
|
|
2099
|
+
{
|
|
2100
|
+
checked: selectedProjectId === null,
|
|
2101
|
+
onChange: () => setSelectedProjectId(null),
|
|
2102
|
+
size: "small"
|
|
2103
|
+
}
|
|
2104
|
+
) }),
|
|
2105
|
+
/* @__PURE__ */ jsx3(ListItemIcon, { children: /* @__PURE__ */ jsx3(
|
|
2106
|
+
Avatar,
|
|
2107
|
+
{
|
|
2108
|
+
sx: {
|
|
2109
|
+
bgcolor: theme.palette.grey[400],
|
|
2110
|
+
width: 32,
|
|
2111
|
+
height: 32
|
|
2112
|
+
},
|
|
2113
|
+
children: /* @__PURE__ */ jsx3(Inbox, {})
|
|
2114
|
+
}
|
|
2115
|
+
) }),
|
|
2116
|
+
/* @__PURE__ */ jsx3(
|
|
2117
|
+
ListItemText,
|
|
2118
|
+
{
|
|
2119
|
+
primary: "No Project",
|
|
2120
|
+
secondary: "Keep conversations ungrouped"
|
|
2121
|
+
}
|
|
2122
|
+
)
|
|
2123
|
+
]
|
|
2124
|
+
}
|
|
2125
|
+
) }),
|
|
2126
|
+
/* @__PURE__ */ jsx3(Divider, { sx: { my: 1 } }),
|
|
2127
|
+
projects.map((project) => /* @__PURE__ */ jsx3(ListItem, { disablePadding: true, children: /* @__PURE__ */ jsxs(
|
|
2128
|
+
ListItemButton,
|
|
2129
|
+
{
|
|
2130
|
+
onClick: () => setSelectedProjectId(project.id),
|
|
2131
|
+
selected: selectedProjectId === project.id,
|
|
2132
|
+
children: [
|
|
2133
|
+
/* @__PURE__ */ jsx3(ListItemIcon, { children: /* @__PURE__ */ jsx3(
|
|
2134
|
+
Radio,
|
|
2135
|
+
{
|
|
2136
|
+
checked: selectedProjectId === project.id,
|
|
2137
|
+
onChange: () => setSelectedProjectId(project.id),
|
|
2138
|
+
size: "small"
|
|
2139
|
+
}
|
|
2140
|
+
) }),
|
|
2141
|
+
/* @__PURE__ */ jsx3(ListItemIcon, { children: /* @__PURE__ */ jsx3(
|
|
2142
|
+
Avatar,
|
|
2143
|
+
{
|
|
2144
|
+
sx: {
|
|
2145
|
+
bgcolor: project.color,
|
|
2146
|
+
width: 32,
|
|
2147
|
+
height: 32
|
|
2148
|
+
},
|
|
2149
|
+
children: /* @__PURE__ */ jsx3(Folder, {})
|
|
2150
|
+
}
|
|
2151
|
+
) }),
|
|
2152
|
+
/* @__PURE__ */ jsx3(ListItemText, { primary: project.name, secondary: project.description })
|
|
2153
|
+
]
|
|
2154
|
+
}
|
|
2155
|
+
) }, project.id)),
|
|
2156
|
+
projects.length === 0 && /* @__PURE__ */ jsx3(
|
|
2157
|
+
Box2,
|
|
2158
|
+
{
|
|
2159
|
+
sx: {
|
|
2160
|
+
textAlign: "center",
|
|
2161
|
+
py: 2,
|
|
2162
|
+
color: theme.palette.text.secondary
|
|
2163
|
+
},
|
|
2164
|
+
children: /* @__PURE__ */ jsx3(Typography, { variant: "body2", children: "No projects available. Create a project first to organize conversations." })
|
|
2165
|
+
}
|
|
2166
|
+
)
|
|
2167
|
+
] })
|
|
2168
|
+
] }),
|
|
2169
|
+
/* @__PURE__ */ jsxs(DialogActions, { sx: { px: 3, pb: 2 }, children: [
|
|
2170
|
+
/* @__PURE__ */ jsx3(Button, { onClick: onClose, children: "Cancel" }),
|
|
2171
|
+
/* @__PURE__ */ jsxs(
|
|
2172
|
+
Button,
|
|
2173
|
+
{
|
|
2174
|
+
onClick: handleMove,
|
|
2175
|
+
variant: "contained",
|
|
2176
|
+
disabled: selectedProjectId === currentProjectId,
|
|
2177
|
+
children: [
|
|
2178
|
+
"Move ",
|
|
2179
|
+
isMultiple ? "Conversations" : "Conversation"
|
|
2180
|
+
]
|
|
2181
|
+
}
|
|
2182
|
+
)
|
|
2183
|
+
] })
|
|
2184
|
+
]
|
|
2185
|
+
}
|
|
2186
|
+
);
|
|
2187
|
+
};
|
|
2188
|
+
var move_conversation_modal_default = MoveConversationModal;
|
|
2189
|
+
|
|
2190
|
+
// src/chat/simple-conversation-item.tsx
|
|
2191
|
+
import { useState as useState3, useRef as useRef2, useEffect as useEffect3 } from "react";
|
|
2192
|
+
import {
|
|
2193
|
+
Box as Box3,
|
|
2194
|
+
Typography as Typography2,
|
|
2195
|
+
IconButton as IconButton2,
|
|
2196
|
+
Menu,
|
|
2197
|
+
MenuItem,
|
|
2198
|
+
ListItemIcon as ListItemIcon2,
|
|
2199
|
+
ListItemText as ListItemText2,
|
|
2200
|
+
useMediaQuery,
|
|
2201
|
+
TextField,
|
|
2202
|
+
Dialog as Dialog2,
|
|
2203
|
+
DialogTitle as DialogTitle2,
|
|
2204
|
+
DialogContent as DialogContent2,
|
|
2205
|
+
DialogActions as DialogActions2,
|
|
2206
|
+
Button as Button2
|
|
2207
|
+
} from "@mui/material";
|
|
2208
|
+
import { useTheme as useTheme2, alpha as alpha2 } from "@mui/material/styles";
|
|
2209
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2210
|
+
var SimpleConversationItem = ({
|
|
2211
|
+
conversation,
|
|
2212
|
+
isSelected,
|
|
2213
|
+
onSelect,
|
|
2214
|
+
onDelete,
|
|
2215
|
+
onRename,
|
|
2216
|
+
onMove,
|
|
2217
|
+
projectColor,
|
|
2218
|
+
snippet,
|
|
2219
|
+
searchQuery,
|
|
2220
|
+
onTouchDragStart,
|
|
2221
|
+
onTouchDragMove,
|
|
2222
|
+
onTouchDragEnd,
|
|
2223
|
+
isTouchDragActive,
|
|
2224
|
+
dense = false
|
|
2225
|
+
}) => {
|
|
2226
|
+
const theme = useTheme2();
|
|
2227
|
+
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
|
2228
|
+
const [anchorEl, setAnchorEl] = useState3(null);
|
|
2229
|
+
const [isEditing, setIsEditing] = useState3(false);
|
|
2230
|
+
const [editName, setEditName] = useState3(conversation.name);
|
|
2231
|
+
const [isDragging, setIsDragging] = useState3(false);
|
|
2232
|
+
const [isTouchDragging, setIsTouchDragging] = useState3(false);
|
|
2233
|
+
const [showRenameDialog, setShowRenameDialog] = useState3(false);
|
|
2234
|
+
const longPressTimeoutRef = useRef2(null);
|
|
2235
|
+
const touchStartPointRef = useRef2(null);
|
|
2236
|
+
const activeTouchIdRef = useRef2(null);
|
|
2237
|
+
const suppressClickRef = useRef2(false);
|
|
2238
|
+
const highlightText = (text, query) => {
|
|
2239
|
+
if (!query || !query.trim()) {
|
|
2240
|
+
return text;
|
|
2241
|
+
}
|
|
2242
|
+
const regex = new RegExp(`(${query.trim()})`, "gi");
|
|
2243
|
+
const parts = text.split(regex);
|
|
2244
|
+
return parts.map(
|
|
2245
|
+
(part, index) => regex.test(part) ? /* @__PURE__ */ jsx4(
|
|
2246
|
+
Box3,
|
|
2247
|
+
{
|
|
2248
|
+
component: "span",
|
|
2249
|
+
sx: {
|
|
2250
|
+
bgcolor: alpha2(theme.palette.warning.main, 0.3),
|
|
2251
|
+
color: theme.palette.text.primary,
|
|
2252
|
+
fontWeight: 600,
|
|
2253
|
+
borderRadius: 0.5,
|
|
2254
|
+
px: 0.25
|
|
2255
|
+
},
|
|
2256
|
+
children: part
|
|
2257
|
+
},
|
|
2258
|
+
index
|
|
2259
|
+
) : part
|
|
2260
|
+
);
|
|
2261
|
+
};
|
|
2262
|
+
const handleMenuOpen = (event) => {
|
|
2263
|
+
event.stopPropagation();
|
|
2264
|
+
event.preventDefault();
|
|
2265
|
+
setAnchorEl(event.currentTarget);
|
|
2266
|
+
};
|
|
2267
|
+
const handleMenuClose = () => {
|
|
2268
|
+
setAnchorEl(null);
|
|
2269
|
+
};
|
|
2270
|
+
const handleEdit = () => {
|
|
2271
|
+
setEditName(conversation.name);
|
|
2272
|
+
if (isMobile) {
|
|
2273
|
+
setShowRenameDialog(true);
|
|
2274
|
+
} else {
|
|
2275
|
+
setIsEditing(true);
|
|
2276
|
+
}
|
|
2277
|
+
handleMenuClose();
|
|
2278
|
+
};
|
|
2279
|
+
const handleMove = () => {
|
|
2280
|
+
if (onMove) {
|
|
2281
|
+
onMove();
|
|
2282
|
+
}
|
|
2283
|
+
handleMenuClose();
|
|
2284
|
+
};
|
|
2285
|
+
const handleDelete = () => {
|
|
2286
|
+
onDelete();
|
|
2287
|
+
handleMenuClose();
|
|
2288
|
+
};
|
|
2289
|
+
const commitRename = () => {
|
|
2290
|
+
const trimmed = editName.trim();
|
|
2291
|
+
if (!trimmed) {
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
if (trimmed !== conversation.name) {
|
|
2295
|
+
onRename?.(trimmed);
|
|
2296
|
+
}
|
|
2297
|
+
setEditName(trimmed);
|
|
2298
|
+
};
|
|
2299
|
+
const handleSaveEdit = () => {
|
|
2300
|
+
commitRename();
|
|
2301
|
+
setIsEditing(false);
|
|
2302
|
+
};
|
|
2303
|
+
const handleCancelEdit = () => {
|
|
2304
|
+
setIsEditing(false);
|
|
2305
|
+
setEditName(conversation.name);
|
|
2306
|
+
};
|
|
2307
|
+
const handleDragStart = (e) => {
|
|
2308
|
+
setIsDragging(true);
|
|
2309
|
+
e.dataTransfer.setData("text/plain", conversation.id);
|
|
2310
|
+
e.dataTransfer.effectAllowed = "move";
|
|
2311
|
+
};
|
|
2312
|
+
const handleDragEnd = () => {
|
|
2313
|
+
setIsDragging(false);
|
|
2314
|
+
};
|
|
2315
|
+
const cancelLongPress = () => {
|
|
2316
|
+
if (longPressTimeoutRef.current !== null) {
|
|
2317
|
+
window.clearTimeout(longPressTimeoutRef.current);
|
|
2318
|
+
longPressTimeoutRef.current = null;
|
|
2319
|
+
}
|
|
2320
|
+
};
|
|
2321
|
+
const handleTouchStart = (e) => {
|
|
2322
|
+
if (!isMobile || isEditing) return;
|
|
2323
|
+
if (e.touches.length === 0) return;
|
|
2324
|
+
const touch = e.touches[0];
|
|
2325
|
+
touchStartPointRef.current = { x: touch.clientX, y: touch.clientY };
|
|
2326
|
+
activeTouchIdRef.current = touch.identifier;
|
|
2327
|
+
suppressClickRef.current = false;
|
|
2328
|
+
cancelLongPress();
|
|
2329
|
+
const initialTouch = touch;
|
|
2330
|
+
longPressTimeoutRef.current = window.setTimeout(() => {
|
|
2331
|
+
if (activeTouchIdRef.current !== null) {
|
|
2332
|
+
setIsTouchDragging(true);
|
|
2333
|
+
onTouchDragStart?.(conversation, initialTouch);
|
|
2334
|
+
}
|
|
2335
|
+
}, 350);
|
|
2336
|
+
};
|
|
2337
|
+
const handleTouchMove = (e) => {
|
|
2338
|
+
if (!isMobile) return;
|
|
2339
|
+
const targetTouch = activeTouchIdRef.current !== null ? Array.from(e.touches).find((t) => t.identifier === activeTouchIdRef.current) : e.touches[0];
|
|
2340
|
+
if (!targetTouch) return;
|
|
2341
|
+
if (isTouchDragging) {
|
|
2342
|
+
if (e.cancelable) {
|
|
2343
|
+
e.preventDefault();
|
|
2344
|
+
}
|
|
2345
|
+
onTouchDragMove?.(targetTouch);
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
const startPoint = touchStartPointRef.current;
|
|
2349
|
+
if (startPoint) {
|
|
2350
|
+
const deltaX = Math.abs(targetTouch.clientX - startPoint.x);
|
|
2351
|
+
const deltaY = Math.abs(targetTouch.clientY - startPoint.y);
|
|
2352
|
+
if (deltaX > 8 || deltaY > 8) {
|
|
2353
|
+
cancelLongPress();
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
};
|
|
2357
|
+
const finalizeTouchDrag = (touch) => {
|
|
2358
|
+
if (isTouchDragging) {
|
|
2359
|
+
onTouchDragEnd?.(touch);
|
|
2360
|
+
suppressClickRef.current = true;
|
|
2361
|
+
}
|
|
2362
|
+
setIsTouchDragging(false);
|
|
2363
|
+
activeTouchIdRef.current = null;
|
|
2364
|
+
touchStartPointRef.current = null;
|
|
2365
|
+
cancelLongPress();
|
|
2366
|
+
};
|
|
2367
|
+
const handleTouchEnd = (e) => {
|
|
2368
|
+
if (!isMobile) return;
|
|
2369
|
+
const touch = activeTouchIdRef.current !== null ? Array.from(e.changedTouches).find(
|
|
2370
|
+
(t) => t.identifier === activeTouchIdRef.current
|
|
2371
|
+
) : e.changedTouches[0];
|
|
2372
|
+
finalizeTouchDrag(touch);
|
|
2373
|
+
};
|
|
2374
|
+
const handleTouchCancel = () => {
|
|
2375
|
+
if (!isMobile) return;
|
|
2376
|
+
finalizeTouchDrag();
|
|
2377
|
+
};
|
|
2378
|
+
useEffect3(() => {
|
|
2379
|
+
if (!isTouchDragActive && isTouchDragging) {
|
|
2380
|
+
setIsTouchDragging(false);
|
|
2381
|
+
}
|
|
2382
|
+
}, [isTouchDragActive, isTouchDragging]);
|
|
2383
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
2384
|
+
/* @__PURE__ */ jsxs2(
|
|
2385
|
+
Box3,
|
|
2386
|
+
{
|
|
2387
|
+
"data-project-id": conversation.projectId ?? "__ungrouped",
|
|
2388
|
+
draggable: !isMobile && !isEditing,
|
|
2389
|
+
onDragStart: handleDragStart,
|
|
2390
|
+
onDragEnd: handleDragEnd,
|
|
2391
|
+
onTouchStart: handleTouchStart,
|
|
2392
|
+
onTouchMove: handleTouchMove,
|
|
2393
|
+
onTouchEnd: handleTouchEnd,
|
|
2394
|
+
onTouchCancel: handleTouchCancel,
|
|
2395
|
+
onClick: isEditing || isTouchDragging ? void 0 : (event) => {
|
|
2396
|
+
if (suppressClickRef.current) {
|
|
2397
|
+
suppressClickRef.current = false;
|
|
2398
|
+
event.preventDefault();
|
|
2399
|
+
event.stopPropagation();
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
onSelect();
|
|
2403
|
+
},
|
|
2404
|
+
sx: {
|
|
2405
|
+
display: "flex",
|
|
2406
|
+
alignItems: "center",
|
|
2407
|
+
px: 2,
|
|
2408
|
+
py: dense ? 0.5 : 1.5,
|
|
2409
|
+
mx: 1,
|
|
2410
|
+
borderRadius: 1,
|
|
2411
|
+
cursor: isEditing || isTouchDragging ? "default" : "pointer",
|
|
2412
|
+
bgcolor: isSelected ? alpha2(projectColor || theme.palette.primary.main, 0.15) : "transparent",
|
|
2413
|
+
border: isSelected ? `1px solid ${alpha2(projectColor || theme.palette.primary.main, 0.3)}` : "1px solid transparent",
|
|
2414
|
+
opacity: isDragging || isTouchDragActive ? 0.55 : 1,
|
|
2415
|
+
transition: "all 0.2s ease",
|
|
2416
|
+
transform: isTouchDragActive ? "scale(0.98)" : "none",
|
|
2417
|
+
boxShadow: isTouchDragActive ? `0 12px 24px ${alpha2(theme.palette.common.black, 0.25)}` : void 0,
|
|
2418
|
+
touchAction: isTouchDragActive ? "none" : void 0,
|
|
2419
|
+
userSelect: isTouchDragging || isTouchDragActive ? "none" : void 0,
|
|
2420
|
+
WebkitUserSelect: isTouchDragging || isTouchDragActive ? "none" : void 0,
|
|
2421
|
+
"&:hover": !isEditing && !isTouchDragging ? {
|
|
2422
|
+
bgcolor: alpha2(theme.palette.text.primary, 0.04)
|
|
2423
|
+
} : {},
|
|
2424
|
+
// Better touch handling on mobile
|
|
2425
|
+
...isMobile && {
|
|
2426
|
+
minHeight: dense ? 44 : 48,
|
|
2427
|
+
// Larger touch target
|
|
2428
|
+
userSelect: "none",
|
|
2429
|
+
WebkitUserSelect: "none",
|
|
2430
|
+
WebkitTouchCallout: "none",
|
|
2431
|
+
"&:active": {
|
|
2432
|
+
bgcolor: alpha2(theme.palette.text.primary, 0.08)
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
},
|
|
2436
|
+
children: [
|
|
2437
|
+
!isMobile && !isEditing && /* @__PURE__ */ jsx4(
|
|
2438
|
+
GripVertical,
|
|
2439
|
+
{
|
|
2440
|
+
size: 16,
|
|
2441
|
+
color: theme.palette.text.disabled,
|
|
2442
|
+
style: { marginRight: 4, cursor: "grab" }
|
|
2443
|
+
}
|
|
2444
|
+
),
|
|
2445
|
+
/* @__PURE__ */ jsxs2(Box3, { sx: { flex: 1, minWidth: 0 }, children: [
|
|
2446
|
+
isEditing ? /* @__PURE__ */ jsx4(
|
|
2447
|
+
TextField,
|
|
2448
|
+
{
|
|
2449
|
+
value: editName,
|
|
2450
|
+
onChange: (e) => setEditName(e.target.value),
|
|
2451
|
+
onBlur: handleSaveEdit,
|
|
2452
|
+
onKeyDown: (e) => {
|
|
2453
|
+
if (e.key === "Enter") {
|
|
2454
|
+
handleSaveEdit();
|
|
2455
|
+
} else if (e.key === "Escape") {
|
|
2456
|
+
handleCancelEdit();
|
|
2457
|
+
}
|
|
2458
|
+
},
|
|
2459
|
+
size: "small",
|
|
2460
|
+
variant: "standard",
|
|
2461
|
+
autoFocus: true,
|
|
2462
|
+
fullWidth: true,
|
|
2463
|
+
sx: {
|
|
2464
|
+
"& .MuiInput-root": {
|
|
2465
|
+
fontSize: "0.875rem"
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
) : /* @__PURE__ */ jsx4(
|
|
2470
|
+
Typography2,
|
|
2471
|
+
{
|
|
2472
|
+
variant: "body2",
|
|
2473
|
+
sx: {
|
|
2474
|
+
fontWeight: isSelected ? 600 : 400,
|
|
2475
|
+
color: isSelected ? projectColor || theme.palette.primary.main : theme.palette.text.primary,
|
|
2476
|
+
overflow: "hidden",
|
|
2477
|
+
textOverflow: "ellipsis",
|
|
2478
|
+
whiteSpace: "nowrap",
|
|
2479
|
+
fontSize: "0.875rem"
|
|
2480
|
+
},
|
|
2481
|
+
children: highlightText(conversation.name, searchQuery)
|
|
2482
|
+
}
|
|
2483
|
+
),
|
|
2484
|
+
!isEditing && snippet && /* @__PURE__ */ jsx4(
|
|
2485
|
+
Typography2,
|
|
2486
|
+
{
|
|
2487
|
+
variant: "caption",
|
|
2488
|
+
sx: {
|
|
2489
|
+
display: "-webkit-box",
|
|
2490
|
+
WebkitLineClamp: 2,
|
|
2491
|
+
WebkitBoxOrient: "vertical",
|
|
2492
|
+
overflow: "hidden",
|
|
2493
|
+
color: alpha2(theme.palette.text.secondary, 0.9),
|
|
2494
|
+
mt: 0.25,
|
|
2495
|
+
lineHeight: 1.3,
|
|
2496
|
+
fontSize: "0.72rem"
|
|
2497
|
+
},
|
|
2498
|
+
title: snippet,
|
|
2499
|
+
children: highlightText(snippet, searchQuery)
|
|
2500
|
+
}
|
|
2501
|
+
)
|
|
2502
|
+
] }),
|
|
2503
|
+
!isEditing && /* @__PURE__ */ jsx4(
|
|
2504
|
+
IconButton2,
|
|
2505
|
+
{
|
|
2506
|
+
onClick: handleMenuOpen,
|
|
2507
|
+
size: "small",
|
|
2508
|
+
sx: {
|
|
2509
|
+
opacity: isMobile ? 1 : isSelected ? 1 : 0,
|
|
2510
|
+
// Always visible on mobile
|
|
2511
|
+
color: theme.palette.text.secondary,
|
|
2512
|
+
ml: 1,
|
|
2513
|
+
minWidth: 32,
|
|
2514
|
+
minHeight: 32,
|
|
2515
|
+
transition: "opacity 0.2s ease",
|
|
2516
|
+
".MuiBox-root:hover &": {
|
|
2517
|
+
opacity: 1
|
|
2518
|
+
},
|
|
2519
|
+
// Larger touch target on mobile
|
|
2520
|
+
...isMobile && {
|
|
2521
|
+
padding: 1,
|
|
2522
|
+
"&:before": {
|
|
2523
|
+
content: '""',
|
|
2524
|
+
position: "absolute",
|
|
2525
|
+
top: -8,
|
|
2526
|
+
left: -8,
|
|
2527
|
+
right: -8,
|
|
2528
|
+
bottom: -8
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
},
|
|
2532
|
+
children: /* @__PURE__ */ jsx4(MoreVertical, { size: 16 })
|
|
2533
|
+
}
|
|
2534
|
+
),
|
|
2535
|
+
/* @__PURE__ */ jsxs2(
|
|
2536
|
+
Menu,
|
|
2537
|
+
{
|
|
2538
|
+
anchorEl,
|
|
2539
|
+
open: Boolean(anchorEl),
|
|
2540
|
+
onClose: handleMenuClose,
|
|
2541
|
+
onClick: (e) => e.stopPropagation(),
|
|
2542
|
+
transformOrigin: {
|
|
2543
|
+
vertical: "top",
|
|
2544
|
+
horizontal: "right"
|
|
2545
|
+
},
|
|
2546
|
+
anchorOrigin: {
|
|
2547
|
+
vertical: "top",
|
|
2548
|
+
horizontal: "right"
|
|
2549
|
+
},
|
|
2550
|
+
PaperProps: {
|
|
2551
|
+
sx: {
|
|
2552
|
+
// Larger menu items on mobile for easier touch
|
|
2553
|
+
...isMobile && {
|
|
2554
|
+
"& .MuiMenuItem-root": {
|
|
2555
|
+
minHeight: 48,
|
|
2556
|
+
fontSize: "1rem"
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
},
|
|
2561
|
+
children: [
|
|
2562
|
+
onRename && /* @__PURE__ */ jsxs2(MenuItem, { onClick: handleEdit, children: [
|
|
2563
|
+
/* @__PURE__ */ jsx4(ListItemIcon2, { children: /* @__PURE__ */ jsx4(Edit, { size: 16 }) }),
|
|
2564
|
+
/* @__PURE__ */ jsx4(ListItemText2, { children: "Rename" })
|
|
2565
|
+
] }),
|
|
2566
|
+
onMove && /* @__PURE__ */ jsxs2(MenuItem, { onClick: handleMove, children: [
|
|
2567
|
+
/* @__PURE__ */ jsx4(ListItemIcon2, { children: /* @__PURE__ */ jsx4(MailOpen, { size: 16 }) }),
|
|
2568
|
+
/* @__PURE__ */ jsx4(ListItemText2, { children: "Move to Project" })
|
|
2569
|
+
] }),
|
|
2570
|
+
/* @__PURE__ */ jsxs2(MenuItem, { onClick: handleDelete, children: [
|
|
2571
|
+
/* @__PURE__ */ jsx4(ListItemIcon2, { children: /* @__PURE__ */ jsx4(Trash, { size: 16 }) }),
|
|
2572
|
+
/* @__PURE__ */ jsx4(ListItemText2, { children: "Delete" })
|
|
2573
|
+
] })
|
|
2574
|
+
]
|
|
2575
|
+
}
|
|
2576
|
+
)
|
|
2577
|
+
]
|
|
2578
|
+
}
|
|
2579
|
+
),
|
|
2580
|
+
isMobile && /* @__PURE__ */ jsxs2(
|
|
2581
|
+
Dialog2,
|
|
2582
|
+
{
|
|
2583
|
+
open: showRenameDialog,
|
|
2584
|
+
onClose: () => {
|
|
2585
|
+
setShowRenameDialog(false);
|
|
2586
|
+
setEditName(conversation.name);
|
|
2587
|
+
},
|
|
2588
|
+
fullWidth: true,
|
|
2589
|
+
PaperProps: {
|
|
2590
|
+
sx: {
|
|
2591
|
+
borderRadius: 3,
|
|
2592
|
+
pb: 1
|
|
2593
|
+
}
|
|
2594
|
+
},
|
|
2595
|
+
children: [
|
|
2596
|
+
/* @__PURE__ */ jsx4(DialogTitle2, { sx: { pb: 1 }, children: "Rename Conversation" }),
|
|
2597
|
+
/* @__PURE__ */ jsx4(DialogContent2, { children: /* @__PURE__ */ jsx4(
|
|
2598
|
+
TextField,
|
|
2599
|
+
{
|
|
2600
|
+
value: editName,
|
|
2601
|
+
onChange: (e) => setEditName(e.target.value),
|
|
2602
|
+
autoFocus: true,
|
|
2603
|
+
fullWidth: true,
|
|
2604
|
+
placeholder: "Enter a new name",
|
|
2605
|
+
InputProps: {
|
|
2606
|
+
sx: { fontSize: "1rem" }
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
) }),
|
|
2610
|
+
/* @__PURE__ */ jsxs2(DialogActions2, { sx: { px: 3, pb: 2 }, children: [
|
|
2611
|
+
/* @__PURE__ */ jsx4(
|
|
2612
|
+
Button2,
|
|
2613
|
+
{
|
|
2614
|
+
onClick: () => {
|
|
2615
|
+
setShowRenameDialog(false);
|
|
2616
|
+
setEditName(conversation.name);
|
|
2617
|
+
},
|
|
2618
|
+
children: "Cancel"
|
|
2619
|
+
}
|
|
2620
|
+
),
|
|
2621
|
+
/* @__PURE__ */ jsx4(
|
|
2622
|
+
Button2,
|
|
2623
|
+
{
|
|
2624
|
+
onClick: () => {
|
|
2625
|
+
commitRename();
|
|
2626
|
+
setShowRenameDialog(false);
|
|
2627
|
+
},
|
|
2628
|
+
variant: "contained",
|
|
2629
|
+
disabled: !editName.trim() || editName.trim() === conversation.name,
|
|
2630
|
+
children: "Save"
|
|
2631
|
+
}
|
|
2632
|
+
)
|
|
2633
|
+
] })
|
|
2634
|
+
]
|
|
2635
|
+
}
|
|
2636
|
+
)
|
|
2637
|
+
] });
|
|
2638
|
+
};
|
|
2639
|
+
var simple_conversation_item_default = SimpleConversationItem;
|
|
2640
|
+
|
|
2641
|
+
// src/chat/sidebar/ProjectDialog.tsx
|
|
2642
|
+
import { useEffect as useEffect4, useState as useState4 } from "react";
|
|
2643
|
+
import {
|
|
2644
|
+
Box as Box4,
|
|
2645
|
+
Button as Button3,
|
|
2646
|
+
Dialog as Dialog3,
|
|
2647
|
+
DialogActions as DialogActions3,
|
|
2648
|
+
DialogContent as DialogContent3,
|
|
2649
|
+
DialogTitle as DialogTitle3,
|
|
2650
|
+
TextField as TextField2,
|
|
2651
|
+
alpha as alpha3
|
|
2652
|
+
} from "@mui/material";
|
|
2653
|
+
import { useTheme as useTheme3 } from "@mui/material/styles";
|
|
2654
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2655
|
+
var ProjectDialog = ({
|
|
2656
|
+
open,
|
|
2657
|
+
onClose,
|
|
2658
|
+
project,
|
|
2659
|
+
onCreated
|
|
2660
|
+
}) => {
|
|
2661
|
+
const theme = useTheme3();
|
|
2662
|
+
const { createProject, renameProject, updateProjectColor, projects } = useProjectStore();
|
|
2663
|
+
const [name, setName] = useState4("");
|
|
2664
|
+
const [color, setColor] = useState4(PROJECT_COLORS[0]);
|
|
2665
|
+
const [saving, setSaving] = useState4(false);
|
|
2666
|
+
const [error, setError] = useState4(null);
|
|
2667
|
+
useEffect4(() => {
|
|
2668
|
+
if (!open) return;
|
|
2669
|
+
setName(project?.name ?? "");
|
|
2670
|
+
setColor(project?.color ?? PROJECT_COLORS[projects.length % PROJECT_COLORS.length]);
|
|
2671
|
+
setError(null);
|
|
2672
|
+
}, [open, project, projects.length]);
|
|
2673
|
+
const trimmed = name.trim();
|
|
2674
|
+
const canSave = trimmed.length > 0 && !saving;
|
|
2675
|
+
const save = async () => {
|
|
2676
|
+
if (!canSave) return;
|
|
2677
|
+
setSaving(true);
|
|
2678
|
+
setError(null);
|
|
2679
|
+
try {
|
|
2680
|
+
if (project) {
|
|
2681
|
+
if (trimmed !== project.name)
|
|
2682
|
+
await renameProject(project.id, trimmed, project.description);
|
|
2683
|
+
if (color !== project.color) await updateProjectColor(project.id, color);
|
|
2684
|
+
} else {
|
|
2685
|
+
const created = await createProject(trimmed, void 0, color);
|
|
2686
|
+
onCreated?.(created);
|
|
2687
|
+
}
|
|
2688
|
+
onClose();
|
|
2689
|
+
} catch (cause) {
|
|
2690
|
+
debugLogger.error("Saving the project failed", {
|
|
2691
|
+
error: cause instanceof Error ? cause.message : String(cause)
|
|
2692
|
+
});
|
|
2693
|
+
setError("The project could not be saved. Try again.");
|
|
2694
|
+
} finally {
|
|
2695
|
+
setSaving(false);
|
|
2696
|
+
}
|
|
2697
|
+
};
|
|
2698
|
+
return /* @__PURE__ */ jsxs3(Dialog3, { open, onClose, maxWidth: "xs", fullWidth: true, children: [
|
|
2699
|
+
/* @__PURE__ */ jsx5(DialogTitle3, { children: project ? "Edit project" : "New project" }),
|
|
2700
|
+
/* @__PURE__ */ jsxs3(DialogContent3, { sx: { display: "flex", flexDirection: "column", gap: 2 }, children: [
|
|
2701
|
+
/* @__PURE__ */ jsx5(
|
|
2702
|
+
TextField2,
|
|
2703
|
+
{
|
|
2704
|
+
autoFocus: true,
|
|
2705
|
+
label: "Name",
|
|
2706
|
+
value: name,
|
|
2707
|
+
onChange: (event) => setName(event.target.value),
|
|
2708
|
+
onKeyDown: (event) => {
|
|
2709
|
+
if (event.key === "Enter") {
|
|
2710
|
+
event.preventDefault();
|
|
2711
|
+
void save();
|
|
2712
|
+
}
|
|
2713
|
+
},
|
|
2714
|
+
error: Boolean(error),
|
|
2715
|
+
helperText: error ?? " ",
|
|
2716
|
+
fullWidth: true,
|
|
2717
|
+
size: "small",
|
|
2718
|
+
inputProps: { maxLength: 80 }
|
|
2719
|
+
}
|
|
2720
|
+
),
|
|
2721
|
+
/* @__PURE__ */ jsx5(
|
|
2722
|
+
Box4,
|
|
2723
|
+
{
|
|
2724
|
+
role: "radiogroup",
|
|
2725
|
+
"aria-label": "Colour",
|
|
2726
|
+
sx: { display: "flex", gap: 1, flexWrap: "wrap" },
|
|
2727
|
+
children: PROJECT_COLORS.map((swatch) => {
|
|
2728
|
+
const selected = swatch === color;
|
|
2729
|
+
return /* @__PURE__ */ jsx5(
|
|
2730
|
+
Box4,
|
|
2731
|
+
{
|
|
2732
|
+
component: "button",
|
|
2733
|
+
type: "button",
|
|
2734
|
+
role: "radio",
|
|
2735
|
+
"aria-checked": selected,
|
|
2736
|
+
"aria-label": `Colour ${swatch}`,
|
|
2737
|
+
onClick: () => setColor(swatch),
|
|
2738
|
+
sx: {
|
|
2739
|
+
width: 28,
|
|
2740
|
+
height: 28,
|
|
2741
|
+
p: 0,
|
|
2742
|
+
borderRadius: "50%",
|
|
2743
|
+
border: `2px solid ${selected ? theme.palette.text.primary : "transparent"}`,
|
|
2744
|
+
bgcolor: swatch,
|
|
2745
|
+
cursor: "pointer",
|
|
2746
|
+
display: "grid",
|
|
2747
|
+
placeItems: "center",
|
|
2748
|
+
color: theme.palette.getContrastText(swatch),
|
|
2749
|
+
"&:focus-visible": {
|
|
2750
|
+
outline: `2px solid ${theme.palette.primary.main}`,
|
|
2751
|
+
outlineOffset: "2px"
|
|
2752
|
+
},
|
|
2753
|
+
"&:hover": { boxShadow: `0 0 0 3px ${alpha3(swatch, 0.35)}` }
|
|
2754
|
+
},
|
|
2755
|
+
children: selected && /* @__PURE__ */ jsx5(Check, { size: 14, "aria-hidden": true })
|
|
2756
|
+
},
|
|
2757
|
+
swatch
|
|
2758
|
+
);
|
|
2759
|
+
})
|
|
2760
|
+
}
|
|
2761
|
+
)
|
|
2762
|
+
] }),
|
|
2763
|
+
/* @__PURE__ */ jsxs3(DialogActions3, { children: [
|
|
2764
|
+
/* @__PURE__ */ jsx5(Button3, { onClick: onClose, children: "Cancel" }),
|
|
2765
|
+
/* @__PURE__ */ jsx5(Button3, { variant: "contained", onClick: () => void save(), disabled: !canSave, children: project ? "Save" : "Create" })
|
|
2766
|
+
] })
|
|
2767
|
+
] });
|
|
2768
|
+
};
|
|
2769
|
+
|
|
2770
|
+
// src/chat/sidebar/recents.ts
|
|
2771
|
+
var BUCKET_LABELS = {
|
|
2772
|
+
today: "Today",
|
|
2773
|
+
week: "Previous 7 days",
|
|
2774
|
+
older: "Older"
|
|
2775
|
+
};
|
|
2776
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
2777
|
+
var asTime = (value) => {
|
|
2778
|
+
if (!value) return 0;
|
|
2779
|
+
const time = value instanceof Date ? value.getTime() : new Date(value).getTime();
|
|
2780
|
+
return Number.isNaN(time) ? 0 : time;
|
|
2781
|
+
};
|
|
2782
|
+
var lastActivity = (conversation) => asTime(conversation.updatedAt) || asTime(conversation.createdAt);
|
|
2783
|
+
var startOfDay = (now) => {
|
|
2784
|
+
const date = new Date(now);
|
|
2785
|
+
date.setHours(0, 0, 0, 0);
|
|
2786
|
+
return date.getTime();
|
|
2787
|
+
};
|
|
2788
|
+
function groupByRecency(conversations, now = Date.now()) {
|
|
2789
|
+
const today = startOfDay(now);
|
|
2790
|
+
const weekAgo = today - 7 * DAY_MS;
|
|
2791
|
+
const sorted = [...conversations].sort((a, b) => lastActivity(b) - lastActivity(a));
|
|
2792
|
+
const buckets = { today: [], week: [], older: [] };
|
|
2793
|
+
for (const conversation of sorted) {
|
|
2794
|
+
const time = lastActivity(conversation);
|
|
2795
|
+
if (time >= today) buckets.today.push(conversation);
|
|
2796
|
+
else if (time >= weekAgo) buckets.week.push(conversation);
|
|
2797
|
+
else buckets.older.push(conversation);
|
|
2798
|
+
}
|
|
2799
|
+
return ["today", "week", "older"].filter((id) => buckets[id].length > 0).map((id) => ({ id, label: BUCKET_LABELS[id], items: buckets[id] }));
|
|
2800
|
+
}
|
|
2801
|
+
var buildSnippet = (text, query, index) => {
|
|
2802
|
+
const start = Math.max(0, index - 40);
|
|
2803
|
+
const end = Math.min(text.length, index + query.length + 60);
|
|
2804
|
+
return text.slice(start, end).replace(/\s+/g, " ").trim();
|
|
2805
|
+
};
|
|
2806
|
+
function searchConversations(conversations, query) {
|
|
2807
|
+
const needle = query.trim().toLowerCase();
|
|
2808
|
+
if (!needle) return [];
|
|
2809
|
+
const matches = [];
|
|
2810
|
+
for (const conversation of conversations) {
|
|
2811
|
+
if (conversation.name.toLowerCase().includes(needle)) {
|
|
2812
|
+
matches.push({ conversation });
|
|
2813
|
+
continue;
|
|
2814
|
+
}
|
|
2815
|
+
for (const entry of conversation.history) {
|
|
2816
|
+
const body = `${entry.question || ""} ${entry.answer || ""}`;
|
|
2817
|
+
const index = body.toLowerCase().indexOf(needle);
|
|
2818
|
+
if (index !== -1) {
|
|
2819
|
+
matches.push({ conversation, snippet: buildSnippet(body, needle, index) });
|
|
2820
|
+
break;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
return matches;
|
|
2825
|
+
}
|
|
2826
|
+
var listable = (conversations) => conversations.filter((conversation) => !conversation.incognito);
|
|
2827
|
+
function formatRelative(time, now = Date.now()) {
|
|
2828
|
+
if (!time) return "";
|
|
2829
|
+
const diff = Math.max(0, now - time);
|
|
2830
|
+
const minute = 60 * 1e3;
|
|
2831
|
+
const hour = 60 * minute;
|
|
2832
|
+
if (diff < minute) return "just now";
|
|
2833
|
+
if (diff < hour) return `${Math.floor(diff / minute)} min ago`;
|
|
2834
|
+
if (diff < DAY_MS) return `${Math.floor(diff / hour)} h ago`;
|
|
2835
|
+
const days = Math.floor((startOfDay(now) - startOfDay(time)) / DAY_MS);
|
|
2836
|
+
if (days <= 1) return "yesterday";
|
|
2837
|
+
if (days < 7) return `${days} days ago`;
|
|
2838
|
+
const date = new Date(time);
|
|
2839
|
+
const sameYear = date.getFullYear() === new Date(now).getFullYear();
|
|
2840
|
+
return date.toLocaleDateString(void 0, {
|
|
2841
|
+
month: "short",
|
|
2842
|
+
day: "numeric",
|
|
2843
|
+
...sameYear ? {} : { year: "numeric" }
|
|
2844
|
+
});
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
// src/chat/sidebar/SidebarProjects.tsx
|
|
2848
|
+
import { useMemo } from "react";
|
|
2849
|
+
import { Box as Box6, IconButton as IconButton3, Tooltip, Typography as Typography4 } from "@mui/material";
|
|
2850
|
+
import { useTheme as useTheme5 } from "@mui/material/styles";
|
|
2851
|
+
|
|
2852
|
+
// src/chat/sidebar/SectionRow.tsx
|
|
2853
|
+
import { forwardRef } from "react";
|
|
2854
|
+
import { Box as Box5, ButtonBase, Typography as Typography3, alpha as alpha4 } from "@mui/material";
|
|
2855
|
+
import { useTheme as useTheme4 } from "@mui/material/styles";
|
|
2856
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2857
|
+
var IS_MAC = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "");
|
|
2858
|
+
var shortcutLabel = (combo) => {
|
|
2859
|
+
if (IS_MAC) {
|
|
2860
|
+
return combo.map((key) => key === "mod" ? "\u2318" : key === "shift" ? "\u21E7" : key).join("");
|
|
2861
|
+
}
|
|
2862
|
+
return combo.map((key) => key === "mod" ? "Ctrl" : key === "shift" ? "Shift" : key).join("+");
|
|
2863
|
+
};
|
|
2864
|
+
var SidebarKbd = ({ children }) => {
|
|
2865
|
+
const theme = useTheme4();
|
|
2866
|
+
return /* @__PURE__ */ jsx6(
|
|
2867
|
+
Box5,
|
|
2868
|
+
{
|
|
2869
|
+
component: "kbd",
|
|
2870
|
+
"aria-hidden": true,
|
|
2871
|
+
sx: {
|
|
2872
|
+
ml: "auto",
|
|
2873
|
+
px: 0.6,
|
|
2874
|
+
py: 0.1,
|
|
2875
|
+
borderRadius: 0.75,
|
|
2876
|
+
border: `1px solid ${alpha4(theme.palette.text.primary, 0.14)}`,
|
|
2877
|
+
color: theme.palette.text.secondary,
|
|
2878
|
+
fontFamily: "inherit",
|
|
2879
|
+
fontSize: "0.68rem",
|
|
2880
|
+
lineHeight: 1.4,
|
|
2881
|
+
whiteSpace: "nowrap",
|
|
2882
|
+
opacity: 0.9
|
|
2883
|
+
},
|
|
2884
|
+
children
|
|
2885
|
+
}
|
|
2886
|
+
);
|
|
2887
|
+
};
|
|
2888
|
+
var SidebarCaption = ({
|
|
2889
|
+
children,
|
|
2890
|
+
action,
|
|
2891
|
+
component = "h2"
|
|
2892
|
+
}) => {
|
|
2893
|
+
const theme = useTheme4();
|
|
2894
|
+
return /* @__PURE__ */ jsxs4(
|
|
2895
|
+
Box5,
|
|
2896
|
+
{
|
|
2897
|
+
sx: {
|
|
2898
|
+
display: "flex",
|
|
2899
|
+
alignItems: "center",
|
|
2900
|
+
gap: 1,
|
|
2901
|
+
px: 2,
|
|
2902
|
+
pt: 1.5,
|
|
2903
|
+
pb: 0.5,
|
|
2904
|
+
minHeight: 32
|
|
2905
|
+
},
|
|
2906
|
+
children: [
|
|
2907
|
+
/* @__PURE__ */ jsx6(
|
|
2908
|
+
Typography3,
|
|
2909
|
+
{
|
|
2910
|
+
component,
|
|
2911
|
+
sx: {
|
|
2912
|
+
m: 0,
|
|
2913
|
+
flex: 1,
|
|
2914
|
+
color: theme.palette.text.secondary,
|
|
2915
|
+
fontSize: "0.7rem",
|
|
2916
|
+
fontWeight: 600,
|
|
2917
|
+
letterSpacing: "0.5px",
|
|
2918
|
+
textTransform: "uppercase",
|
|
2919
|
+
lineHeight: 1.4
|
|
2920
|
+
},
|
|
2921
|
+
children
|
|
2922
|
+
}
|
|
2923
|
+
),
|
|
2924
|
+
action
|
|
2925
|
+
]
|
|
2926
|
+
}
|
|
2927
|
+
);
|
|
2928
|
+
};
|
|
2929
|
+
var SectionRow = forwardRef(function SectionRow2({
|
|
2930
|
+
icon,
|
|
2931
|
+
label,
|
|
2932
|
+
trailing,
|
|
2933
|
+
active = false,
|
|
2934
|
+
href,
|
|
2935
|
+
external,
|
|
2936
|
+
onClick,
|
|
2937
|
+
size = "regular",
|
|
2938
|
+
swatch,
|
|
2939
|
+
...rest
|
|
2940
|
+
}, ref) {
|
|
2941
|
+
const theme = useTheme4();
|
|
2942
|
+
const anchorProps = href ? {
|
|
2943
|
+
component: "a",
|
|
2944
|
+
href,
|
|
2945
|
+
...external ? { rel: "noopener" } : {}
|
|
2946
|
+
} : { component: "button", type: "button" };
|
|
2947
|
+
return /* @__PURE__ */ jsxs4(
|
|
2948
|
+
ButtonBase,
|
|
2949
|
+
{
|
|
2950
|
+
ref,
|
|
2951
|
+
...anchorProps,
|
|
2952
|
+
onClick,
|
|
2953
|
+
"aria-current": active ? "page" : void 0,
|
|
2954
|
+
"aria-label": rest["aria-label"],
|
|
2955
|
+
"data-section": rest["data-section"],
|
|
2956
|
+
"data-external": external ? "" : void 0,
|
|
2957
|
+
sx: {
|
|
2958
|
+
width: "calc(100% - 16px)",
|
|
2959
|
+
mx: 1,
|
|
2960
|
+
minHeight: size === "touch" ? 44 : 36,
|
|
2961
|
+
px: 1.25,
|
|
2962
|
+
py: 0.5,
|
|
2963
|
+
display: "flex",
|
|
2964
|
+
alignItems: "center",
|
|
2965
|
+
justifyContent: "flex-start",
|
|
2966
|
+
gap: 1.25,
|
|
2967
|
+
borderRadius: 1,
|
|
2968
|
+
textAlign: "left",
|
|
2969
|
+
textDecoration: "none",
|
|
2970
|
+
font: "inherit",
|
|
2971
|
+
fontSize: "0.875rem",
|
|
2972
|
+
fontWeight: active ? 600 : 500,
|
|
2973
|
+
color: active ? theme.palette.text.primary : theme.palette.text.secondary,
|
|
2974
|
+
bgcolor: active ? alpha4(theme.palette.text.primary, 0.08) : "transparent",
|
|
2975
|
+
transition: theme.transitions.create(["background-color", "color"], {
|
|
2976
|
+
duration: theme.transitions.duration.shorter
|
|
2977
|
+
}),
|
|
2978
|
+
"@media (pointer: coarse)": { minHeight: 44 },
|
|
2979
|
+
"&:hover": {
|
|
2980
|
+
color: theme.palette.text.primary,
|
|
2981
|
+
bgcolor: alpha4(theme.palette.text.primary, active ? 0.1 : 0.04)
|
|
2982
|
+
},
|
|
2983
|
+
"&:focus-visible": {
|
|
2984
|
+
outline: `2px solid ${theme.palette.primary.main}`,
|
|
2985
|
+
outlineOffset: "-2px"
|
|
2986
|
+
},
|
|
2987
|
+
"& svg": { flexShrink: 0 }
|
|
2988
|
+
},
|
|
2989
|
+
children: [
|
|
2990
|
+
swatch ? /* @__PURE__ */ jsx6(
|
|
2991
|
+
Box5,
|
|
2992
|
+
{
|
|
2993
|
+
"aria-hidden": true,
|
|
2994
|
+
sx: {
|
|
2995
|
+
width: 10,
|
|
2996
|
+
height: 10,
|
|
2997
|
+
mx: "4px",
|
|
2998
|
+
borderRadius: "50%",
|
|
2999
|
+
bgcolor: swatch,
|
|
3000
|
+
flexShrink: 0
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
) : icon,
|
|
3004
|
+
/* @__PURE__ */ jsx6(
|
|
3005
|
+
Box5,
|
|
3006
|
+
{
|
|
3007
|
+
component: "span",
|
|
3008
|
+
sx: {
|
|
3009
|
+
flex: 1,
|
|
3010
|
+
minWidth: 0,
|
|
3011
|
+
overflow: "hidden",
|
|
3012
|
+
textOverflow: "ellipsis",
|
|
3013
|
+
whiteSpace: "nowrap"
|
|
3014
|
+
},
|
|
3015
|
+
children: label
|
|
3016
|
+
}
|
|
3017
|
+
),
|
|
3018
|
+
trailing
|
|
3019
|
+
]
|
|
3020
|
+
}
|
|
3021
|
+
);
|
|
3022
|
+
});
|
|
3023
|
+
|
|
3024
|
+
// src/chat/sidebar/SidebarProjects.tsx
|
|
3025
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
3026
|
+
var SIDEBAR_PROJECT_LIMIT = 6;
|
|
3027
|
+
function summarizeProjects(projects, conversations) {
|
|
3028
|
+
return projects.map((project) => {
|
|
3029
|
+
const chats = conversations.filter((c) => c.projectId === project.id);
|
|
3030
|
+
const newest = chats.reduce((max, c) => Math.max(max, lastActivity(c)), 0);
|
|
3031
|
+
return {
|
|
3032
|
+
project,
|
|
3033
|
+
chatCount: chats.length,
|
|
3034
|
+
activeAt: newest || project.updatedAt.getTime()
|
|
3035
|
+
};
|
|
3036
|
+
}).sort((a, b) => b.activeAt - a.activeAt || a.project.order - b.project.order);
|
|
3037
|
+
}
|
|
3038
|
+
var SidebarProjects = ({
|
|
3039
|
+
activeProjectId,
|
|
3040
|
+
onOpenProject,
|
|
3041
|
+
onAllProjects,
|
|
3042
|
+
onNewProject,
|
|
3043
|
+
size
|
|
3044
|
+
}) => {
|
|
3045
|
+
const theme = useTheme5();
|
|
3046
|
+
const projects = useProjectStore((state) => state.projects);
|
|
3047
|
+
const conversations = useConversationStore((state) => state.conversations);
|
|
3048
|
+
const summaries = useMemo(
|
|
3049
|
+
() => summarizeProjects(projects, listable(conversations)),
|
|
3050
|
+
[projects, conversations]
|
|
3051
|
+
);
|
|
3052
|
+
const shown = summaries.slice(0, SIDEBAR_PROJECT_LIMIT);
|
|
3053
|
+
return /* @__PURE__ */ jsxs5(Box6, { component: "section", "aria-labelledby": "bandit-sidebar-projects", children: [
|
|
3054
|
+
/* @__PURE__ */ jsx7(
|
|
3055
|
+
SidebarCaption,
|
|
3056
|
+
{
|
|
3057
|
+
action: /* @__PURE__ */ jsx7(Tooltip, { title: "New project", arrow: true, children: /* @__PURE__ */ jsx7(
|
|
3058
|
+
IconButton3,
|
|
3059
|
+
{
|
|
3060
|
+
size: "small",
|
|
3061
|
+
"aria-label": "New project",
|
|
3062
|
+
onClick: onNewProject,
|
|
3063
|
+
sx: { color: theme.palette.text.secondary, width: 28, height: 28 },
|
|
3064
|
+
children: /* @__PURE__ */ jsx7(Plus, { size: 16 })
|
|
3065
|
+
}
|
|
3066
|
+
) }),
|
|
3067
|
+
children: /* @__PURE__ */ jsx7("span", { id: "bandit-sidebar-projects", children: "Projects" })
|
|
3068
|
+
}
|
|
3069
|
+
),
|
|
3070
|
+
shown.length === 0 && /* @__PURE__ */ jsx7(
|
|
3071
|
+
Typography4,
|
|
3072
|
+
{
|
|
3073
|
+
variant: "caption",
|
|
3074
|
+
sx: { display: "block", px: 2.25, pb: 1, color: theme.palette.text.secondary },
|
|
3075
|
+
children: "No projects yet."
|
|
3076
|
+
}
|
|
3077
|
+
),
|
|
3078
|
+
shown.map(({ project, chatCount }) => /* @__PURE__ */ jsx7(
|
|
3079
|
+
SectionRow,
|
|
3080
|
+
{
|
|
3081
|
+
"data-section": `project:${project.id}`,
|
|
3082
|
+
swatch: project.color || theme.palette.text.disabled,
|
|
3083
|
+
label: project.name,
|
|
3084
|
+
size,
|
|
3085
|
+
active: activeProjectId === project.id,
|
|
3086
|
+
onClick: () => onOpenProject(project.id),
|
|
3087
|
+
trailing: chatCount > 0 ? /* @__PURE__ */ jsx7(
|
|
3088
|
+
Typography4,
|
|
3089
|
+
{
|
|
3090
|
+
component: "span",
|
|
3091
|
+
variant: "caption",
|
|
3092
|
+
"aria-label": `${chatCount} ${chatCount === 1 ? "chat" : "chats"}`,
|
|
3093
|
+
sx: {
|
|
3094
|
+
color: theme.palette.text.disabled,
|
|
3095
|
+
fontVariantNumeric: "tabular-nums"
|
|
3096
|
+
},
|
|
3097
|
+
children: chatCount
|
|
3098
|
+
}
|
|
3099
|
+
) : null
|
|
3100
|
+
},
|
|
3101
|
+
project.id
|
|
3102
|
+
)),
|
|
3103
|
+
summaries.length > SIDEBAR_PROJECT_LIMIT && /* @__PURE__ */ jsx7(
|
|
3104
|
+
SectionRow,
|
|
3105
|
+
{
|
|
3106
|
+
"data-section": "all-projects",
|
|
3107
|
+
label: `All projects (${summaries.length})`,
|
|
3108
|
+
size,
|
|
3109
|
+
onClick: onAllProjects,
|
|
3110
|
+
icon: /* @__PURE__ */ jsx7(Box6, { "aria-hidden": true, sx: { width: 18 } })
|
|
3111
|
+
}
|
|
3112
|
+
)
|
|
3113
|
+
] });
|
|
3114
|
+
};
|
|
3115
|
+
|
|
3116
|
+
// src/chat/sidebar/SidebarSections.tsx
|
|
3117
|
+
import { Box as Box7 } from "@mui/material";
|
|
3118
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3119
|
+
var NEW_CHAT_SHORTCUT = ["mod", "shift", "O"];
|
|
3120
|
+
var SEARCH_SHORTCUT = ["mod", "K"];
|
|
3121
|
+
var TOGGLE_SIDEBAR_SHORTCUT = ["mod", "shift", "S"];
|
|
3122
|
+
var useNoSections = () => [];
|
|
3123
|
+
function useHostSections() {
|
|
3124
|
+
const useSections = usePackageSettingsStore(
|
|
3125
|
+
(state) => state.settings?.navigation?.useSections
|
|
3126
|
+
);
|
|
3127
|
+
return (useSections ?? useNoSections)();
|
|
3128
|
+
}
|
|
3129
|
+
var isPlainLeftClick = (event) => event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
|
|
3130
|
+
var SidebarSections = ({
|
|
3131
|
+
onNewChat,
|
|
3132
|
+
onSearch,
|
|
3133
|
+
searchOpen,
|
|
3134
|
+
onProjects,
|
|
3135
|
+
projectsActive,
|
|
3136
|
+
showHints = false,
|
|
3137
|
+
size = "regular",
|
|
3138
|
+
onAfterNavigate
|
|
3139
|
+
}) => {
|
|
3140
|
+
const hostSections = useHostSections();
|
|
3141
|
+
const safeNavigate = useSafeNavigate();
|
|
3142
|
+
const iconSize = 18;
|
|
3143
|
+
const follow = (section) => (event) => {
|
|
3144
|
+
if (!isPlainLeftClick(event)) return;
|
|
3145
|
+
section.onSelect?.(event);
|
|
3146
|
+
if (event.defaultPrevented) {
|
|
3147
|
+
onAfterNavigate?.();
|
|
3148
|
+
return;
|
|
3149
|
+
}
|
|
3150
|
+
if (section.external) {
|
|
3151
|
+
onAfterNavigate?.();
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3154
|
+
event.preventDefault();
|
|
3155
|
+
safeNavigate(section.href);
|
|
3156
|
+
onAfterNavigate?.();
|
|
3157
|
+
};
|
|
3158
|
+
return /* @__PURE__ */ jsxs6(
|
|
3159
|
+
Box7,
|
|
3160
|
+
{
|
|
3161
|
+
component: "nav",
|
|
3162
|
+
"aria-label": "Chat navigation",
|
|
3163
|
+
sx: { display: "flex", flexDirection: "column", gap: 0.25, pt: 0.5, pb: 1 },
|
|
3164
|
+
children: [
|
|
3165
|
+
/* @__PURE__ */ jsx8(
|
|
3166
|
+
SectionRow,
|
|
3167
|
+
{
|
|
3168
|
+
"data-section": "new-chat",
|
|
3169
|
+
icon: /* @__PURE__ */ jsx8(MessageSquarePlus, { size: iconSize }),
|
|
3170
|
+
label: "New chat",
|
|
3171
|
+
size,
|
|
3172
|
+
onClick: onNewChat,
|
|
3173
|
+
trailing: showHints ? /* @__PURE__ */ jsx8(SidebarKbd, { children: shortcutLabel(NEW_CHAT_SHORTCUT) }) : null
|
|
3174
|
+
}
|
|
3175
|
+
),
|
|
3176
|
+
/* @__PURE__ */ jsx8(
|
|
3177
|
+
SectionRow,
|
|
3178
|
+
{
|
|
3179
|
+
"data-section": "search",
|
|
3180
|
+
icon: /* @__PURE__ */ jsx8(Search, { size: iconSize }),
|
|
3181
|
+
label: "Search chats",
|
|
3182
|
+
size,
|
|
3183
|
+
active: searchOpen,
|
|
3184
|
+
onClick: onSearch,
|
|
3185
|
+
"aria-label": searchOpen ? "Close search" : "Search chats",
|
|
3186
|
+
trailing: showHints ? /* @__PURE__ */ jsx8(SidebarKbd, { children: shortcutLabel(SEARCH_SHORTCUT) }) : null
|
|
3187
|
+
}
|
|
3188
|
+
),
|
|
3189
|
+
/* @__PURE__ */ jsx8(
|
|
3190
|
+
SectionRow,
|
|
3191
|
+
{
|
|
3192
|
+
"data-section": "projects",
|
|
3193
|
+
icon: /* @__PURE__ */ jsx8(Folder, { size: iconSize }),
|
|
3194
|
+
label: "Projects",
|
|
3195
|
+
size,
|
|
3196
|
+
active: projectsActive,
|
|
3197
|
+
onClick: onProjects
|
|
3198
|
+
}
|
|
3199
|
+
),
|
|
3200
|
+
hostSections.map((section) => /* @__PURE__ */ jsx8(
|
|
3201
|
+
SectionRow,
|
|
3202
|
+
{
|
|
3203
|
+
"data-section": section.id,
|
|
3204
|
+
icon: section.icon,
|
|
3205
|
+
label: section.label,
|
|
3206
|
+
size,
|
|
3207
|
+
href: section.href,
|
|
3208
|
+
external: section.external,
|
|
3209
|
+
active: section.active,
|
|
3210
|
+
onClick: follow(section)
|
|
3211
|
+
},
|
|
3212
|
+
section.id
|
|
3213
|
+
))
|
|
3214
|
+
]
|
|
3215
|
+
}
|
|
3216
|
+
);
|
|
3217
|
+
};
|
|
3218
|
+
|
|
3219
|
+
// src/chat/sidebar/SidebarUserCard.tsx
|
|
3220
|
+
import { Avatar as Avatar2, Box as Box8, IconButton as IconButton4, Tooltip as Tooltip2, Typography as Typography5, alpha as alpha5 } from "@mui/material";
|
|
3221
|
+
import { useTheme as useTheme6 } from "@mui/material/styles";
|
|
3222
|
+
|
|
3223
|
+
// src/utils/installedApp.ts
|
|
3224
|
+
var isNativeShell = () => {
|
|
3225
|
+
if (typeof window === "undefined") return false;
|
|
3226
|
+
const cap = window.Capacitor;
|
|
3227
|
+
return typeof cap?.isNativePlatform === "function" && cap.isNativePlatform() === true;
|
|
3228
|
+
};
|
|
3229
|
+
var isStandalonePwa = () => {
|
|
3230
|
+
if (typeof window === "undefined") return false;
|
|
3231
|
+
const nav = window.navigator;
|
|
3232
|
+
return window.matchMedia?.("(display-mode: standalone)").matches || nav.standalone === true;
|
|
3233
|
+
};
|
|
3234
|
+
|
|
3235
|
+
// src/chat/sidebar/useSidebarUser.ts
|
|
3236
|
+
import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo2, useState as useState5 } from "react";
|
|
3237
|
+
var BANDIT_AVATAR = "https://cdn.burtson.ai/images/bandit-head.png";
|
|
3238
|
+
var optionalString = (value) => {
|
|
3239
|
+
if (typeof value !== "string") return void 0;
|
|
3240
|
+
const trimmed = value.trim();
|
|
3241
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
3242
|
+
};
|
|
3243
|
+
var deriveInitials = (value) => {
|
|
3244
|
+
const sanitized = (value || "").trim();
|
|
3245
|
+
if (!sanitized) return "B";
|
|
3246
|
+
const words = sanitized.split(/\s+/).filter(Boolean);
|
|
3247
|
+
if (words.length >= 2) {
|
|
3248
|
+
const initials = `${words[0]?.charAt(0) ?? ""}${words[words.length - 1]?.charAt(0) ?? ""}`;
|
|
3249
|
+
return (initials.trim() || sanitized.slice(0, 2)).toUpperCase();
|
|
3250
|
+
}
|
|
3251
|
+
const alphanumeric = sanitized.replace(/[^A-Za-z0-9]/g, "");
|
|
3252
|
+
if (alphanumeric.length >= 1) return alphanumeric.slice(0, 2).toUpperCase();
|
|
3253
|
+
return sanitized.slice(0, 2).toUpperCase();
|
|
3254
|
+
};
|
|
3255
|
+
function useSidebarUser() {
|
|
3256
|
+
const { user } = useAuthenticationStore();
|
|
3257
|
+
const [avatarSrc, setAvatarSrc] = useState5(BANDIT_AVATAR);
|
|
3258
|
+
const claim = useCallback2(
|
|
3259
|
+
(key) => user ? optionalString(user[key]) : void 0,
|
|
3260
|
+
[user]
|
|
3261
|
+
);
|
|
3262
|
+
const displayName = useMemo2(() => {
|
|
3263
|
+
if (!user) return void 0;
|
|
3264
|
+
const candidates = [
|
|
3265
|
+
optionalString(user.name),
|
|
3266
|
+
optionalString(user.preferred_username),
|
|
3267
|
+
user.given_name && user.family_name ? optionalString(`${user.given_name} ${user.family_name}`) : void 0,
|
|
3268
|
+
claim("full_name"),
|
|
3269
|
+
claim("displayName")
|
|
3270
|
+
];
|
|
3271
|
+
return candidates.find(Boolean) ?? optionalString(user.email) ?? user.sub;
|
|
3272
|
+
}, [user, claim]);
|
|
3273
|
+
const secondaryText = useMemo2(() => {
|
|
3274
|
+
if (!user) return void 0;
|
|
3275
|
+
const email = optionalString(user.email);
|
|
3276
|
+
if (email && email !== displayName) return email;
|
|
3277
|
+
const sub = optionalString(user.sub);
|
|
3278
|
+
return sub && sub !== displayName ? sub : void 0;
|
|
3279
|
+
}, [user, displayName]);
|
|
3280
|
+
useEffect5(() => {
|
|
3281
|
+
const fresh = readPersistedToken();
|
|
3282
|
+
const store = useAuthenticationStore.getState();
|
|
3283
|
+
if (fresh && fresh !== store.token) store.setToken(fresh);
|
|
3284
|
+
}, []);
|
|
3285
|
+
useEffect5(() => {
|
|
3286
|
+
let active = true;
|
|
3287
|
+
let objectUrl = null;
|
|
3288
|
+
const resolve = async () => {
|
|
3289
|
+
try {
|
|
3290
|
+
const avatarId = claim("avatarUrl");
|
|
3291
|
+
const fileStorageApiUrl = usePackageSettingsStore.getState().settings?.fileStorageApiUrl;
|
|
3292
|
+
const token = useAuthenticationStore.getState().token;
|
|
3293
|
+
if (avatarId && fileStorageApiUrl && token) {
|
|
3294
|
+
const base = fileStorageApiUrl.replace(/\/$/, "");
|
|
3295
|
+
const response = await fetch(
|
|
3296
|
+
`${base}/app-asset/download/${encodeURIComponent(avatarId)}`,
|
|
3297
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
3298
|
+
);
|
|
3299
|
+
if (response.ok) {
|
|
3300
|
+
const blob = await response.blob();
|
|
3301
|
+
if (active) {
|
|
3302
|
+
objectUrl = URL.createObjectURL(blob);
|
|
3303
|
+
setAvatarSrc(objectUrl);
|
|
3304
|
+
}
|
|
3305
|
+
return;
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
} catch {
|
|
3309
|
+
}
|
|
3310
|
+
try {
|
|
3311
|
+
const branding = await brandingService_default.getBranding();
|
|
3312
|
+
if (active) setAvatarSrc(branding?.logoBase64 || BANDIT_AVATAR);
|
|
3313
|
+
} catch (error) {
|
|
3314
|
+
debugLogger.error("Failed to load branding avatar", {
|
|
3315
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3316
|
+
});
|
|
3317
|
+
if (active) setAvatarSrc(BANDIT_AVATAR);
|
|
3318
|
+
}
|
|
3319
|
+
};
|
|
3320
|
+
void resolve();
|
|
3321
|
+
return () => {
|
|
3322
|
+
active = false;
|
|
3323
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
3324
|
+
};
|
|
3325
|
+
}, [claim]);
|
|
3326
|
+
const label = displayName || user?.email || "Bandit";
|
|
3327
|
+
return {
|
|
3328
|
+
signedIn: Boolean(user),
|
|
3329
|
+
displayName,
|
|
3330
|
+
secondaryText,
|
|
3331
|
+
avatarSrc,
|
|
3332
|
+
initials: deriveInitials(label)
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
// src/chat/sidebar/SidebarUserCard.tsx
|
|
3337
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3338
|
+
function goHome(safeNavigate, preferencesHomeUrl) {
|
|
3339
|
+
const homeUrl = preferencesHomeUrl?.trim() || usePackageSettingsStore.getState().settings?.homeUrl?.trim() || "";
|
|
3340
|
+
if (homeUrl) {
|
|
3341
|
+
window.location.href = homeUrl;
|
|
3342
|
+
return;
|
|
3343
|
+
}
|
|
3344
|
+
if (isNativeShell()) {
|
|
3345
|
+
safeNavigate("/chat");
|
|
3346
|
+
return;
|
|
3347
|
+
}
|
|
3348
|
+
if (isStandalonePwa()) {
|
|
3349
|
+
try {
|
|
3350
|
+
sessionStorage.setItem("banditPwaStayOnHome", "true");
|
|
3351
|
+
} catch {
|
|
3352
|
+
}
|
|
3353
|
+
window.location.href = "/?mode=home";
|
|
3354
|
+
return;
|
|
3355
|
+
}
|
|
3356
|
+
window.location.href = "/";
|
|
3357
|
+
}
|
|
3358
|
+
var managementPathFor = (pathname) => pathname.startsWith("/playground") ? "/playground/management" : "/management";
|
|
3359
|
+
var SidebarUserCard = ({ safeAreaBottom }) => {
|
|
3360
|
+
const theme = useTheme6();
|
|
3361
|
+
const user = useSidebarUser();
|
|
3362
|
+
const safeNavigate = useSafeNavigate();
|
|
3363
|
+
const { preferences } = usePreferencesStore();
|
|
3364
|
+
const { showLimitedAdminPanel } = useFeatureVisibility();
|
|
3365
|
+
const managementPath = managementPathFor(
|
|
3366
|
+
typeof window === "undefined" ? "/" : window.location.pathname
|
|
3367
|
+
);
|
|
3368
|
+
const roundButton = {
|
|
3369
|
+
flexShrink: 0,
|
|
3370
|
+
width: 32,
|
|
3371
|
+
height: 32,
|
|
3372
|
+
border: `1px solid ${alpha5(theme.palette.divider, 0.8)}`,
|
|
3373
|
+
color: theme.palette.text.secondary,
|
|
3374
|
+
"&:hover": {
|
|
3375
|
+
color: theme.palette.text.primary,
|
|
3376
|
+
bgcolor: alpha5(theme.palette.text.primary, 0.06)
|
|
3377
|
+
},
|
|
3378
|
+
"&:focus-visible": {
|
|
3379
|
+
outline: `2px solid ${theme.palette.primary.main}`,
|
|
3380
|
+
outlineOffset: "2px"
|
|
3381
|
+
}
|
|
3382
|
+
};
|
|
3383
|
+
return /* @__PURE__ */ jsxs7(
|
|
3384
|
+
Box8,
|
|
3385
|
+
{
|
|
3386
|
+
sx: {
|
|
3387
|
+
mt: "auto",
|
|
3388
|
+
px: 1.5,
|
|
3389
|
+
py: 1.25,
|
|
3390
|
+
pb: safeAreaBottom ? `calc(${theme.spacing(1.25)} + env(safe-area-inset-bottom, 0px))` : 1.25,
|
|
3391
|
+
borderTop: `1px solid ${alpha5(theme.palette.divider, 0.6)}`,
|
|
3392
|
+
display: "flex",
|
|
3393
|
+
alignItems: "center",
|
|
3394
|
+
gap: 1
|
|
3395
|
+
},
|
|
3396
|
+
children: [
|
|
3397
|
+
/* @__PURE__ */ jsxs7(
|
|
3398
|
+
Box8,
|
|
3399
|
+
{
|
|
3400
|
+
component: user.signedIn ? "button" : "div",
|
|
3401
|
+
type: user.signedIn ? "button" : void 0,
|
|
3402
|
+
onClick: user.signedIn ? () => safeNavigate("/profile") : void 0,
|
|
3403
|
+
"aria-label": user.signedIn ? "Profile and account" : void 0,
|
|
3404
|
+
sx: {
|
|
3405
|
+
flex: 1,
|
|
3406
|
+
minWidth: 0,
|
|
3407
|
+
display: "flex",
|
|
3408
|
+
alignItems: "center",
|
|
3409
|
+
gap: 1.25,
|
|
3410
|
+
p: 0.5,
|
|
3411
|
+
m: -0.5,
|
|
3412
|
+
border: 0,
|
|
3413
|
+
borderRadius: 1,
|
|
3414
|
+
bgcolor: "transparent",
|
|
3415
|
+
color: "inherit",
|
|
3416
|
+
font: "inherit",
|
|
3417
|
+
textAlign: "left",
|
|
3418
|
+
cursor: user.signedIn ? "pointer" : "default",
|
|
3419
|
+
"&:hover": user.signedIn ? { bgcolor: alpha5(theme.palette.text.primary, 0.04) } : void 0,
|
|
3420
|
+
"&:focus-visible": {
|
|
3421
|
+
outline: `2px solid ${theme.palette.primary.main}`,
|
|
3422
|
+
outlineOffset: "-2px"
|
|
3423
|
+
}
|
|
3424
|
+
},
|
|
3425
|
+
children: [
|
|
3426
|
+
/* @__PURE__ */ jsx9(
|
|
3427
|
+
Avatar2,
|
|
3428
|
+
{
|
|
3429
|
+
src: user.avatarSrc,
|
|
3430
|
+
alt: "",
|
|
3431
|
+
sx: {
|
|
3432
|
+
width: 34,
|
|
3433
|
+
height: 34,
|
|
3434
|
+
fontSize: "0.9rem",
|
|
3435
|
+
bgcolor: alpha5(theme.palette.primary.main, 0.12),
|
|
3436
|
+
color: theme.palette.primary.main
|
|
3437
|
+
},
|
|
3438
|
+
children: user.initials
|
|
3439
|
+
}
|
|
3440
|
+
),
|
|
3441
|
+
/* @__PURE__ */ jsxs7(Box8, { sx: { minWidth: 0, display: "flex", flexDirection: "column" }, children: [
|
|
3442
|
+
/* @__PURE__ */ jsx9(
|
|
3443
|
+
Typography5,
|
|
3444
|
+
{
|
|
3445
|
+
variant: "subtitle2",
|
|
3446
|
+
noWrap: true,
|
|
3447
|
+
sx: { fontWeight: 600, lineHeight: 1.3 },
|
|
3448
|
+
children: user.signedIn ? user.displayName : "Not signed in"
|
|
3449
|
+
}
|
|
3450
|
+
),
|
|
3451
|
+
/* @__PURE__ */ jsx9(
|
|
3452
|
+
Typography5,
|
|
3453
|
+
{
|
|
3454
|
+
variant: "caption",
|
|
3455
|
+
noWrap: true,
|
|
3456
|
+
sx: { color: theme.palette.text.secondary, lineHeight: 1.3 },
|
|
3457
|
+
children: user.signedIn ? user.secondaryText || "Profile and account" : "Sign in to sync chats"
|
|
3458
|
+
}
|
|
3459
|
+
)
|
|
3460
|
+
] })
|
|
3461
|
+
]
|
|
3462
|
+
}
|
|
3463
|
+
),
|
|
3464
|
+
/* @__PURE__ */ jsx9(Tooltip2, { title: "Home", arrow: true, children: /* @__PURE__ */ jsx9(
|
|
3465
|
+
IconButton4,
|
|
3466
|
+
{
|
|
3467
|
+
"aria-label": "Home",
|
|
3468
|
+
onClick: () => goHome(safeNavigate, preferences.homeUrl),
|
|
3469
|
+
sx: roundButton,
|
|
3470
|
+
children: /* @__PURE__ */ jsx9(Home, { size: 16 })
|
|
3471
|
+
}
|
|
3472
|
+
) }),
|
|
3473
|
+
user.signedIn && showLimitedAdminPanel() && /* @__PURE__ */ jsx9(Tooltip2, { title: "Settings", arrow: true, children: /* @__PURE__ */ jsx9(
|
|
3474
|
+
IconButton4,
|
|
3475
|
+
{
|
|
3476
|
+
"aria-label": "Settings",
|
|
3477
|
+
onClick: () => safeNavigate(managementPath),
|
|
3478
|
+
sx: roundButton,
|
|
3479
|
+
children: /* @__PURE__ */ jsx9(Settings, { size: 16 })
|
|
3480
|
+
}
|
|
3481
|
+
) }),
|
|
3482
|
+
user.signedIn && /* @__PURE__ */ jsx9(Tooltip2, { title: "Sign out", arrow: true, children: /* @__PURE__ */ jsx9(
|
|
3483
|
+
IconButton4,
|
|
3484
|
+
{
|
|
3485
|
+
"aria-label": "Sign out",
|
|
3486
|
+
onClick: () => {
|
|
3487
|
+
authenticationService.clearToken();
|
|
3488
|
+
window.location.href = "/login";
|
|
3489
|
+
},
|
|
3490
|
+
sx: {
|
|
3491
|
+
...roundButton,
|
|
3492
|
+
border: `1px solid ${alpha5(theme.palette.error.main, 0.4)}`,
|
|
3493
|
+
color: theme.palette.error.main,
|
|
3494
|
+
"&:hover": { bgcolor: alpha5(theme.palette.error.main, 0.1) }
|
|
3495
|
+
},
|
|
3496
|
+
children: /* @__PURE__ */ jsx9(LogOut, { size: 16 })
|
|
3497
|
+
}
|
|
3498
|
+
) })
|
|
3499
|
+
]
|
|
3500
|
+
}
|
|
3501
|
+
);
|
|
3502
|
+
};
|
|
3503
|
+
|
|
3504
|
+
export {
|
|
3505
|
+
newId,
|
|
3506
|
+
useAIQueryStore,
|
|
3507
|
+
CONVERSATION_UPSERT_EVENT,
|
|
3508
|
+
CONVERSATION_DELETE_EVENT,
|
|
3509
|
+
PROJECT_UPSERT_EVENT,
|
|
3510
|
+
PROJECT_DELETE_EVENT,
|
|
3511
|
+
sanitizeConversationName,
|
|
3512
|
+
useConversationStore,
|
|
3513
|
+
useProjectStore,
|
|
3514
|
+
DEFAULT_TIER_FEATURES,
|
|
3515
|
+
OSS_DEFAULT_FEATURES,
|
|
3516
|
+
FeatureFlagContext,
|
|
3517
|
+
FeatureFlagProvider,
|
|
3518
|
+
useFeatureFlag,
|
|
3519
|
+
useFeatures,
|
|
3520
|
+
useFeatureVisibility,
|
|
3521
|
+
brandingService_default,
|
|
3522
|
+
light,
|
|
3523
|
+
dark,
|
|
3524
|
+
radius,
|
|
3525
|
+
shadow,
|
|
3526
|
+
fontSans,
|
|
3527
|
+
fontMono,
|
|
3528
|
+
duration,
|
|
3529
|
+
easing,
|
|
3530
|
+
mix,
|
|
3531
|
+
contrast,
|
|
3532
|
+
accentTokens,
|
|
3533
|
+
chatTokens,
|
|
3534
|
+
fileIconFor,
|
|
3535
|
+
monoFont,
|
|
3536
|
+
fileExtensionLabel,
|
|
3537
|
+
ComposerControl,
|
|
3538
|
+
appBarTokens,
|
|
3539
|
+
responseTokens,
|
|
3540
|
+
isNativeShell,
|
|
3541
|
+
isStandalonePwa,
|
|
3542
|
+
useSafeNavigate,
|
|
3543
|
+
move_conversation_modal_default,
|
|
3544
|
+
simple_conversation_item_default,
|
|
3545
|
+
ProjectDialog,
|
|
3546
|
+
lastActivity,
|
|
3547
|
+
groupByRecency,
|
|
3548
|
+
searchConversations,
|
|
3549
|
+
listable,
|
|
3550
|
+
formatRelative,
|
|
3551
|
+
shortcutLabel,
|
|
3552
|
+
SidebarCaption,
|
|
3553
|
+
summarizeProjects,
|
|
3554
|
+
SidebarProjects,
|
|
3555
|
+
TOGGLE_SIDEBAR_SHORTCUT,
|
|
3556
|
+
useHostSections,
|
|
3557
|
+
SidebarSections,
|
|
3558
|
+
SIDEBAR_WIDTH,
|
|
3559
|
+
writePersistedCollapsed,
|
|
3560
|
+
initialSidebarOpen,
|
|
3561
|
+
useSidebarStore,
|
|
3562
|
+
syncViewWithUrl,
|
|
3563
|
+
useSectionsLayout,
|
|
3564
|
+
managementPathFor,
|
|
3565
|
+
SidebarUserCard
|
|
3566
|
+
};
|
|
3567
|
+
//# sourceMappingURL=chunk-SQJ4OKWM.mjs.map
|