@gendive/chatllm 0.6.12 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,10 +48,10 @@ __export(index_exports, {
48
48
  module.exports = __toCommonJS(index_exports);
49
49
 
50
50
  // src/react/ChatUI.tsx
51
- var import_react10 = __toESM(require("react"));
51
+ var import_react12 = __toESM(require("react"));
52
52
 
53
53
  // src/react/hooks/useChatUI.ts
54
- var import_react = require("react");
54
+ var import_react3 = require("react");
55
55
 
56
56
  // src/types.ts
57
57
  var DEFAULT_PERSONALIZATION = {
@@ -67,10 +67,481 @@ var DEFAULT_PERSONALIZATION = {
67
67
  language: "auto"
68
68
  };
69
69
 
70
+ // src/react/hooks/useGlobalMemory.ts
71
+ var import_react = require("react");
72
+
73
+ // src/react/adapters/LocalStorageAdapter.ts
74
+ var LocalStorageAdapter = class {
75
+ storageKey;
76
+ constructor(storageKey = "chatllm_global_memory") {
77
+ this.storageKey = storageKey;
78
+ }
79
+ /**
80
+ * @description 전체 데이터 로드
81
+ */
82
+ loadData() {
83
+ if (typeof window === "undefined") return {};
84
+ try {
85
+ const data = localStorage.getItem(this.storageKey);
86
+ return data ? JSON.parse(data) : {};
87
+ } catch {
88
+ return {};
89
+ }
90
+ }
91
+ /**
92
+ * @description 전체 데이터 저장
93
+ */
94
+ saveData(data) {
95
+ if (typeof window === "undefined") return;
96
+ try {
97
+ localStorage.setItem(this.storageKey, JSON.stringify(data));
98
+ } catch (error) {
99
+ console.error("[LocalStorageAdapter] Failed to save data:", error);
100
+ }
101
+ }
102
+ async read(key) {
103
+ const data = this.loadData();
104
+ const value = data[key];
105
+ return value !== void 0 ? value : null;
106
+ }
107
+ async write(key, value) {
108
+ const data = this.loadData();
109
+ data[key] = value;
110
+ this.saveData(data);
111
+ }
112
+ async delete(key) {
113
+ const data = this.loadData();
114
+ if (key in data) {
115
+ delete data[key];
116
+ this.saveData(data);
117
+ return true;
118
+ }
119
+ return false;
120
+ }
121
+ async exists(key) {
122
+ const data = this.loadData();
123
+ return key in data;
124
+ }
125
+ async getAll() {
126
+ const data = this.loadData();
127
+ return new Map(Object.entries(data));
128
+ }
129
+ /**
130
+ * @description 전체 삭제
131
+ */
132
+ async clear() {
133
+ if (typeof window === "undefined") return;
134
+ localStorage.removeItem(this.storageKey);
135
+ }
136
+ };
137
+
138
+ // src/react/adapters/PostgreSQLAdapter.ts
139
+ var PostgreSQLAdapter = class {
140
+ apiEndpoint;
141
+ userId;
142
+ authToken;
143
+ constructor(apiEndpoint, userId, authToken) {
144
+ this.apiEndpoint = apiEndpoint.replace(/\/$/, "");
145
+ this.userId = userId;
146
+ this.authToken = authToken;
147
+ }
148
+ /**
149
+ * @description HTTP 헤더 생성
150
+ */
151
+ getHeaders() {
152
+ const headers = {
153
+ "Content-Type": "application/json"
154
+ };
155
+ if (this.authToken) {
156
+ headers["Authorization"] = `Bearer ${this.authToken}`;
157
+ }
158
+ return headers;
159
+ }
160
+ /**
161
+ * @description API 요청 헬퍼
162
+ */
163
+ async request(path, options = {}) {
164
+ try {
165
+ const response = await fetch(`${this.apiEndpoint}${path}`, {
166
+ ...options,
167
+ headers: {
168
+ ...this.getHeaders(),
169
+ ...options.headers
170
+ }
171
+ });
172
+ if (!response.ok) {
173
+ if (response.status === 404) return null;
174
+ throw new Error(`API error: ${response.status}`);
175
+ }
176
+ const contentType = response.headers.get("content-type");
177
+ if (contentType?.includes("application/json")) {
178
+ return response.json();
179
+ }
180
+ return null;
181
+ } catch (error) {
182
+ console.error("[PostgreSQLAdapter] Request failed:", error);
183
+ return null;
184
+ }
185
+ }
186
+ async read(key) {
187
+ const result = await this.request(
188
+ `/memory/${this.userId}/${encodeURIComponent(key)}`
189
+ );
190
+ return result?.value ?? null;
191
+ }
192
+ async write(key, value) {
193
+ await this.request(`/memory/${this.userId}`, {
194
+ method: "POST",
195
+ body: JSON.stringify({ key, value })
196
+ });
197
+ }
198
+ async delete(key) {
199
+ const result = await this.request(
200
+ `/memory/${this.userId}/${encodeURIComponent(key)}`,
201
+ { method: "DELETE" }
202
+ );
203
+ return result?.success ?? false;
204
+ }
205
+ async exists(key) {
206
+ try {
207
+ const response = await fetch(
208
+ `${this.apiEndpoint}/memory/${this.userId}/${encodeURIComponent(key)}`,
209
+ {
210
+ method: "HEAD",
211
+ headers: this.getHeaders()
212
+ }
213
+ );
214
+ return response.ok;
215
+ } catch {
216
+ return false;
217
+ }
218
+ }
219
+ async getAll() {
220
+ const result = await this.request(
221
+ `/memory/${this.userId}`
222
+ );
223
+ if (!result?.entries) {
224
+ return /* @__PURE__ */ new Map();
225
+ }
226
+ return new Map(Object.entries(result.entries));
227
+ }
228
+ /**
229
+ * @description 전체 삭제
230
+ */
231
+ async clear() {
232
+ await this.request(`/memory/${this.userId}`, {
233
+ method: "DELETE"
234
+ });
235
+ }
236
+ };
237
+
238
+ // src/react/hooks/useGlobalMemory.ts
239
+ function createAdapter(options) {
240
+ switch (options.storageType) {
241
+ case "postgresql":
242
+ if (!options.apiEndpoint || !options.userId) {
243
+ console.warn("[useGlobalMemory] PostgreSQL requires apiEndpoint and userId, falling back to localStorage");
244
+ return new LocalStorageAdapter(options.storageKey);
245
+ }
246
+ return new PostgreSQLAdapter(options.apiEndpoint, options.userId, options.authToken);
247
+ case "localStorage":
248
+ case "memory":
249
+ default:
250
+ return new LocalStorageAdapter(options.storageKey);
251
+ }
252
+ }
253
+ function useGlobalMemory(options) {
254
+ const [state, setState] = (0, import_react.useState)({
255
+ entries: /* @__PURE__ */ new Map(),
256
+ isLoading: true,
257
+ error: null
258
+ });
259
+ const adapterRef = (0, import_react.useRef)(createAdapter(options));
260
+ const refresh = (0, import_react.useCallback)(async () => {
261
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
262
+ try {
263
+ const allData = await adapterRef.current.getAll();
264
+ const entries = /* @__PURE__ */ new Map();
265
+ for (const [key, value] of allData) {
266
+ if (typeof value === "object" && value !== null && "value" in value) {
267
+ entries.set(key, value);
268
+ } else {
269
+ entries.set(key, {
270
+ key,
271
+ value,
272
+ createdAt: Date.now(),
273
+ updatedAt: Date.now()
274
+ });
275
+ }
276
+ }
277
+ setState({ entries, isLoading: false, error: null });
278
+ } catch (error) {
279
+ setState((prev) => ({
280
+ ...prev,
281
+ isLoading: false,
282
+ error: error instanceof Error ? error : new Error("Unknown error")
283
+ }));
284
+ }
285
+ }, []);
286
+ const set = (0, import_react.useCallback)(
287
+ async (key, value, meta) => {
288
+ const now = Date.now();
289
+ const existingEntry = state.entries.get(key);
290
+ const entry = {
291
+ key,
292
+ value,
293
+ createdAt: existingEntry?.createdAt ?? now,
294
+ updatedAt: now,
295
+ category: meta?.category ?? existingEntry?.category,
296
+ confidence: meta?.confidence ?? existingEntry?.confidence,
297
+ source: meta?.source ?? existingEntry?.source
298
+ };
299
+ try {
300
+ await adapterRef.current.write(key, entry);
301
+ setState((prev) => {
302
+ const newEntries = new Map(prev.entries);
303
+ newEntries.set(key, entry);
304
+ return { ...prev, entries: newEntries };
305
+ });
306
+ } catch (error) {
307
+ console.error("[useGlobalMemory] Failed to set:", error);
308
+ }
309
+ },
310
+ [state.entries]
311
+ );
312
+ const get = (0, import_react.useCallback)(
313
+ (key) => {
314
+ const entry = state.entries.get(key);
315
+ return entry?.value;
316
+ },
317
+ [state.entries]
318
+ );
319
+ const remove = (0, import_react.useCallback)(async (key) => {
320
+ try {
321
+ await adapterRef.current.delete(key);
322
+ setState((prev) => {
323
+ const newEntries = new Map(prev.entries);
324
+ newEntries.delete(key);
325
+ return { ...prev, entries: newEntries };
326
+ });
327
+ } catch (error) {
328
+ console.error("[useGlobalMemory] Failed to remove:", error);
329
+ }
330
+ }, []);
331
+ const has = (0, import_react.useCallback)(
332
+ (key) => {
333
+ return state.entries.has(key);
334
+ },
335
+ [state.entries]
336
+ );
337
+ const clear = (0, import_react.useCallback)(async () => {
338
+ try {
339
+ for (const key of state.entries.keys()) {
340
+ await adapterRef.current.delete(key);
341
+ }
342
+ setState((prev) => ({ ...prev, entries: /* @__PURE__ */ new Map() }));
343
+ } catch (error) {
344
+ console.error("[useGlobalMemory] Failed to clear:", error);
345
+ }
346
+ }, [state.entries]);
347
+ const toPromptContext = (0, import_react.useCallback)(() => {
348
+ if (state.entries.size === 0) return "";
349
+ const lines = ["[\uC0AC\uC6A9\uC790 \uAE30\uC5B5 \uC815\uBCF4]"];
350
+ const categorized = {
351
+ basic: [],
352
+ professional: [],
353
+ preferences: [],
354
+ context: [],
355
+ other: []
356
+ };
357
+ for (const [key, entry] of state.entries) {
358
+ const category = entry.category || "other";
359
+ if (category in categorized) {
360
+ categorized[category].push({ key, value: entry.value });
361
+ } else {
362
+ categorized.other.push({ key, value: entry.value });
363
+ }
364
+ }
365
+ const categoryLabels2 = {
366
+ basic: "\uAE30\uBCF8 \uC815\uBCF4",
367
+ professional: "\uC9C1\uC5C5/\uC804\uBB38 \uC815\uBCF4",
368
+ preferences: "\uC120\uD638\uB3C4",
369
+ context: "\uD604\uC7AC \uB9E5\uB77D",
370
+ other: "\uAE30\uD0C0"
371
+ };
372
+ for (const [category, items] of Object.entries(categorized)) {
373
+ if (items.length > 0) {
374
+ lines.push(`
375
+ ${categoryLabels2[category]}:`);
376
+ for (const { key, value } of items) {
377
+ const valueStr = typeof value === "object" ? JSON.stringify(value) : String(value);
378
+ lines.push(`- ${key}: ${valueStr}`);
379
+ }
380
+ }
381
+ }
382
+ return lines.join("\n");
383
+ }, [state.entries]);
384
+ (0, import_react.useEffect)(() => {
385
+ refresh();
386
+ }, [refresh]);
387
+ (0, import_react.useEffect)(() => {
388
+ adapterRef.current = createAdapter(options);
389
+ refresh();
390
+ }, [options.storageType, options.storageKey, options.apiEndpoint, options.userId]);
391
+ return {
392
+ state,
393
+ set,
394
+ get,
395
+ remove,
396
+ has,
397
+ clear,
398
+ toPromptContext,
399
+ refresh
400
+ };
401
+ }
402
+
403
+ // src/react/hooks/useInfoExtraction.ts
404
+ var import_react2 = require("react");
405
+ var buildExtractionPrompt = (messages) => {
406
+ const conversationText = messages.map((m) => `${m.role === "user" ? "\uC0AC\uC6A9\uC790" : "AI"}: ${m.content}`).join("\n\n");
407
+ return `\uB2E4\uC74C \uB300\uD654\uC5D0\uC11C \uC0AC\uC6A9\uC790\uC5D0 \uB300\uD574 \uAE30\uC5B5\uD574\uB458 \uB9CC\uD55C \uC911\uC694\uD55C \uC815\uBCF4\uB97C \uCD94\uCD9C\uD574\uC8FC\uC138\uC694.
408
+
409
+ \uB300\uD654:
410
+ ${conversationText}
411
+
412
+ \uB2E4\uC74C JSON \uD615\uC2DD\uC73C\uB85C\uB9CC \uC751\uB2F5\uD558\uC138\uC694 (JSON\uB9CC \uCD9C\uB825, \uB2E4\uB978 \uD14D\uC2A4\uD2B8 \uC5C6\uC774):
413
+ {
414
+ "extracted": [
415
+ {
416
+ "category": "basic|professional|preferences|context",
417
+ "key": "\uC815\uBCF4 \uD0A4 (\uC608: name, occupation, preferredLanguage)",
418
+ "value": "\uC815\uBCF4 \uAC12",
419
+ "confidence": 0.0-1.0,
420
+ "source": "\uADFC\uAC70\uAC00 \uB41C \uC6D0\uBB38 \uC77C\uBD80"
421
+ }
422
+ ],
423
+ "shouldStore": true
424
+ }
425
+
426
+ \uCE74\uD14C\uACE0\uB9AC \uC124\uBA85:
427
+ - basic: \uC774\uB984, \uB098\uC774, \uC704\uCE58 \uB4F1 \uAE30\uBCF8 \uC815\uBCF4
428
+ - professional: \uC9C1\uC5C5, \uD68C\uC0AC, \uC804\uBB38 \uBD84\uC57C
429
+ - preferences: \uC120\uD638\uD558\uB294 \uC5B8\uC5B4, \uC2A4\uD0C0\uC77C, \uAD00\uC2EC\uC0AC
430
+ - context: \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8, \uBAA9\uD45C, \uC9C4\uD589 \uC911\uC778 \uC791\uC5C5
431
+
432
+ \uADDC\uCE59:
433
+ 1. \uBA85\uC2DC\uC801\uC73C\uB85C \uC5B8\uAE09\uB41C \uC815\uBCF4\uB9CC \uCD94\uCD9C (\uCD94\uB860 \uAE08\uC9C0)
434
+ 2. \uAC1C\uC778\uC815\uBCF4 \uBBFC\uAC10\uB3C4\uAC00 \uB192\uC740 \uC815\uBCF4(\uC8FC\uBBFC\uBC88\uD638, \uBE44\uBC00\uBC88\uD638, \uCE74\uB4DC\uBC88\uD638 \uB4F1)\uB294 \uC81C\uC678
435
+ 3. confidence\uB294 \uC815\uBCF4\uC758 \uD655\uC2E4\uC131 (0.8 \uC774\uC0C1 \uAD8C\uC7A5)
436
+ 4. \uB300\uD654\uC5D0\uC11C \uC911\uC694 \uC815\uBCF4\uAC00 \uC5C6\uC73C\uBA74 extracted\uB97C \uBE48 \uBC30\uC5F4\uB85C \uBC18\uD658
437
+ 5. key\uB294 \uC601\uBB38 camelCase\uB85C \uC791\uC131 (\uC608: userName, currentProject)`;
438
+ };
439
+ var parseExtractionResult = (text) => {
440
+ try {
441
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
442
+ if (!jsonMatch) {
443
+ return { extracted: [], shouldStore: false };
444
+ }
445
+ const parsed = JSON.parse(jsonMatch[0]);
446
+ if (!parsed.extracted || !Array.isArray(parsed.extracted)) {
447
+ return { extracted: [], shouldStore: false };
448
+ }
449
+ const validExtracted = parsed.extracted.filter(
450
+ (item) => item.category && item.key && item.value !== void 0 && typeof item.confidence === "number"
451
+ );
452
+ return {
453
+ extracted: validExtracted,
454
+ shouldStore: parsed.shouldStore ?? validExtracted.length > 0
455
+ };
456
+ } catch {
457
+ return { extracted: [], shouldStore: false };
458
+ }
459
+ };
460
+ function useInfoExtraction(options) {
461
+ const [isExtracting, setIsExtracting] = (0, import_react2.useState)(false);
462
+ const [lastExtraction, setLastExtraction] = (0, import_react2.useState)(null);
463
+ const { apiEndpoint, model, minConfidence = 0.8, globalMemory } = options;
464
+ const extractInfo = (0, import_react2.useCallback)(
465
+ async (messages) => {
466
+ if (messages.length === 0) {
467
+ return [];
468
+ }
469
+ setIsExtracting(true);
470
+ try {
471
+ const prompt = buildExtractionPrompt(messages);
472
+ const response = await fetch(apiEndpoint, {
473
+ method: "POST",
474
+ headers: { "Content-Type": "application/json" },
475
+ body: JSON.stringify({
476
+ messages: [{ role: "user", content: prompt }],
477
+ model: model || "default"
478
+ })
479
+ });
480
+ if (!response.ok) {
481
+ throw new Error(`API error: ${response.status}`);
482
+ }
483
+ const reader = response.body?.getReader();
484
+ if (!reader) {
485
+ return [];
486
+ }
487
+ const decoder = new TextDecoder();
488
+ let buffer = "";
489
+ let fullResponse = "";
490
+ while (true) {
491
+ const { done, value } = await reader.read();
492
+ if (done) break;
493
+ buffer += decoder.decode(value, { stream: true });
494
+ const lines = buffer.split("\n");
495
+ buffer = lines.pop() || "";
496
+ for (const line of lines) {
497
+ if (line.startsWith("data: ")) {
498
+ const data = line.slice(6);
499
+ if (data === "[DONE]") continue;
500
+ try {
501
+ const parsed = JSON.parse(data);
502
+ if (parsed.content) fullResponse += parsed.content;
503
+ } catch {
504
+ }
505
+ }
506
+ }
507
+ }
508
+ const result = parseExtractionResult(fullResponse);
509
+ const extracted = result.extracted.filter(
510
+ (item) => item.confidence >= minConfidence
511
+ );
512
+ if (result.shouldStore && globalMemory) {
513
+ for (const item of extracted) {
514
+ await globalMemory.set(`${item.category}.${item.key}`, item.value, {
515
+ category: item.category,
516
+ confidence: item.confidence,
517
+ source: item.source
518
+ });
519
+ }
520
+ }
521
+ setLastExtraction(extracted);
522
+ return extracted;
523
+ } catch (error) {
524
+ console.error("[useInfoExtraction] Failed to extract:", error);
525
+ return [];
526
+ } finally {
527
+ setIsExtracting(false);
528
+ }
529
+ },
530
+ [apiEndpoint, model, minConfidence, globalMemory]
531
+ );
532
+ return {
533
+ extractInfo,
534
+ isExtracting,
535
+ lastExtraction
536
+ };
537
+ }
538
+
70
539
  // src/react/hooks/useChatUI.ts
71
540
  var DEFAULT_STORAGE_KEY = "chatllm_sessions";
72
541
  var DEFAULT_COMPRESSION_THRESHOLD = 20;
73
542
  var DEFAULT_KEEP_RECENT = 6;
543
+ var DEFAULT_RECOMPRESSION_THRESHOLD = 10;
544
+ var DEFAULT_TOKEN_LIMIT = 8e3;
74
545
  var generateId = (prefix) => `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
75
546
  var generateTitle = (messages) => {
76
547
  const firstUserMessage = messages.find((m) => m.role === "user");
@@ -91,28 +562,50 @@ var useChatUI = (options) => {
91
562
  keepRecentMessages = DEFAULT_KEEP_RECENT,
92
563
  onSendMessage,
93
564
  onSessionChange,
94
- onError
565
+ onError,
566
+ // Memory options
567
+ useGlobalMemoryEnabled = true,
568
+ globalMemoryConfig,
569
+ enableAutoExtraction = true
95
570
  } = options;
96
- const [sessions, setSessions] = (0, import_react.useState)([]);
97
- const [currentSessionId, setCurrentSessionId] = (0, import_react.useState)(null);
98
- const [input, setInput] = (0, import_react.useState)("");
99
- const [isLoading, setIsLoading] = (0, import_react.useState)(false);
100
- const [selectedModel, setSelectedModel] = (0, import_react.useState)(initialModel || models[0]?.id || "");
101
- const [sidebarOpen, setSidebarOpen] = (0, import_react.useState)(true);
102
- const [settingsOpen, setSettingsOpen] = (0, import_react.useState)(false);
103
- const [quotedText, setQuotedText] = (0, import_react.useState)(null);
104
- const [selectedAction, setSelectedAction] = (0, import_react.useState)(null);
105
- const [copiedMessageId, setCopiedMessageId] = (0, import_react.useState)(null);
106
- const [editingMessageId, setEditingMessageId] = (0, import_react.useState)(null);
107
- const [personalization, setPersonalization] = (0, import_react.useState)({
571
+ const [sessions, setSessions] = (0, import_react3.useState)([]);
572
+ const [currentSessionId, setCurrentSessionId] = (0, import_react3.useState)(null);
573
+ const [input, setInput] = (0, import_react3.useState)("");
574
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
575
+ const [selectedModel, setSelectedModel] = (0, import_react3.useState)(initialModel || models[0]?.id || "");
576
+ const [sidebarOpen, setSidebarOpen] = (0, import_react3.useState)(true);
577
+ const [settingsOpen, setSettingsOpen] = (0, import_react3.useState)(false);
578
+ const [quotedText, setQuotedText] = (0, import_react3.useState)(null);
579
+ const [selectedAction, setSelectedAction] = (0, import_react3.useState)(null);
580
+ const [copiedMessageId, setCopiedMessageId] = (0, import_react3.useState)(null);
581
+ const [editingMessageId, setEditingMessageId] = (0, import_react3.useState)(null);
582
+ const [personalization, setPersonalization] = (0, import_react3.useState)({
108
583
  ...DEFAULT_PERSONALIZATION,
109
584
  ...initialPersonalization
110
585
  });
111
- const [activeAlternatives, setActiveAlternatives] = (0, import_react.useState)({});
112
- const abortControllerRef = (0, import_react.useRef)(null);
586
+ const [activeAlternatives, setActiveAlternatives] = (0, import_react3.useState)({});
587
+ const abortControllerRef = (0, import_react3.useRef)(null);
588
+ const memoryOptions = (0, import_react3.useMemo)(
589
+ () => ({
590
+ storageType: globalMemoryConfig?.storageType || "localStorage",
591
+ storageKey: globalMemoryConfig?.localStorage?.key || `${storageKey}_memory`,
592
+ apiEndpoint: globalMemoryConfig?.postgresql?.apiEndpoint,
593
+ userId: globalMemoryConfig?.postgresql?.userId,
594
+ authToken: globalMemoryConfig?.postgresql?.authToken
595
+ }),
596
+ [globalMemoryConfig, storageKey]
597
+ );
598
+ const globalMemory = useGlobalMemoryEnabled ? useGlobalMemory(memoryOptions) : null;
599
+ const infoExtraction = useInfoExtraction({
600
+ apiEndpoint,
601
+ model: selectedModel,
602
+ minConfidence: 0.8,
603
+ globalMemory
604
+ });
113
605
  const currentSession = sessions.find((s) => s.id === currentSessionId) || null;
114
606
  const messages = currentSession?.messages || [];
115
- (0, import_react.useEffect)(() => {
607
+ const compressionState = currentSession?.compressionState || null;
608
+ (0, import_react3.useEffect)(() => {
116
609
  if (typeof window === "undefined") return;
117
610
  const saved = localStorage.getItem(storageKey);
118
611
  if (saved) {
@@ -134,20 +627,20 @@ var useChatUI = (options) => {
134
627
  }
135
628
  }
136
629
  }, [storageKey]);
137
- (0, import_react.useEffect)(() => {
630
+ (0, import_react3.useEffect)(() => {
138
631
  if (typeof window === "undefined") return;
139
632
  if (sessions.length > 0) {
140
633
  localStorage.setItem(storageKey, JSON.stringify(sessions));
141
634
  }
142
635
  }, [sessions, storageKey]);
143
- (0, import_react.useEffect)(() => {
636
+ (0, import_react3.useEffect)(() => {
144
637
  if (typeof window === "undefined") return;
145
638
  localStorage.setItem(`${storageKey}_personalization`, JSON.stringify(personalization));
146
639
  }, [personalization, storageKey]);
147
- (0, import_react.useEffect)(() => {
640
+ (0, import_react3.useEffect)(() => {
148
641
  onSessionChange?.(currentSession);
149
642
  }, [currentSession, onSessionChange]);
150
- const buildSystemPrompt = (0, import_react.useCallback)(() => {
643
+ const buildSystemPrompt = (0, import_react3.useCallback)(() => {
151
644
  const parts = [];
152
645
  const { userProfile, responseStyle, language } = personalization;
153
646
  if (userProfile.nickname) {
@@ -175,9 +668,16 @@ var useChatUI = (options) => {
175
668
  const languageNames = { ko: "\uD55C\uAD6D\uC5B4", en: "\uC601\uC5B4", ja: "\uC77C\uBCF8\uC5B4" };
176
669
  parts.push(`\uC751\uB2F5 \uC5B8\uC5B4: ${languageNames[language] || language}\uB85C \uC751\uB2F5\uD574\uC8FC\uC138\uC694.`);
177
670
  }
671
+ if (useGlobalMemoryEnabled && globalMemory && personalization.useMemory) {
672
+ const memoryContext = globalMemory.toPromptContext();
673
+ if (memoryContext) {
674
+ parts.push("");
675
+ parts.push(memoryContext);
676
+ }
677
+ }
178
678
  return parts.length > 0 ? parts.join("\n") : "";
179
- }, [personalization]);
180
- const compressContext = (0, import_react.useCallback)(async (messagesToCompress, model) => {
679
+ }, [personalization, globalMemory, useGlobalMemoryEnabled]);
680
+ const compressContext = (0, import_react3.useCallback)(async (messagesToCompress, model) => {
181
681
  const conversationText = messagesToCompress.map((m) => `${m.role === "user" ? "\uC0AC\uC6A9\uC790" : "AI"}: ${m.content}`).join("\n\n");
182
682
  const summaryPrompt = `\uB2E4\uC74C \uB300\uD654 \uB0B4\uC6A9\uC744 \uD575\uC2EC \uC815\uBCF4\uB9CC \uC720\uC9C0\uD558\uBA74\uC11C \uAC04\uACB0\uD558\uAC8C \uC694\uC57D\uD574\uC8FC\uC138\uC694.
183
683
  \uC911\uC694\uD55C \uACB0\uC815\uC0AC\uD56D, \uC0AC\uC6A9\uC790 \uC694\uAD6C\uC0AC\uD56D, \uB9E5\uB77D \uC815\uBCF4\uB97C \uBCF4\uC874\uD558\uC138\uC694.
@@ -224,7 +724,68 @@ ${conversationText}
224
724
  return "";
225
725
  }
226
726
  }, [apiEndpoint]);
227
- const newSession = (0, import_react.useCallback)(() => {
727
+ const incrementalCompressContext = (0, import_react3.useCallback)(
728
+ async (existingSummary, newMessages, model) => {
729
+ const newConversation = newMessages.map((m) => `${m.role === "user" ? "\uC0AC\uC6A9\uC790" : "AI"}: ${m.content}`).join("\n\n");
730
+ const mergePrompt = `\uAE30\uC874 \uB300\uD654 \uC694\uC57D\uACFC \uC0C8\uB85C\uC6B4 \uB300\uD654 \uB0B4\uC6A9\uC744 \uD1B5\uD569\uD558\uC5EC \uD558\uB098\uC758 \uC694\uC57D\uC73C\uB85C \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694.
731
+
732
+ [\uAE30\uC874 \uC694\uC57D]
733
+ ${existingSummary}
734
+
735
+ [\uC0C8\uB85C\uC6B4 \uB300\uD654]
736
+ ${newConversation}
737
+
738
+ \uD1B5\uD569 \uC694\uC57D \uC791\uC131 \uC2DC \uB2E4\uC74C\uC744 \uC9C0\uCF1C\uC8FC\uC138\uC694:
739
+ 1. \uD575\uC2EC \uACB0\uC815\uC0AC\uD56D, \uC0AC\uC6A9\uC790 \uC694\uAD6C\uC0AC\uD56D, \uB9E5\uB77D \uC815\uBCF4 \uBCF4\uC874
740
+ 2. \uC911\uBCF5 \uC815\uBCF4 \uC81C\uAC70
741
+ 3. \uC2DC\uAC04\uC21C \uD750\uB984 \uC720\uC9C0
742
+ 4. 300\uC790 \uC774\uB0B4\uB85C \uAC04\uACB0\uD558\uAC8C
743
+
744
+ \uD1B5\uD569 \uC694\uC57D:`;
745
+ try {
746
+ const response = await fetch(apiEndpoint, {
747
+ method: "POST",
748
+ headers: { "Content-Type": "application/json" },
749
+ body: JSON.stringify({
750
+ messages: [{ role: "user", content: mergePrompt }],
751
+ model
752
+ })
753
+ });
754
+ if (!response.ok) return existingSummary;
755
+ const reader = response.body?.getReader();
756
+ if (!reader) return existingSummary;
757
+ const decoder = new TextDecoder();
758
+ let buffer = "";
759
+ let summary = "";
760
+ while (true) {
761
+ const { done, value } = await reader.read();
762
+ if (done) break;
763
+ buffer += decoder.decode(value, { stream: true });
764
+ const lines = buffer.split("\n");
765
+ buffer = lines.pop() || "";
766
+ for (const line of lines) {
767
+ if (line.startsWith("data: ")) {
768
+ const data = line.slice(6);
769
+ if (data === "[DONE]") continue;
770
+ try {
771
+ const parsed = JSON.parse(data);
772
+ if (parsed.content) summary += parsed.content;
773
+ } catch {
774
+ }
775
+ }
776
+ }
777
+ }
778
+ return summary || existingSummary;
779
+ } catch {
780
+ return existingSummary;
781
+ }
782
+ },
783
+ [apiEndpoint]
784
+ );
785
+ const estimateTokens = (0, import_react3.useCallback)((messages2) => {
786
+ return messages2.reduce((sum, m) => sum + Math.ceil(m.content.length / 4), 0);
787
+ }, []);
788
+ const newSession = (0, import_react3.useCallback)(() => {
228
789
  const now = Date.now();
229
790
  const newSess = {
230
791
  id: generateId("session"),
@@ -237,14 +798,14 @@ ${conversationText}
237
798
  setSessions((prev) => [newSess, ...prev]);
238
799
  setCurrentSessionId(newSess.id);
239
800
  }, [selectedModel]);
240
- const selectSession = (0, import_react.useCallback)((id) => {
801
+ const selectSession = (0, import_react3.useCallback)((id) => {
241
802
  const session = sessions.find((s) => s.id === id);
242
803
  if (session) {
243
804
  setCurrentSessionId(id);
244
805
  setSelectedModel(session.model);
245
806
  }
246
807
  }, [sessions]);
247
- const deleteSession = (0, import_react.useCallback)((id) => {
808
+ const deleteSession = (0, import_react3.useCallback)((id) => {
248
809
  setSessions((prev) => {
249
810
  const filtered = prev.filter((s) => s.id !== id);
250
811
  if (currentSessionId === id) {
@@ -256,7 +817,7 @@ ${conversationText}
256
817
  return filtered;
257
818
  });
258
819
  }, [currentSessionId, storageKey]);
259
- const setModel = (0, import_react.useCallback)((model) => {
820
+ const setModel = (0, import_react3.useCallback)((model) => {
260
821
  setSelectedModel(model);
261
822
  if (currentSessionId) {
262
823
  setSessions(
@@ -264,10 +825,10 @@ ${conversationText}
264
825
  );
265
826
  }
266
827
  }, [currentSessionId]);
267
- const toggleSidebar = (0, import_react.useCallback)(() => setSidebarOpen((prev) => !prev), []);
268
- const openSettings = (0, import_react.useCallback)(() => setSettingsOpen(true), []);
269
- const closeSettings = (0, import_react.useCallback)(() => setSettingsOpen(false), []);
270
- const copyMessage = (0, import_react.useCallback)((content, id) => {
828
+ const toggleSidebar = (0, import_react3.useCallback)(() => setSidebarOpen((prev) => !prev), []);
829
+ const openSettings = (0, import_react3.useCallback)(() => setSettingsOpen(true), []);
830
+ const closeSettings = (0, import_react3.useCallback)(() => setSettingsOpen(false), []);
831
+ const copyMessage = (0, import_react3.useCallback)((content, id) => {
271
832
  if (typeof navigator !== "undefined") {
272
833
  navigator.clipboard.writeText(content).then(() => {
273
834
  setCopiedMessageId(id);
@@ -275,21 +836,21 @@ ${conversationText}
275
836
  });
276
837
  }
277
838
  }, []);
278
- const startEdit = (0, import_react.useCallback)((message) => {
839
+ const startEdit = (0, import_react3.useCallback)((message) => {
279
840
  if (message.role === "user") {
280
841
  setEditingMessageId(message.id);
281
842
  }
282
843
  }, []);
283
- const cancelEdit = (0, import_react.useCallback)(() => {
844
+ const cancelEdit = (0, import_react3.useCallback)(() => {
284
845
  setEditingMessageId(null);
285
846
  }, []);
286
- const stopGeneration = (0, import_react.useCallback)(() => {
847
+ const stopGeneration = (0, import_react3.useCallback)(() => {
287
848
  abortControllerRef.current?.abort();
288
849
  }, []);
289
- const updatePersonalization = (0, import_react.useCallback)((config) => {
850
+ const updatePersonalization = (0, import_react3.useCallback)((config) => {
290
851
  setPersonalization((prev) => ({ ...prev, ...config }));
291
852
  }, []);
292
- const sendMessage = (0, import_react.useCallback)(async (content) => {
853
+ const sendMessage = (0, import_react3.useCallback)(async (content) => {
293
854
  const messageContent = content || input;
294
855
  if (!messageContent.trim() || isLoading) return;
295
856
  let sessionId = currentSessionId;
@@ -355,17 +916,50 @@ ${finalContent}`;
355
916
  const session = sessions.find((s) => s.id === capturedSessionId);
356
917
  const existingMessages = session?.messages || [];
357
918
  let messagesToSend = [...existingMessages, userMessage];
358
- let contextSummary = session?.contextSummary;
359
- if (messagesToSend.length > contextCompressionThreshold && !contextSummary) {
919
+ const recompressionThreshold = DEFAULT_RECOMPRESSION_THRESHOLD;
920
+ const tokenLimit = DEFAULT_TOKEN_LIMIT;
921
+ let contextSummary = session?.compressionState?.contextSummary || session?.contextSummary;
922
+ const summaryAfterIndex = session?.compressionState?.summaryAfterIndex || session?.summaryAfterIndex || 0;
923
+ let compressionCount = session?.compressionState?.compressionCount || 0;
924
+ const newMessageCount = messagesToSend.length - summaryAfterIndex;
925
+ const estimatedTokensCount = estimateTokens(messagesToSend);
926
+ const needsInitialCompression = !contextSummary && messagesToSend.length > contextCompressionThreshold;
927
+ const needsRecompression = contextSummary && (newMessageCount > recompressionThreshold || estimatedTokensCount > tokenLimit * 0.7);
928
+ if (needsInitialCompression || needsRecompression) {
360
929
  const toCompress = messagesToSend.slice(0, -keepRecentMessages);
361
- const summary = await compressContext(toCompress, selectedModel);
930
+ let summary;
931
+ if (needsRecompression && contextSummary) {
932
+ const newMessages = toCompress.slice(summaryAfterIndex);
933
+ summary = await incrementalCompressContext(contextSummary, newMessages, selectedModel);
934
+ } else {
935
+ summary = await compressContext(toCompress, selectedModel);
936
+ }
362
937
  if (summary) {
363
938
  contextSummary = summary;
939
+ compressionCount += 1;
364
940
  setSessions(
365
941
  (prev) => prev.map(
366
- (s) => s.id === capturedSessionId ? { ...s, contextSummary: summary, summaryAfterIndex: toCompress.length } : s
942
+ (s) => s.id === capturedSessionId ? {
943
+ ...s,
944
+ contextSummary: summary,
945
+ summaryAfterIndex: toCompress.length,
946
+ compressionState: {
947
+ contextSummary: summary,
948
+ summaryAfterIndex: toCompress.length,
949
+ compressionCount,
950
+ lastCompressionAt: Date.now()
951
+ }
952
+ } : s
367
953
  )
368
954
  );
955
+ if (enableAutoExtraction && globalMemory) {
956
+ const messagesForExtraction = toCompress.map((m) => ({
957
+ role: m.role,
958
+ content: m.content
959
+ }));
960
+ infoExtraction.extractInfo(messagesForExtraction).catch(() => {
961
+ });
962
+ }
369
963
  }
370
964
  }
371
965
  let chatMessages;
@@ -517,7 +1111,7 @@ ${contextSummary}` },
517
1111
  onSendMessage,
518
1112
  onError
519
1113
  ]);
520
- const saveEdit = (0, import_react.useCallback)(async (content) => {
1114
+ const saveEdit = (0, import_react3.useCallback)(async (content) => {
521
1115
  if (!editingMessageId || !currentSession || !currentSessionId) return;
522
1116
  const messageIndex = currentSession.messages.findIndex((m) => m.id === editingMessageId);
523
1117
  if (messageIndex === -1) return;
@@ -534,7 +1128,7 @@ ${contextSummary}` },
534
1128
  setEditingMessageId(null);
535
1129
  await sendMessage(content);
536
1130
  }, [editingMessageId, currentSession, currentSessionId, sendMessage]);
537
- const regenerate = (0, import_react.useCallback)(async (messageId) => {
1131
+ const regenerate = (0, import_react3.useCallback)(async (messageId) => {
538
1132
  if (!currentSession || !currentSessionId || isLoading) return;
539
1133
  const assistantIndex = currentSession.messages.findIndex((m) => m.id === messageId);
540
1134
  if (assistantIndex === -1) return;
@@ -556,7 +1150,7 @@ ${contextSummary}` },
556
1150
  setInput(userMessage.content);
557
1151
  await sendMessage(userMessage.content);
558
1152
  }, [currentSession, currentSessionId, isLoading, sendMessage]);
559
- const askOtherModel = (0, import_react.useCallback)(async (messageId, targetModel) => {
1153
+ const askOtherModel = (0, import_react3.useCallback)(async (messageId, targetModel) => {
560
1154
  if (!currentSession || !currentSessionId || isLoading) return;
561
1155
  const assistantIndex = currentSession.messages.findIndex((m) => m.id === messageId);
562
1156
  if (assistantIndex === -1) return;
@@ -708,13 +1302,13 @@ ${currentSession.contextSummary}` },
708
1302
  onSendMessage,
709
1303
  onError
710
1304
  ]);
711
- const setActiveAlternative = (0, import_react.useCallback)((assistantMessageId, index) => {
1305
+ const setActiveAlternative = (0, import_react3.useCallback)((assistantMessageId, index) => {
712
1306
  setActiveAlternatives((prev) => ({
713
1307
  ...prev,
714
1308
  [assistantMessageId]: index
715
1309
  }));
716
1310
  }, []);
717
- const getActiveAlternative = (0, import_react.useCallback)((assistantMessageId) => {
1311
+ const getActiveAlternative = (0, import_react3.useCallback)((assistantMessageId) => {
718
1312
  return activeAlternatives[assistantMessageId] ?? 0;
719
1313
  }, [activeAlternatives]);
720
1314
  return {
@@ -756,7 +1350,18 @@ ${currentSession.contextSummary}` },
756
1350
  setActiveAlternative,
757
1351
  getActiveAlternative,
758
1352
  updatePersonalization,
759
- models
1353
+ models,
1354
+ // Memory
1355
+ globalMemory,
1356
+ compressionState,
1357
+ extractUserInfo: async () => {
1358
+ if (!enableAutoExtraction || !globalMemory) return;
1359
+ const recentMessages = messages.slice(-10).map((m) => ({
1360
+ role: m.role,
1361
+ content: m.content
1362
+ }));
1363
+ await infoExtraction.extractInfo(recentMessages);
1364
+ }
760
1365
  };
761
1366
  };
762
1367
 
@@ -1064,7 +1669,7 @@ var ChatSidebar = ({
1064
1669
  };
1065
1670
 
1066
1671
  // src/react/components/ChatHeader.tsx
1067
- var import_react2 = require("react");
1672
+ var import_react4 = require("react");
1068
1673
  var import_jsx_runtime3 = require("react/jsx-runtime");
1069
1674
  var ChatHeader = ({
1070
1675
  title,
@@ -1077,7 +1682,7 @@ var ChatHeader = ({
1077
1682
  showModelSelector = true,
1078
1683
  showSettings = true
1079
1684
  }) => {
1080
- const [modelDropdownOpen, setModelDropdownOpen] = (0, import_react2.useState)(false);
1685
+ const [modelDropdownOpen, setModelDropdownOpen] = (0, import_react4.useState)(false);
1081
1686
  const currentModel = models.find((m) => m.id === model);
1082
1687
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1083
1688
  "header",
@@ -1331,7 +1936,7 @@ var ChatHeader = ({
1331
1936
  };
1332
1937
 
1333
1938
  // src/react/components/ChatInput.tsx
1334
- var import_react3 = require("react");
1939
+ var import_react5 = require("react");
1335
1940
  var import_jsx_runtime4 = require("react/jsx-runtime");
1336
1941
  var ChatInput = ({
1337
1942
  value,
@@ -1347,9 +1952,9 @@ var ChatInput = ({
1347
1952
  onActionSelect,
1348
1953
  actions = []
1349
1954
  }) => {
1350
- const textareaRef = (0, import_react3.useRef)(null);
1351
- const [actionMenuOpen, setActionMenuOpen] = (0, import_react3.useState)(false);
1352
- (0, import_react3.useEffect)(() => {
1955
+ const textareaRef = (0, import_react5.useRef)(null);
1956
+ const [actionMenuOpen, setActionMenuOpen] = (0, import_react5.useState)(false);
1957
+ (0, import_react5.useEffect)(() => {
1353
1958
  if (textareaRef.current) {
1354
1959
  textareaRef.current.style.height = "auto";
1355
1960
  textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`;
@@ -1647,16 +2252,16 @@ var ChatInput = ({
1647
2252
  };
1648
2253
 
1649
2254
  // src/react/components/MessageList.tsx
1650
- var import_react7 = require("react");
2255
+ var import_react9 = require("react");
1651
2256
 
1652
2257
  // src/react/components/MessageBubble.tsx
1653
- var import_react6 = require("react");
2258
+ var import_react8 = require("react");
1654
2259
 
1655
2260
  // src/react/components/MarkdownRenderer.tsx
1656
- var import_react5 = __toESM(require("react"));
2261
+ var import_react7 = __toESM(require("react"));
1657
2262
 
1658
2263
  // src/react/components/LinkChip.tsx
1659
- var import_react4 = require("react");
2264
+ var import_react6 = require("react");
1660
2265
  var import_jsx_runtime5 = require("react/jsx-runtime");
1661
2266
  var getDomain = (url) => {
1662
2267
  try {
@@ -1710,7 +2315,7 @@ var LinkChip = ({
1710
2315
  index,
1711
2316
  style
1712
2317
  }) => {
1713
- const [isHovered, setIsHovered] = (0, import_react4.useState)(false);
2318
+ const [isHovered, setIsHovered] = (0, import_react6.useState)(false);
1714
2319
  const domain = getDomain(url);
1715
2320
  const shortName = getShortName(domain);
1716
2321
  const domainColor = getDomainColor(domain);
@@ -1824,6 +2429,8 @@ var import_jsx_runtime6 = require("react/jsx-runtime");
1824
2429
  var IMAGE_REGEX = /!\[([^\]]*)\]\(([^)]+)\)/g;
1825
2430
  var LINK_REGEX = /(?<!!)\[([^\]]+)\]\(([^)]+)\)/g;
1826
2431
  var SOURCE_LINKS_REGEX = /(\*{0,2}출처:?\*{0,2}\s*)?((?:(?<!!)\[`?[^\]]+`?\]\([^)]+\)\s*)+)/gi;
2432
+ var INLINE_CHOICE_PATTERN = /\[(\d+)\]\s*([^\[\n]+?)(?=\s*\[\d+\]|$)/g;
2433
+ var INLINE_CHOICE_LINE_PATTERN = /^(.+?)?\s*(\[\d+\][^\[]+(?:\[\d+\][^\[]+)+)$/;
1827
2434
  var CODE_BLOCK_REGEX = /```(\w*)\n?([\s\S]*?)```/g;
1828
2435
  var INLINE_CODE_REGEX = /`([^`]+)`/g;
1829
2436
  var BOLD_REGEX = /\*\*([^*]+)\*\*/g;
@@ -1843,6 +2450,30 @@ var parseSourceLinks = (text) => {
1843
2450
  }
1844
2451
  return links;
1845
2452
  };
2453
+ var parseInlineChoices = (text) => {
2454
+ const lineMatch = text.match(INLINE_CHOICE_LINE_PATTERN);
2455
+ if (!lineMatch) return null;
2456
+ const title = lineMatch[1]?.trim();
2457
+ const choicesText = lineMatch[2];
2458
+ const choices = [];
2459
+ let match;
2460
+ const regex = new RegExp(INLINE_CHOICE_PATTERN.source, "g");
2461
+ while ((match = regex.exec(choicesText)) !== null) {
2462
+ const number = parseInt(match[1]);
2463
+ const choiceText = match[2].trim();
2464
+ if (choiceText) {
2465
+ choices.push({
2466
+ number,
2467
+ text: choiceText,
2468
+ fullText: `[${number}] ${choiceText}`
2469
+ });
2470
+ }
2471
+ }
2472
+ if (choices.length >= 2) {
2473
+ return { title, choices };
2474
+ }
2475
+ return null;
2476
+ };
1846
2477
  var parseInlineElements = (text, key) => {
1847
2478
  const elements = [];
1848
2479
  let lastIndex = 0;
@@ -1979,7 +2610,7 @@ var MarkdownTable = ({ data }) => {
1979
2610
  );
1980
2611
  };
1981
2612
  var CodeBlock = ({ language, code }) => {
1982
- const [copied, setCopied] = import_react5.default.useState(false);
2613
+ const [copied, setCopied] = import_react7.default.useState(false);
1983
2614
  const handleCopy = async () => {
1984
2615
  try {
1985
2616
  await navigator.clipboard.writeText(code);
@@ -2062,9 +2693,9 @@ var CodeBlock = ({ language, code }) => {
2062
2693
  );
2063
2694
  };
2064
2695
  var ImageWithCopyButton = ({ src, alt, imageKey }) => {
2065
- const [isHovered, setIsHovered] = import_react5.default.useState(false);
2066
- const [copyState, setCopyState] = import_react5.default.useState("idle");
2067
- const imgRef = import_react5.default.useRef(null);
2696
+ const [isHovered, setIsHovered] = import_react7.default.useState(false);
2697
+ const [copyState, setCopyState] = import_react7.default.useState("idle");
2698
+ const imgRef = import_react7.default.useRef(null);
2068
2699
  const getImageBlob = async () => {
2069
2700
  const img = imgRef.current;
2070
2701
  if (img && img.complete && img.naturalWidth > 0) {
@@ -2279,8 +2910,8 @@ var ImageWithCopyButton = ({ src, alt, imageKey }) => {
2279
2910
  imageKey
2280
2911
  );
2281
2912
  };
2282
- var ChoiceButtons = ({ choices, onChoiceClick }) => {
2283
- const [hoveredIndex, setHoveredIndex] = import_react5.default.useState(null);
2913
+ var ChoiceButtons = ({ choices, title, onChoiceClick }) => {
2914
+ const [hoveredIndex, setHoveredIndex] = import_react7.default.useState(null);
2284
2915
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2285
2916
  "div",
2286
2917
  {
@@ -2300,14 +2931,14 @@ var ChoiceButtons = ({ choices, onChoiceClick }) => {
2300
2931
  "div",
2301
2932
  {
2302
2933
  style: {
2303
- fontSize: "12px",
2934
+ fontSize: title ? "14px" : "12px",
2304
2935
  fontWeight: 600,
2305
- color: "var(--chatllm-text-muted, #6b7280)",
2936
+ color: title ? "var(--chatllm-text, #374151)" : "var(--chatllm-text-muted, #6b7280)",
2306
2937
  marginBottom: "4px",
2307
- textTransform: "uppercase",
2308
- letterSpacing: "0.5px"
2938
+ textTransform: title ? "none" : "uppercase",
2939
+ letterSpacing: title ? "normal" : "0.5px"
2309
2940
  },
2310
- children: "\uC120\uD0DD\uD558\uC138\uC694"
2941
+ children: title || "\uC120\uD0DD\uD558\uC138\uC694"
2311
2942
  }
2312
2943
  ),
2313
2944
  choices.map((choice, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
@@ -2403,7 +3034,7 @@ var SourceLinksSection = ({ links, label }) => {
2403
3034
  );
2404
3035
  };
2405
3036
  var MarkdownRenderer = ({ content, className, onChoiceClick }) => {
2406
- const rendered = (0, import_react5.useMemo)(() => {
3037
+ const rendered = (0, import_react7.useMemo)(() => {
2407
3038
  const elements = [];
2408
3039
  let processedContent = content;
2409
3040
  const codeBlocks = [];
@@ -2501,7 +3132,7 @@ var MarkdownRenderer = ({ content, className, onChoiceClick }) => {
2501
3132
  borderRadius: "0 8px 8px 0",
2502
3133
  color: "var(--chatllm-text, #374151)"
2503
3134
  },
2504
- children: blockquoteLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react5.default.Fragment, { children: [
3135
+ children: blockquoteLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react7.default.Fragment, { children: [
2505
3136
  parseInlineElements(line, `bq-line-${i}`),
2506
3137
  i < blockquoteLines.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("br", {})
2507
3138
  ] }, i))
@@ -2637,6 +3268,23 @@ var MarkdownRenderer = ({ content, className, onChoiceClick }) => {
2637
3268
  elements.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("br", {}, `br-${lineIndex}`));
2638
3269
  return;
2639
3270
  }
3271
+ if (onChoiceClick) {
3272
+ const choiceBlock = parseInlineChoices(line);
3273
+ if (choiceBlock) {
3274
+ elements.push(
3275
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3276
+ ChoiceButtons,
3277
+ {
3278
+ choices: choiceBlock.choices,
3279
+ title: choiceBlock.title,
3280
+ onChoiceClick
3281
+ },
3282
+ `inline-choices-${lineIndex}`
3283
+ )
3284
+ );
3285
+ return;
3286
+ }
3287
+ }
2640
3288
  elements.push(
2641
3289
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: { margin: "4px 0" }, children: parseInlineElements(line, `p-${lineIndex}`) }, `p-${lineIndex}`)
2642
3290
  );
@@ -2679,8 +3327,8 @@ var MessageBubble = ({
2679
3327
  nextAssistantMessage,
2680
3328
  onChoiceClick
2681
3329
  }) => {
2682
- const [showActions, setShowActions] = (0, import_react6.useState)(false);
2683
- const [showModelMenu, setShowModelMenu] = (0, import_react6.useState)(false);
3330
+ const [showActions, setShowActions] = (0, import_react8.useState)(false);
3331
+ const [showModelMenu, setShowModelMenu] = (0, import_react8.useState)(false);
2684
3332
  const isUser = message.role === "user";
2685
3333
  const isAssistant = message.role === "assistant";
2686
3334
  const relevantAlternatives = isUser ? alternatives : message.alternatives;
@@ -3073,14 +3721,14 @@ var MessageList = ({
3073
3721
  editingId,
3074
3722
  onChoiceClick
3075
3723
  }) => {
3076
- const messagesEndRef = (0, import_react7.useRef)(null);
3077
- const containerRef = (0, import_react7.useRef)(null);
3078
- const [selectedText, setSelectedText] = (0, import_react7.useState)("");
3079
- const [selectionPosition, setSelectionPosition] = (0, import_react7.useState)(null);
3080
- (0, import_react7.useEffect)(() => {
3724
+ const messagesEndRef = (0, import_react9.useRef)(null);
3725
+ const containerRef = (0, import_react9.useRef)(null);
3726
+ const [selectedText, setSelectedText] = (0, import_react9.useState)("");
3727
+ const [selectionPosition, setSelectionPosition] = (0, import_react9.useState)(null);
3728
+ (0, import_react9.useEffect)(() => {
3081
3729
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
3082
3730
  }, [messages]);
3083
- const handleMouseUp = (0, import_react7.useCallback)(() => {
3731
+ const handleMouseUp = (0, import_react9.useCallback)(() => {
3084
3732
  const selection = window.getSelection();
3085
3733
  const text = selection?.toString().trim();
3086
3734
  if (text && text.length > 0) {
@@ -3457,7 +4105,7 @@ var EmptyState = ({
3457
4105
  };
3458
4106
 
3459
4107
  // src/react/components/MemoryPanel.tsx
3460
- var import_react8 = require("react");
4108
+ var import_react10 = require("react");
3461
4109
  var import_jsx_runtime10 = require("react/jsx-runtime");
3462
4110
  var categoryLabels = {
3463
4111
  context: "\uB300\uD654 \uCEE8\uD14D\uC2A4\uD2B8",
@@ -3479,8 +4127,8 @@ var MemoryPanel = ({
3479
4127
  isOpen,
3480
4128
  onToggle
3481
4129
  }) => {
3482
- const [expandedId, setExpandedId] = (0, import_react8.useState)(null);
3483
- const [activeTab, setActiveTab] = (0, import_react8.useState)("all");
4130
+ const [expandedId, setExpandedId] = (0, import_react10.useState)(null);
4131
+ const [activeTab, setActiveTab] = (0, import_react10.useState)("all");
3484
4132
  const filteredItems = activeTab === "all" ? items : items.filter((item) => item.category === activeTab);
3485
4133
  const formatDate2 = (timestamp) => {
3486
4134
  const date = new Date(timestamp);
@@ -3798,7 +4446,7 @@ var MemoryPanel = ({
3798
4446
  };
3799
4447
 
3800
4448
  // src/react/components/SettingsModal.tsx
3801
- var import_react9 = require("react");
4449
+ var import_react11 = require("react");
3802
4450
  var import_jsx_runtime11 = require("react/jsx-runtime");
3803
4451
  var DEFAULT_PERSONALIZATION2 = {
3804
4452
  responseStyle: {
@@ -3823,8 +4471,8 @@ var SettingsModal = ({
3823
4471
  apiKeyLabel = "API Key",
3824
4472
  apiKeyDescription = "Cloud \uBAA8\uB378 \uC0AC\uC6A9\uC5D0 \uD544\uC694\uD569\uB2C8\uB2E4"
3825
4473
  }) => {
3826
- const [activeTab, setActiveTab] = (0, import_react9.useState)("general");
3827
- const [localApiKey, setLocalApiKey] = (0, import_react9.useState)(apiKey);
4474
+ const [activeTab, setActiveTab] = (0, import_react11.useState)("general");
4475
+ const [localApiKey, setLocalApiKey] = (0, import_react11.useState)(apiKey);
3828
4476
  if (!isOpen) return null;
3829
4477
  const updateResponseStyle = (key, value) => {
3830
4478
  onPersonalizationChange({
@@ -4432,7 +5080,7 @@ var ChatUI = ({
4432
5080
  onSessionChange,
4433
5081
  onError
4434
5082
  }) => {
4435
- import_react10.default.useEffect(() => {
5083
+ import_react12.default.useEffect(() => {
4436
5084
  injectStyles();
4437
5085
  }, []);
4438
5086
  const hookOptions = {
@@ -4500,8 +5148,8 @@ var ChatUI = ({
4500
5148
  const handleChoiceClick = (choice) => {
4501
5149
  setInput(choice.text);
4502
5150
  };
4503
- const [memoryPanelOpen, setMemoryPanelOpen] = (0, import_react10.useState)(false);
4504
- const memoryItems = import_react10.default.useMemo(() => {
5151
+ const [memoryPanelOpen, setMemoryPanelOpen] = (0, import_react12.useState)(false);
5152
+ const memoryItems = import_react12.default.useMemo(() => {
4505
5153
  const items = [];
4506
5154
  if (currentSession?.contextSummary) {
4507
5155
  items.push({
@@ -4683,4 +5331,22 @@ var ChatUI = ({
4683
5331
  SettingsModal,
4684
5332
  useChatUI
4685
5333
  });
5334
+ /**
5335
+ * @description localStorage 기반 메모리 저장소 어댑터
5336
+ * @license MIT
5337
+ */
5338
+ /**
5339
+ * @description PostgreSQL REST API 기반 메모리 저장소 어댑터
5340
+ * @license MIT
5341
+ */
5342
+ /**
5343
+ * @description 글로벌 메모리 관리 훅
5344
+ * localStorage 또는 PostgreSQL API를 통해 세션 간 정보 유지
5345
+ * @license MIT
5346
+ */
5347
+ /**
5348
+ * @description 자동 정보 추출 훅
5349
+ * LLM을 사용하여 대화에서 중요 정보를 자동으로 추출
5350
+ * @license MIT
5351
+ */
4686
5352
  //# sourceMappingURL=index.js.map