@dipertq/dsh-openviking-status 0.1.5

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/lib/client.cjs ADDED
@@ -0,0 +1,1456 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@dipertq/dsh-openviking-status",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ "use strict";
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __export = (target, all) => {
12
+ for (var name2 in all)
13
+ __defProp(target, name2, { get: all[name2], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
+
25
+ // src/client/index.tsx
26
+ var client_exports = {};
27
+ __export(client_exports, {
28
+ COMMIT_THRESHOLD: () => COMMIT_THRESHOLD,
29
+ DEFAULT_OPENVIKING_ENDPOINT: () => DEFAULT_OPENVIKING_ENDPOINT,
30
+ OpenVikingClient: () => OpenVikingClient,
31
+ OpenVikingStatusChip: () => OpenVikingStatusChip,
32
+ OpenVikingStatusPopover: () => OpenVikingStatusPopover,
33
+ apply: () => apply,
34
+ checkHealth: () => checkHealth,
35
+ commitSession: () => commitSession,
36
+ defaultOpenVikingClient: () => defaultOpenVikingClient,
37
+ fetchSession: () => fetchSession,
38
+ formatEndpoint: () => formatEndpoint,
39
+ formatMemoryLeafName: () => formatMemoryLeafName,
40
+ formatPendingTokens: () => formatPendingTokens,
41
+ formatRelativeTime: () => formatRelativeTime,
42
+ formatStatusLabel: () => formatStatusLabel,
43
+ formatTooltipTitle: () => formatTooltipTitle,
44
+ getCategoryBadgeStyle: () => getCategoryBadgeStyle,
45
+ getFallbackSessionMessages: () => getFallbackSessionMessages,
46
+ getProgressBarColor: () => getProgressBarColor,
47
+ getProgressBarPercent: () => getProgressBarPercent,
48
+ getSession: () => getSession,
49
+ getStatusGlow: () => getStatusGlow,
50
+ getStatusIndicatorColor: () => getStatusIndicatorColor,
51
+ handleEscapeKey: () => handleEscapeKey,
52
+ inferCategory: () => inferCategory,
53
+ inject: () => inject,
54
+ name: () => name,
55
+ parseRecalledMemories: () => parseRecalledMemories,
56
+ resolveApiKey: () => resolveApiKey,
57
+ resolveEndpoint: () => resolveEndpoint,
58
+ truncateSessionId: () => truncateSessionId
59
+ });
60
+ module.exports = __toCommonJS(client_exports);
61
+
62
+ // src/client/OpenVikingStatusChip.tsx
63
+ var import_react2 = require("react");
64
+
65
+ // src/client/api.ts
66
+ var DEFAULT_OPENVIKING_ENDPOINT = "http://127.0.0.1:1933";
67
+ function resolveEndpoint(endpoint) {
68
+ if (endpoint && endpoint.trim().length > 0) {
69
+ return endpoint.trim().replace(/\/+$/, "");
70
+ }
71
+ if (typeof window !== "undefined") {
72
+ const win = window;
73
+ if (typeof win.__OPENVIKING_ENDPOINT__ === "string" && win.__OPENVIKING_ENDPOINT__.trim()) {
74
+ return win.__OPENVIKING_ENDPOINT__.trim().replace(/\/+$/, "");
75
+ }
76
+ }
77
+ if (typeof localStorage !== "undefined") {
78
+ try {
79
+ const stored = localStorage.getItem("openviking_endpoint") || localStorage.getItem("OPENVIKING_ENDPOINT");
80
+ if (stored && stored.trim()) {
81
+ return stored.trim().replace(/\/+$/, "");
82
+ }
83
+ } catch {
84
+ }
85
+ }
86
+ return DEFAULT_OPENVIKING_ENDPOINT;
87
+ }
88
+ function resolveApiKey(apiKey) {
89
+ if (apiKey && apiKey.trim().length > 0) {
90
+ return apiKey.trim();
91
+ }
92
+ if (typeof window !== "undefined") {
93
+ const win = window;
94
+ if (typeof win.__OPENVIKING_API_KEY__ === "string" && win.__OPENVIKING_API_KEY__.trim()) {
95
+ return win.__OPENVIKING_API_KEY__.trim();
96
+ }
97
+ }
98
+ if (typeof localStorage !== "undefined") {
99
+ try {
100
+ const stored = localStorage.getItem("openviking_api_key") || localStorage.getItem("OPENVIKING_API_KEY");
101
+ if (stored && stored.trim()) {
102
+ return stored.trim();
103
+ }
104
+ } catch {
105
+ }
106
+ }
107
+ return void 0;
108
+ }
109
+ var OpenVikingClient = class {
110
+ endpoint;
111
+ apiKey;
112
+ resolvedSessionIds = /* @__PURE__ */ new Map();
113
+ constructor(endpoint, apiKey) {
114
+ this.endpoint = resolveEndpoint(endpoint);
115
+ this.apiKey = resolveApiKey(apiKey);
116
+ }
117
+ /**
118
+ * Формирование заголовков запроса, включая опциональный заголовок авторизации
119
+ */
120
+ getHeaders(customHeaders) {
121
+ const headers = {
122
+ "Content-Type": "application/json",
123
+ ...customHeaders
124
+ };
125
+ if (this.apiKey) {
126
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
127
+ }
128
+ return headers;
129
+ }
130
+ /**
131
+ * Генерация кандидатов ID сессии для fallback-разрешения:
132
+ * dsh-session-${id} <-> dsh-${id}
133
+ */
134
+ getCandidateSessionIds(sessionId) {
135
+ const raw = sessionId.trim();
136
+ const candidates = [];
137
+ const cached = this.resolvedSessionIds.get(raw);
138
+ if (cached) {
139
+ candidates.push(cached);
140
+ }
141
+ if (raw.startsWith("dsh-session-")) {
142
+ const suffix = raw.slice("dsh-session-".length);
143
+ candidates.push(raw, `dsh-${suffix}`);
144
+ } else if (raw.startsWith("dsh-")) {
145
+ const suffix = raw.slice("dsh-".length);
146
+ candidates.push(raw, `dsh-session-${suffix}`);
147
+ } else {
148
+ candidates.push(`dsh-session-${raw}`, `dsh-${raw}`, raw);
149
+ }
150
+ return Array.from(new Set(candidates));
151
+ }
152
+ /**
153
+ * Проверка доступности и состояния сервиса OpenViking
154
+ */
155
+ async checkHealth() {
156
+ try {
157
+ const res = await fetch(`${this.endpoint}/health`, {
158
+ method: "GET",
159
+ headers: this.getHeaders()
160
+ });
161
+ if (!res.ok) {
162
+ return {
163
+ ok: false,
164
+ error: `HTTP ${res.status}: ${res.statusText}`
165
+ };
166
+ }
167
+ const body = await res.json().catch(() => ({}));
168
+ const isOk = body.ok !== false && body.status !== "error" && (body.ok === true || body.status === "ok" || body.status === "healthy" || res.ok);
169
+ return {
170
+ ok: isOk,
171
+ version: typeof body.version === "string" ? body.version : void 0,
172
+ storage: typeof body.storage === "string" ? body.storage : void 0
173
+ };
174
+ } catch (err) {
175
+ return {
176
+ ok: false,
177
+ error: err instanceof Error ? err.message : String(err)
178
+ };
179
+ }
180
+ }
181
+ /**
182
+ * Получение метаданных сессии по идентификатору с автоматическим разрешением префикса.
183
+ * При сетевых сбоях или ошибках авторизации возвращает null, не выбрасывая исключений.
184
+ */
185
+ async fetchSession(sessionId) {
186
+ if (!sessionId || !sessionId.trim()) {
187
+ return null;
188
+ }
189
+ const candidates = this.getCandidateSessionIds(sessionId);
190
+ for (const candidateId of candidates) {
191
+ try {
192
+ const res = await fetch(
193
+ `${this.endpoint}/api/v1/sessions/${encodeURIComponent(candidateId)}`,
194
+ {
195
+ method: "GET",
196
+ headers: this.getHeaders()
197
+ }
198
+ );
199
+ if (res.status === 404) {
200
+ continue;
201
+ }
202
+ if (!res.ok) {
203
+ return null;
204
+ }
205
+ const data = await res.json();
206
+ const raw = data?.result ?? data?.data ?? data;
207
+ if (!raw || typeof raw !== "object") {
208
+ return null;
209
+ }
210
+ this.resolvedSessionIds.set(sessionId.trim(), candidateId);
211
+ return {
212
+ session_id: typeof raw.session_id === "string" ? raw.session_id : candidateId,
213
+ peer_id: typeof raw.peer_id === "string" ? raw.peer_id : void 0,
214
+ pending_tokens: typeof raw.pending_tokens === "number" ? raw.pending_tokens : 0,
215
+ message_count: typeof raw.message_count === "number" ? raw.message_count : void 0,
216
+ commit_count: typeof raw.commit_count === "number" ? raw.commit_count : void 0,
217
+ last_commit_at: typeof raw.last_commit_at === "string" ? raw.last_commit_at : typeof raw.last_commit === "string" ? raw.last_commit : void 0,
218
+ created_at: typeof raw.created_at === "string" ? raw.created_at : void 0,
219
+ updated_at: typeof raw.updated_at === "string" ? raw.updated_at : void 0
220
+ };
221
+ } catch {
222
+ return null;
223
+ }
224
+ }
225
+ return null;
226
+ }
227
+ /**
228
+ * Алиас для fetchSession
229
+ */
230
+ async getSession(sessionId) {
231
+ return this.fetchSession(sessionId);
232
+ }
233
+ /**
234
+ * Инициация фиксации (коммита) накопленных токенов сессии в долговременную память
235
+ */
236
+ async commitSession(sessionId, options) {
237
+ if (!sessionId || !sessionId.trim()) {
238
+ return { ok: false, error: "Missing sessionId" };
239
+ }
240
+ const candidates = this.getCandidateSessionIds(sessionId);
241
+ const bodyPayload = JSON.stringify(options ?? { keep_recent_count: 10 });
242
+ let lastError = "Session commit failed";
243
+ for (const candidateId of candidates) {
244
+ try {
245
+ const res = await fetch(
246
+ `${this.endpoint}/api/v1/sessions/${encodeURIComponent(candidateId)}/commit`,
247
+ {
248
+ method: "POST",
249
+ headers: this.getHeaders(),
250
+ body: bodyPayload
251
+ }
252
+ );
253
+ if (res.status === 404) {
254
+ lastError = `Session not found: ${candidateId}`;
255
+ continue;
256
+ }
257
+ if (!res.ok) {
258
+ const errBody = await res.json().catch(() => null);
259
+ const errorMsg = errBody?.error?.message || errBody?.message || errBody?.error || `HTTP ${res.status}: ${res.statusText}`;
260
+ return { ok: false, error: String(errorMsg) };
261
+ }
262
+ this.resolvedSessionIds.set(sessionId.trim(), candidateId);
263
+ return { ok: true };
264
+ } catch (err) {
265
+ return {
266
+ ok: false,
267
+ error: err instanceof Error ? err.message : String(err)
268
+ };
269
+ }
270
+ }
271
+ return { ok: false, error: lastError };
272
+ }
273
+ };
274
+ var defaultOpenVikingClient = new OpenVikingClient();
275
+ function checkHealth(endpoint, apiKey) {
276
+ const client = endpoint || apiKey ? new OpenVikingClient(endpoint, apiKey) : defaultOpenVikingClient;
277
+ return client.checkHealth();
278
+ }
279
+ function fetchSession(sessionId, endpoint, apiKey) {
280
+ const client = endpoint || apiKey ? new OpenVikingClient(endpoint, apiKey) : defaultOpenVikingClient;
281
+ return client.fetchSession(sessionId);
282
+ }
283
+ function getSession(sessionId, endpoint, apiKey) {
284
+ return fetchSession(sessionId, endpoint, apiKey);
285
+ }
286
+ function commitSession(sessionId, options, endpoint, apiKey) {
287
+ const client = endpoint || apiKey ? new OpenVikingClient(endpoint, apiKey) : defaultOpenVikingClient;
288
+ return client.commitSession(sessionId, options);
289
+ }
290
+
291
+ // src/client/recallParser.ts
292
+ var KNOWN_CATEGORIES = /* @__PURE__ */ new Set([
293
+ "preferences",
294
+ "entities",
295
+ "events",
296
+ "skills",
297
+ "resources",
298
+ "profile"
299
+ ]);
300
+ function inferCategory(uri) {
301
+ if (!uri || typeof uri !== "string") return void 0;
302
+ const memoriesMatch = uri.match(/\/memories\/([^/#?]+)/i);
303
+ if (memoriesMatch && memoriesMatch[1]) {
304
+ return memoriesMatch[1].toLowerCase();
305
+ }
306
+ const cleanUri = uri.replace(/^viking:\/\/?/i, "");
307
+ const segments = cleanUri.split("/").filter(Boolean);
308
+ for (const seg of segments) {
309
+ const lower = seg.toLowerCase();
310
+ if (KNOWN_CATEGORIES.has(lower)) {
311
+ return lower;
312
+ }
313
+ }
314
+ if (segments[0]?.toLowerCase() === "user" && segments.length >= 3) {
315
+ return segments[2].toLowerCase();
316
+ }
317
+ if (segments.length >= 2) {
318
+ return segments[0].toLowerCase();
319
+ }
320
+ return void 0;
321
+ }
322
+ function extractAllText(input) {
323
+ if (input == null) return "";
324
+ if (typeof input === "string") return input;
325
+ if (typeof input === "number" || typeof input === "boolean")
326
+ return String(input);
327
+ if (Array.isArray(input)) {
328
+ return input.map(extractAllText).filter(Boolean).join("\n");
329
+ }
330
+ if (typeof input === "object") {
331
+ const obj = input;
332
+ const parts = [];
333
+ if ("content" in obj && obj.content != null) {
334
+ parts.push(extractAllText(obj.content));
335
+ }
336
+ if ("text" in obj && obj.text != null) {
337
+ parts.push(extractAllText(obj.text));
338
+ }
339
+ if ("message" in obj && obj.message != null) {
340
+ parts.push(extractAllText(obj.message));
341
+ }
342
+ if (parts.length > 0) {
343
+ return parts.join("\n");
344
+ }
345
+ try {
346
+ return Object.values(obj).map(extractAllText).filter(Boolean).join("\n");
347
+ } catch {
348
+ return "";
349
+ }
350
+ }
351
+ return "";
352
+ }
353
+ function findContextBlocks(text) {
354
+ const blocks = [];
355
+ const openTagRegex = /<openviking-context\b([^>]*)>/gi;
356
+ let match;
357
+ const tagPositions = [];
358
+ while ((match = openTagRegex.exec(text)) !== null) {
359
+ tagPositions.push({
360
+ index: match.index,
361
+ endIndex: match.index + match[0].length,
362
+ attrs: match[1] || ""
363
+ });
364
+ }
365
+ if (tagPositions.length === 0) {
366
+ if (/<available-memories\b/i.test(text)) {
367
+ blocks.push({
368
+ isProfile: true,
369
+ attributes: 'source="profile"',
370
+ content: text
371
+ });
372
+ } else if (/<memory\b/i.test(text)) {
373
+ blocks.push({
374
+ isProfile: false,
375
+ attributes: "",
376
+ content: text
377
+ });
378
+ }
379
+ return blocks;
380
+ }
381
+ for (let i = 0; i < tagPositions.length; i++) {
382
+ const current = tagPositions[i];
383
+ const nextStart = i + 1 < tagPositions.length ? tagPositions[i + 1].index : text.length;
384
+ const blockText = text.slice(current.endIndex, nextStart);
385
+ const closeIndex = blockText.search(/<\/openviking-context>/i);
386
+ const content = closeIndex !== -1 ? blockText.slice(0, closeIndex) : blockText;
387
+ const isProfile = /\bsource\s*=\s*["']profile["']/i.test(current.attrs);
388
+ blocks.push({
389
+ isProfile,
390
+ attributes: current.attrs,
391
+ content
392
+ });
393
+ }
394
+ return blocks;
395
+ }
396
+ function normalizeUri(uri) {
397
+ let clean = uri.trim();
398
+ if (clean.endsWith("/") && !clean.endsWith("://")) {
399
+ clean = clean.slice(0, -1);
400
+ }
401
+ return clean;
402
+ }
403
+ function parseProfileItems(blockContent) {
404
+ const items = [];
405
+ let memContent = blockContent;
406
+ const availMatch = blockContent.match(
407
+ /<available-memories\b[^>]*>([\s\S]*?)(?:<\/available-memories>|$)/i
408
+ );
409
+ if (availMatch && availMatch[1]) {
410
+ memContent = availMatch[1];
411
+ }
412
+ const lines = memContent.split(/\r?\n/);
413
+ let currentBaseUri = null;
414
+ for (const rawLine of lines) {
415
+ const trimmed = rawLine.trim();
416
+ if (!trimmed) continue;
417
+ const baseMatch = trimmed.match(
418
+ /^[-*•]?\s*(viking:\/\/[^\s<>"'`]+?\/)\s*$/i
419
+ );
420
+ if (baseMatch) {
421
+ currentBaseUri = baseMatch[1];
422
+ continue;
423
+ }
424
+ const bulletMatch = trimmed.match(/^[-*•]\s+([^\s<>"'`].*?)$/);
425
+ if (bulletMatch && currentBaseUri && !bulletMatch[1].startsWith("viking://")) {
426
+ let relPath = bulletMatch[1].trim();
427
+ relPath = relPath.replace(/^\[([^\]]+)\](?:\([^)]*\))?/, "$1");
428
+ relPath = relPath.replace(/[.,;:)\]]+$/, "").trim();
429
+ if (relPath) {
430
+ const cleanBase = currentBaseUri.endsWith("/") ? currentBaseUri : currentBaseUri + "/";
431
+ const fullUri = normalizeUri(cleanBase + relPath.replace(/^\/+/, ""));
432
+ items.push({
433
+ uri: fullUri,
434
+ category: inferCategory(fullUri),
435
+ source: "profile"
436
+ });
437
+ continue;
438
+ }
439
+ }
440
+ const fullBulletMatch = trimmed.match(
441
+ /^[-*•]\s+(viking:\/\/[^<>"'`\r\n]+)/i
442
+ );
443
+ if (fullBulletMatch) {
444
+ let uri = fullBulletMatch[1].trim().replace(/[.,;:)\]]+$/, "");
445
+ if (uri.endsWith("/")) {
446
+ currentBaseUri = uri;
447
+ } else {
448
+ uri = normalizeUri(uri);
449
+ items.push({
450
+ uri,
451
+ category: inferCategory(uri),
452
+ source: "profile"
453
+ });
454
+ continue;
455
+ }
456
+ }
457
+ const inlineMatches = trimmed.matchAll(/viking:\/\/[^\s<>"'`]+/gi);
458
+ for (const m of inlineMatches) {
459
+ let uri = m[0].replace(/[.,;:)\]]+$/, "");
460
+ if (uri.endsWith("/")) {
461
+ currentBaseUri = uri;
462
+ } else {
463
+ uri = normalizeUri(uri);
464
+ items.push({
465
+ uri,
466
+ category: inferCategory(uri),
467
+ source: "profile"
468
+ });
469
+ }
470
+ }
471
+ }
472
+ return items;
473
+ }
474
+ function parseRecallItems(blockContent) {
475
+ const items = [];
476
+ const memoryTagRegex = /<memory\b([^>]*?)(?:\/>|>([\s\S]*?)(?:<\/memory>|(?=<memory\b)|$))/gi;
477
+ let memMatch;
478
+ while ((memMatch = memoryTagRegex.exec(blockContent)) !== null) {
479
+ const attrsString = memMatch[1] || "";
480
+ const innerContent = memMatch[2] || "";
481
+ const uriMatch = attrsString.match(
482
+ /\buri\s*=\s*(?:["'](viking:\/\/[^"']+)["']|(viking:\/\/[^\s>]+))/i
483
+ );
484
+ if (!uriMatch) continue;
485
+ const rawUri = uriMatch[1] || uriMatch[2];
486
+ const uri = normalizeUri(rawUri);
487
+ const scoreMatch = attrsString.match(
488
+ /\bscore\s*=\s*(?:["']([^"']+)["']|([0-9.]+))/i
489
+ );
490
+ let score;
491
+ const scoreStr = scoreMatch ? scoreMatch[1] || scoreMatch[2] : void 0;
492
+ if (scoreStr) {
493
+ const parsed = parseFloat(scoreStr);
494
+ if (Number.isFinite(parsed)) {
495
+ score = parsed;
496
+ }
497
+ }
498
+ const typeMatch = attrsString.match(
499
+ /\b(?:type|category)\s*=\s*["']([^"']+)["']/i
500
+ );
501
+ const explicitCategory = typeMatch ? typeMatch[1].trim() : void 0;
502
+ const category = inferCategory(uri) || explicitCategory;
503
+ let abstractText;
504
+ const abstractAttrMatch = attrsString.match(
505
+ /\babstract\s*=\s*["']([^"']+)["']/i
506
+ );
507
+ if (abstractAttrMatch) {
508
+ abstractText = abstractAttrMatch[1].trim();
509
+ } else {
510
+ const trimmedContent = innerContent.trim();
511
+ if (trimmedContent) {
512
+ abstractText = trimmedContent;
513
+ }
514
+ }
515
+ items.push({
516
+ uri,
517
+ category,
518
+ source: "recall",
519
+ score,
520
+ abstract: abstractText
521
+ });
522
+ }
523
+ return items;
524
+ }
525
+ function parseRecalledMemories(input) {
526
+ if (input == null) {
527
+ return {
528
+ recalledCount: 0,
529
+ items: [],
530
+ profileItems: [],
531
+ recallItems: []
532
+ };
533
+ }
534
+ const text = extractAllText(input);
535
+ if (!text.trim()) {
536
+ return {
537
+ recalledCount: 0,
538
+ items: [],
539
+ profileItems: [],
540
+ recallItems: []
541
+ };
542
+ }
543
+ const blocks = findContextBlocks(text);
544
+ const rawProfileItems = [];
545
+ const rawRecallItems = [];
546
+ for (const block of blocks) {
547
+ if (block.isProfile) {
548
+ rawProfileItems.push(...parseProfileItems(block.content));
549
+ } else {
550
+ rawRecallItems.push(...parseRecallItems(block.content));
551
+ }
552
+ }
553
+ const recallMap = /* @__PURE__ */ new Map();
554
+ for (const item of rawRecallItems) {
555
+ const existing = recallMap.get(item.uri);
556
+ if (!existing) {
557
+ recallMap.set(item.uri, { ...item });
558
+ } else {
559
+ if (existing.score === void 0 && item.score !== void 0) {
560
+ existing.score = item.score;
561
+ } else if (existing.score !== void 0 && item.score !== void 0) {
562
+ existing.score = Math.max(existing.score, item.score);
563
+ }
564
+ if (!existing.abstract && item.abstract) {
565
+ existing.abstract = item.abstract;
566
+ }
567
+ if (!existing.category && item.category) {
568
+ existing.category = item.category;
569
+ }
570
+ }
571
+ }
572
+ const profileMap = /* @__PURE__ */ new Map();
573
+ for (const item of rawProfileItems) {
574
+ if (!profileMap.has(item.uri)) {
575
+ profileMap.set(item.uri, { ...item });
576
+ }
577
+ }
578
+ const itemsMap = /* @__PURE__ */ new Map();
579
+ for (const [uri, item] of profileMap.entries()) {
580
+ itemsMap.set(uri, item);
581
+ }
582
+ for (const [uri, item] of recallMap.entries()) {
583
+ itemsMap.set(uri, item);
584
+ }
585
+ const items = Array.from(itemsMap.values());
586
+ const profileItems = items.filter((it) => it.source === "profile");
587
+ const recallItems = items.filter((it) => it.source === "recall");
588
+ return {
589
+ recalledCount: items.length,
590
+ items,
591
+ profileItems,
592
+ recallItems
593
+ };
594
+ }
595
+
596
+ // src/client/OpenVikingStatusPopover.tsx
597
+ var import_react = require("react");
598
+ var import_jsx_runtime = require("react/jsx-runtime");
599
+ function getProgressBarPercent(pendingTokens, threshold = COMMIT_THRESHOLD) {
600
+ if (!threshold || threshold <= 0) return 0;
601
+ const ratio = (pendingTokens || 0) / threshold;
602
+ return Math.min(100, Math.max(0, Math.round(ratio * 100)));
603
+ }
604
+ function getProgressBarColor(percent) {
605
+ if (percent >= 80) {
606
+ return "var(--dsw-status-warning, #fbbf24)";
607
+ }
608
+ return "var(--dsw-status-success, #34d399)";
609
+ }
610
+ function formatRelativeTime(isoOrTimestamp, now = Date.now()) {
611
+ if (!isoOrTimestamp) return void 0;
612
+ const date = new Date(isoOrTimestamp);
613
+ const time = date.getTime();
614
+ if (isNaN(time)) return String(isoOrTimestamp);
615
+ const diffMs = now - time;
616
+ if (diffMs < 0) return "just now";
617
+ const diffSec = Math.floor(diffMs / 1e3);
618
+ if (diffSec < 60) return "just now";
619
+ const diffMin = Math.floor(diffSec / 60);
620
+ if (diffMin < 60) return `${diffMin}m ago`;
621
+ const diffHour = Math.floor(diffMin / 60);
622
+ if (diffHour < 24) return `${diffHour}h ago`;
623
+ const diffDay = Math.floor(diffHour / 24);
624
+ return `${diffDay}d ago`;
625
+ }
626
+ function formatMemoryLeafName(uri) {
627
+ if (!uri) return "";
628
+ const clean = uri.replace(/^viking:\/\/?/i, "");
629
+ const memMatch = clean.match(/memories\/[^/]+\/(.+)$/i);
630
+ if (memMatch && memMatch[1]) {
631
+ return memMatch[1];
632
+ }
633
+ const segments = clean.split("/").filter(Boolean);
634
+ if (segments.length >= 2) {
635
+ return segments.slice(-2).join("/");
636
+ }
637
+ return segments[0] || clean;
638
+ }
639
+ function formatEndpoint(endpoint) {
640
+ if (!endpoint || !endpoint.trim()) {
641
+ return "127.0.0.1:1933";
642
+ }
643
+ return endpoint.trim().replace(/^https?:\/\//, "");
644
+ }
645
+ function truncateSessionId(id, maxLen = 16) {
646
+ if (!id) return "";
647
+ if (id.length <= maxLen) return id;
648
+ return `${id.slice(0, maxLen)}...`;
649
+ }
650
+ function getCategoryBadgeStyle(category) {
651
+ switch (category?.toLowerCase()) {
652
+ case "preferences":
653
+ return {
654
+ backgroundColor: "rgba(168, 85, 247, 0.15)",
655
+ color: "var(--dsw-status-purple, #c084fc)"
656
+ };
657
+ case "entities":
658
+ return {
659
+ backgroundColor: "rgba(59, 130, 246, 0.15)",
660
+ color: "var(--dsw-status-info, #60a5fa)"
661
+ };
662
+ case "skills":
663
+ return {
664
+ backgroundColor: "rgba(236, 72, 153, 0.15)",
665
+ color: "var(--dsw-status-pink, #f472b6)"
666
+ };
667
+ case "events":
668
+ return {
669
+ backgroundColor: "rgba(245, 158, 11, 0.15)",
670
+ color: "var(--dsw-status-warning, #fbbf24)"
671
+ };
672
+ case "resources":
673
+ return {
674
+ backgroundColor: "rgba(20, 184, 166, 0.15)",
675
+ color: "var(--dsw-status-teal, #2dd4bf)"
676
+ };
677
+ default:
678
+ return {
679
+ backgroundColor: "rgba(148, 163, 184, 0.15)",
680
+ color: "var(--dsw-text-muted, #94a3b8)"
681
+ };
682
+ }
683
+ }
684
+ function handleEscapeKey(event, onClose) {
685
+ if (event.key === "Escape") {
686
+ onClose?.();
687
+ return true;
688
+ }
689
+ return false;
690
+ }
691
+ function OpenVikingStatusPopover({
692
+ sessionId,
693
+ health,
694
+ sessionData,
695
+ recalledResult,
696
+ endpoint,
697
+ isCommitting = false,
698
+ commitError = null,
699
+ onCommitNow,
700
+ onClose,
701
+ className,
702
+ style
703
+ }) {
704
+ const [copied, setCopied] = (0, import_react.useState)(false);
705
+ const copyTimeoutRef = (0, import_react.useRef)(null);
706
+ (0, import_react.useEffect)(() => {
707
+ return () => {
708
+ if (copyTimeoutRef.current) {
709
+ clearTimeout(copyTimeoutRef.current);
710
+ }
711
+ };
712
+ }, []);
713
+ const isOnline = health?.ok === true;
714
+ const statusColor = isOnline ? "var(--dsw-status-success, #34d399)" : "var(--dsw-status-error, #f87171)";
715
+ const displaySessionId = sessionData?.session_id || sessionId || "";
716
+ const pendingTokens = sessionData?.pending_tokens ?? 0;
717
+ const progressPercent = getProgressBarPercent(pendingTokens);
718
+ const progressBarColor = getProgressBarColor(progressPercent);
719
+ const memoryItems = recalledResult?.items || [];
720
+ const recalledCount = recalledResult?.recalledCount ?? memoryItems.length;
721
+ const handleCopySessionId = (0, import_react.useCallback)(() => {
722
+ if (!displaySessionId) return;
723
+ if (typeof navigator !== "undefined" && navigator.clipboard) {
724
+ navigator.clipboard.writeText(displaySessionId).catch(() => {
725
+ });
726
+ setCopied(true);
727
+ if (copyTimeoutRef.current) {
728
+ clearTimeout(copyTimeoutRef.current);
729
+ }
730
+ copyTimeoutRef.current = setTimeout(() => {
731
+ setCopied(false);
732
+ copyTimeoutRef.current = null;
733
+ }, 1500);
734
+ }
735
+ }, [displaySessionId]);
736
+ const isCommitDisabled = isCommitting || !isOnline || pendingTokens === 0;
737
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
738
+ "div",
739
+ {
740
+ role: "dialog",
741
+ "aria-label": "OpenViking Memory Details",
742
+ className,
743
+ style: {
744
+ position: "absolute",
745
+ bottom: "calc(100% + 8px)",
746
+ right: 0,
747
+ width: "320px",
748
+ backgroundColor: "var(--dsw-surface-overlay, #1e293b)",
749
+ border: "1px solid var(--dsw-border-default, #334155)",
750
+ borderRadius: "8px",
751
+ padding: "12px",
752
+ boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.5)",
753
+ zIndex: 1e3,
754
+ fontSize: "12px",
755
+ color: "var(--dsw-text-default, #f1f5f9)",
756
+ fontFamily: "var(--dsw-font-sans, system-ui, sans-serif)",
757
+ boxSizing: "border-box",
758
+ ...style
759
+ },
760
+ children: [
761
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes ov-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }` }),
762
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
763
+ "div",
764
+ {
765
+ style: {
766
+ display: "flex",
767
+ justifyContent: "space-between",
768
+ alignItems: "flex-start",
769
+ marginBottom: "10px",
770
+ paddingBottom: "8px",
771
+ borderBottom: "1px solid var(--dsw-border-subtle, rgba(255, 255, 255, 0.08))"
772
+ },
773
+ children: [
774
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
775
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
776
+ "div",
777
+ {
778
+ style: {
779
+ fontWeight: 600,
780
+ fontSize: "13px",
781
+ color: "var(--dsw-text-default, #f1f5f9)"
782
+ },
783
+ children: "OpenViking Memory"
784
+ }
785
+ ),
786
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
787
+ "div",
788
+ {
789
+ "data-testid": "endpoint-label",
790
+ style: {
791
+ fontSize: "10px",
792
+ color: "var(--dsw-text-muted, #94a3b8)",
793
+ fontFamily: "var(--dsw-font-mono, monospace)",
794
+ marginTop: "1px"
795
+ },
796
+ children: formatEndpoint(endpoint)
797
+ }
798
+ )
799
+ ] }),
800
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
801
+ "div",
802
+ {
803
+ "data-testid": "status-badge",
804
+ style: {
805
+ display: "inline-flex",
806
+ alignItems: "center",
807
+ gap: "5px",
808
+ fontSize: "10px",
809
+ padding: "2px 7px",
810
+ borderRadius: "4px",
811
+ backgroundColor: isOnline ? "rgba(52, 211, 153, 0.15)" : "rgba(248, 113, 113, 0.15)",
812
+ color: statusColor,
813
+ fontWeight: 600
814
+ },
815
+ children: [
816
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
817
+ "span",
818
+ {
819
+ "data-testid": "status-badge-dot",
820
+ style: {
821
+ width: "6px",
822
+ height: "6px",
823
+ borderRadius: "50%",
824
+ backgroundColor: statusColor,
825
+ flexShrink: 0
826
+ }
827
+ }
828
+ ),
829
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: isOnline ? health?.version ? `ONLINE v${health.version}` : "ONLINE" : "OFFLINE" })
830
+ ]
831
+ }
832
+ )
833
+ ]
834
+ }
835
+ ),
836
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
837
+ "div",
838
+ {
839
+ style: {
840
+ display: "flex",
841
+ flexDirection: "column",
842
+ gap: "6px",
843
+ marginBottom: "10px"
844
+ },
845
+ children: [
846
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
847
+ "div",
848
+ {
849
+ style: {
850
+ display: "flex",
851
+ justifyContent: "space-between",
852
+ alignItems: "center"
853
+ },
854
+ children: [
855
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-text-muted, #94a3b8)" }, children: "Session ID:" }),
856
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
857
+ "div",
858
+ {
859
+ style: {
860
+ display: "flex",
861
+ alignItems: "center",
862
+ gap: "4px"
863
+ },
864
+ children: [
865
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
866
+ "span",
867
+ {
868
+ "data-testid": "session-id-value",
869
+ role: "button",
870
+ tabIndex: 0,
871
+ onKeyDown: (e) => {
872
+ if (e.key === "Enter" || e.key === " ") {
873
+ e.preventDefault();
874
+ handleCopySessionId();
875
+ }
876
+ },
877
+ "aria-label": "Click to copy Session ID",
878
+ style: {
879
+ fontFamily: "var(--dsw-font-mono, monospace)",
880
+ cursor: "pointer"
881
+ },
882
+ title: displaySessionId,
883
+ onClick: handleCopySessionId,
884
+ children: truncateSessionId(displaySessionId)
885
+ }
886
+ ),
887
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
888
+ "button",
889
+ {
890
+ type: "button",
891
+ "data-testid": "copy-session-btn",
892
+ onClick: handleCopySessionId,
893
+ title: copied ? "Copied!" : "Copy Session ID",
894
+ "aria-label": copied ? "Copied!" : "Copy Session ID",
895
+ style: {
896
+ background: "none",
897
+ border: "none",
898
+ cursor: "pointer",
899
+ padding: "2px 4px",
900
+ fontSize: "10px",
901
+ color: copied ? "var(--dsw-status-success, #34d399)" : "var(--dsw-text-muted, #94a3b8)",
902
+ borderRadius: "3px"
903
+ },
904
+ children: copied ? "\u2713" : "\u{1F4CB}"
905
+ }
906
+ )
907
+ ]
908
+ }
909
+ )
910
+ ]
911
+ }
912
+ ),
913
+ sessionData?.peer_id && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
914
+ "div",
915
+ {
916
+ style: {
917
+ display: "flex",
918
+ justifyContent: "space-between",
919
+ alignItems: "center"
920
+ },
921
+ children: [
922
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-text-muted, #94a3b8)" }, children: "Peer ID:" }),
923
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
924
+ "span",
925
+ {
926
+ "data-testid": "peer-id-value",
927
+ style: {
928
+ maxWidth: "180px",
929
+ overflow: "hidden",
930
+ textOverflow: "ellipsis",
931
+ whiteSpace: "nowrap",
932
+ fontFamily: "var(--dsw-font-mono, monospace)"
933
+ },
934
+ title: sessionData.peer_id,
935
+ children: sessionData.peer_id
936
+ }
937
+ )
938
+ ]
939
+ }
940
+ ),
941
+ sessionData?.last_commit_at && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
942
+ "div",
943
+ {
944
+ style: {
945
+ display: "flex",
946
+ justifyContent: "space-between",
947
+ alignItems: "center"
948
+ },
949
+ children: [
950
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-text-muted, #94a3b8)" }, children: "Last Commit:" }),
951
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
952
+ "span",
953
+ {
954
+ "data-testid": "last-commit-value",
955
+ title: sessionData.last_commit_at,
956
+ style: { color: "var(--dsw-text-default, #e2e8f0)" },
957
+ children: formatRelativeTime(sessionData.last_commit_at) || sessionData.last_commit_at
958
+ }
959
+ )
960
+ ]
961
+ }
962
+ )
963
+ ]
964
+ }
965
+ ),
966
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: "12px" }, children: [
967
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
968
+ "div",
969
+ {
970
+ style: {
971
+ display: "flex",
972
+ justifyContent: "space-between",
973
+ marginBottom: "4px"
974
+ },
975
+ children: [
976
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-text-muted, #94a3b8)" }, children: "Pending Tokens:" }),
977
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "data-testid": "pending-tokens-label", style: { fontWeight: 500 }, children: `${pendingTokens.toLocaleString()} / ${COMMIT_THRESHOLD.toLocaleString()}` })
978
+ ]
979
+ }
980
+ ),
981
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
982
+ "div",
983
+ {
984
+ "data-testid": "progress-bar-track",
985
+ style: {
986
+ width: "100%",
987
+ height: "6px",
988
+ borderRadius: "3px",
989
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
990
+ overflow: "hidden"
991
+ },
992
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
993
+ "div",
994
+ {
995
+ "data-testid": "progress-bar-fill",
996
+ style: {
997
+ width: `${progressPercent}%`,
998
+ height: "100%",
999
+ backgroundColor: progressBarColor,
1000
+ transition: "width 0.3s ease, background-color 0.3s ease"
1001
+ }
1002
+ }
1003
+ )
1004
+ }
1005
+ )
1006
+ ] }),
1007
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: "12px" }, children: [
1008
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1009
+ "div",
1010
+ {
1011
+ style: {
1012
+ display: "flex",
1013
+ justifyContent: "space-between",
1014
+ alignItems: "center",
1015
+ marginBottom: "4px"
1016
+ },
1017
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: 600, fontSize: "11px" }, children: `Recalled Memories (${recalledCount})` })
1018
+ }
1019
+ ),
1020
+ memoryItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1021
+ "div",
1022
+ {
1023
+ "data-testid": "empty-memories-message",
1024
+ style: {
1025
+ padding: "8px 0",
1026
+ color: "var(--dsw-text-muted, #94a3b8)",
1027
+ fontStyle: "italic",
1028
+ fontSize: "11px",
1029
+ textAlign: "center"
1030
+ },
1031
+ children: "No memories recalled in this session"
1032
+ }
1033
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1034
+ "div",
1035
+ {
1036
+ "data-testid": "recalled-memories-list",
1037
+ style: {
1038
+ maxHeight: "140px",
1039
+ overflowY: "auto",
1040
+ display: "flex",
1041
+ flexDirection: "column",
1042
+ gap: "4px",
1043
+ paddingRight: "2px"
1044
+ },
1045
+ children: memoryItems.map((item, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1046
+ "div",
1047
+ {
1048
+ "data-testid": "recalled-memory-item",
1049
+ title: item.uri,
1050
+ style: {
1051
+ display: "flex",
1052
+ alignItems: "center",
1053
+ gap: "6px",
1054
+ padding: "4px 6px",
1055
+ borderRadius: "4px",
1056
+ backgroundColor: "rgba(255, 255, 255, 0.04)",
1057
+ border: "1px solid var(--dsw-border-subtle, rgba(255, 255, 255, 0.05))",
1058
+ fontSize: "11px"
1059
+ },
1060
+ children: [
1061
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1062
+ "span",
1063
+ {
1064
+ "data-testid": "memory-category-badge",
1065
+ style: {
1066
+ fontSize: "9px",
1067
+ fontWeight: 600,
1068
+ padding: "1px 4px",
1069
+ borderRadius: "3px",
1070
+ textTransform: "uppercase",
1071
+ flexShrink: 0,
1072
+ ...getCategoryBadgeStyle(item.category)
1073
+ },
1074
+ children: item.category || "memory"
1075
+ }
1076
+ ),
1077
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1078
+ "span",
1079
+ {
1080
+ "data-testid": "memory-source-badge",
1081
+ style: {
1082
+ fontSize: "9px",
1083
+ fontWeight: 600,
1084
+ padding: "1px 4px",
1085
+ borderRadius: "3px",
1086
+ textTransform: "uppercase",
1087
+ flexShrink: 0,
1088
+ ...item.source === "profile" ? {
1089
+ backgroundColor: "rgba(99, 102, 241, 0.15)",
1090
+ color: "#818cf8"
1091
+ } : {
1092
+ backgroundColor: "rgba(16, 185, 129, 0.15)",
1093
+ color: "var(--dsw-status-success, #34d399)"
1094
+ }
1095
+ },
1096
+ children: item.source
1097
+ }
1098
+ ),
1099
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1100
+ "span",
1101
+ {
1102
+ "data-testid": "memory-leaf-name",
1103
+ style: {
1104
+ flex: 1,
1105
+ overflow: "hidden",
1106
+ textOverflow: "ellipsis",
1107
+ whiteSpace: "nowrap",
1108
+ fontFamily: "var(--dsw-font-mono, monospace)",
1109
+ color: "var(--dsw-text-default, #f1f5f9)"
1110
+ },
1111
+ children: formatMemoryLeafName(item.uri)
1112
+ }
1113
+ )
1114
+ ]
1115
+ },
1116
+ `${item.uri}-${idx}`
1117
+ ))
1118
+ }
1119
+ )
1120
+ ] }),
1121
+ commitError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1122
+ "div",
1123
+ {
1124
+ "data-testid": "commit-error-message",
1125
+ style: {
1126
+ marginBottom: "8px",
1127
+ padding: "6px 8px",
1128
+ borderRadius: "4px",
1129
+ backgroundColor: "rgba(248, 113, 113, 0.1)",
1130
+ border: "1px solid var(--dsw-status-error, #f87171)",
1131
+ color: "var(--dsw-status-error, #f87171)",
1132
+ fontSize: "11px",
1133
+ wordBreak: "break-word"
1134
+ },
1135
+ children: commitError
1136
+ }
1137
+ ),
1138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1139
+ "button",
1140
+ {
1141
+ type: "button",
1142
+ "data-testid": "commit-now-btn",
1143
+ onClick: () => onCommitNow?.(),
1144
+ disabled: isCommitDisabled,
1145
+ style: {
1146
+ width: "100%",
1147
+ padding: "7px 0",
1148
+ borderRadius: "6px",
1149
+ border: "1px solid var(--dsw-border-default, #475569)",
1150
+ backgroundColor: isCommitting ? "var(--dsw-surface-active, #334155)" : isCommitDisabled ? "rgba(255, 255, 255, 0.03)" : "var(--dsw-surface-base, #1e293b)",
1151
+ color: isCommitDisabled ? "var(--dsw-text-muted, #64748b)" : "var(--dsw-text-default, #f8fafc)",
1152
+ cursor: isCommitDisabled ? "not-allowed" : "pointer",
1153
+ fontWeight: 600,
1154
+ fontSize: "12px",
1155
+ transition: "all 0.15s ease",
1156
+ display: "flex",
1157
+ alignItems: "center",
1158
+ justifyContent: "center",
1159
+ gap: "6px"
1160
+ },
1161
+ children: isCommitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1163
+ "span",
1164
+ {
1165
+ style: {
1166
+ display: "inline-block",
1167
+ width: "10px",
1168
+ height: "10px",
1169
+ borderRadius: "50%",
1170
+ border: "2px solid var(--dsw-status-warning, #fbbf24)",
1171
+ borderTopColor: "transparent",
1172
+ animation: "ov-spin 1s linear infinite"
1173
+ }
1174
+ }
1175
+ ),
1176
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Committing..." })
1177
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Commit To Memory Now" })
1178
+ }
1179
+ )
1180
+ ]
1181
+ }
1182
+ );
1183
+ }
1184
+
1185
+ // src/client/OpenVikingStatusChip.tsx
1186
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1187
+ var COMMIT_THRESHOLD = 2e4;
1188
+ function formatPendingTokens(pendingTokens) {
1189
+ const k = Math.round((pendingTokens || 0) / 1e3);
1190
+ return `${k}k pend`;
1191
+ }
1192
+ function formatStatusLabel(isOnline, recalledCount, pendingTokens) {
1193
+ if (!isOnline) {
1194
+ return "OV offline";
1195
+ }
1196
+ return `OV: ${recalledCount} rec \xB7 ${formatPendingTokens(pendingTokens)}`;
1197
+ }
1198
+ function formatTooltipTitle({
1199
+ isOnline,
1200
+ isCommitting = false,
1201
+ recalledCount,
1202
+ pendingTokens
1203
+ }) {
1204
+ if (!isOnline) {
1205
+ return "OpenViking: Offline";
1206
+ }
1207
+ const countLabel = `${recalledCount} recalled`;
1208
+ const tokenLabel = `${(pendingTokens || 0).toLocaleString()} pending tokens`;
1209
+ if (isCommitting) {
1210
+ return `OpenViking: Committing... (${countLabel}, ${tokenLabel})`;
1211
+ }
1212
+ return `OpenViking: Online (${countLabel}, ${tokenLabel})`;
1213
+ }
1214
+ function getStatusIndicatorColor(isOnline, isCommitting = false) {
1215
+ if (!isOnline) {
1216
+ return "var(--dsw-status-error, #f87171)";
1217
+ }
1218
+ if (isCommitting) {
1219
+ return "var(--dsw-status-warning, #fbbf24)";
1220
+ }
1221
+ return "var(--dsw-status-success, #34d399)";
1222
+ }
1223
+ function getStatusGlow(isOnline, isCommitting = false) {
1224
+ if (isOnline && !isCommitting) {
1225
+ return "0 0 6px var(--dsw-status-success, #34d399)";
1226
+ }
1227
+ return "none";
1228
+ }
1229
+ function getFallbackSessionMessages(sessionId) {
1230
+ if (!sessionId || typeof window === "undefined") {
1231
+ return void 0;
1232
+ }
1233
+ const win = window;
1234
+ if (win.__DSH_STORE__?.getState) {
1235
+ const state = win.__DSH_STORE__.getState();
1236
+ return state?.conversations?.[sessionId]?.messages || state?.sessions?.[sessionId]?.messages;
1237
+ }
1238
+ if (win.__DSH_SESSION_MESSAGES__?.[sessionId]) {
1239
+ return win.__DSH_SESSION_MESSAGES__[sessionId];
1240
+ }
1241
+ return void 0;
1242
+ }
1243
+ function OpenVikingStatusChip({
1244
+ sessionId,
1245
+ messages,
1246
+ contextText,
1247
+ client,
1248
+ onCommit,
1249
+ className,
1250
+ initialHealth,
1251
+ initialSessionData,
1252
+ initialOpen = false
1253
+ }) {
1254
+ const [health, setHealth] = (0, import_react2.useState)(
1255
+ initialHealth ?? null
1256
+ );
1257
+ const [sessionData, setSessionData] = (0, import_react2.useState)(
1258
+ initialSessionData ?? null
1259
+ );
1260
+ const [isOpen, setIsOpen] = (0, import_react2.useState)(initialOpen);
1261
+ const [isCommitting, setIsCommitting] = (0, import_react2.useState)(false);
1262
+ const [commitError, setCommitError] = (0, import_react2.useState)(null);
1263
+ const popoverRef = (0, import_react2.useRef)(null);
1264
+ const apiClient = client ?? defaultOpenVikingClient;
1265
+ const fallbackMessages = (0, import_react2.useMemo)(() => {
1266
+ if (messages || contextText) return void 0;
1267
+ return getFallbackSessionMessages(sessionId);
1268
+ }, [sessionId, messages, contextText]);
1269
+ const inputForParser = (0, import_react2.useMemo)(() => {
1270
+ if (contextText && messages) {
1271
+ return [contextText, ...messages];
1272
+ }
1273
+ return contextText ?? messages ?? fallbackMessages;
1274
+ }, [contextText, messages, fallbackMessages]);
1275
+ const recalledResult = (0, import_react2.useMemo)(
1276
+ () => parseRecalledMemories(inputForParser),
1277
+ [inputForParser]
1278
+ );
1279
+ const fetchStatus = (0, import_react2.useCallback)(async () => {
1280
+ try {
1281
+ const healthRes = await apiClient.checkHealth();
1282
+ setHealth(healthRes);
1283
+ if (!healthRes.ok) {
1284
+ return;
1285
+ }
1286
+ const session = await apiClient.fetchSession(sessionId);
1287
+ setSessionData(session);
1288
+ } catch {
1289
+ setHealth({ ok: false });
1290
+ }
1291
+ }, [sessionId, apiClient]);
1292
+ (0, import_react2.useEffect)(() => {
1293
+ fetchStatus();
1294
+ const timer = setInterval(fetchStatus, 15e3);
1295
+ return () => clearInterval(timer);
1296
+ }, [fetchStatus]);
1297
+ (0, import_react2.useEffect)(() => {
1298
+ function handleClickOutside(event) {
1299
+ if (popoverRef.current && !popoverRef.current.contains(event.target)) {
1300
+ setIsOpen(false);
1301
+ }
1302
+ }
1303
+ function handleKeyDown(event) {
1304
+ if (event.key === "Escape") {
1305
+ setIsOpen(false);
1306
+ }
1307
+ }
1308
+ if (isOpen) {
1309
+ document.addEventListener("mousedown", handleClickOutside);
1310
+ document.addEventListener("keydown", handleKeyDown);
1311
+ }
1312
+ return () => {
1313
+ document.removeEventListener("mousedown", handleClickOutside);
1314
+ document.removeEventListener("keydown", handleKeyDown);
1315
+ };
1316
+ }, [isOpen]);
1317
+ const handleCommitNow = async () => {
1318
+ if (isCommitting) return;
1319
+ setIsCommitting(true);
1320
+ setCommitError(null);
1321
+ try {
1322
+ const res = await apiClient.commitSession(sessionId, {
1323
+ keep_recent_count: 10
1324
+ });
1325
+ if (res.ok) {
1326
+ await fetchStatus();
1327
+ onCommit?.();
1328
+ } else {
1329
+ setCommitError(res.error || "Commit failed");
1330
+ }
1331
+ } catch (e) {
1332
+ const msg = e instanceof Error ? e.message : String(e);
1333
+ setCommitError(msg || "Failed to commit session");
1334
+ console.error("[dsh-openviking-status] Failed to commit session:", e);
1335
+ } finally {
1336
+ setIsCommitting(false);
1337
+ }
1338
+ };
1339
+ const isOnline = health?.ok === true;
1340
+ const pendingTokens = sessionData?.pending_tokens ?? 0;
1341
+ const pendingTokensK = Math.round(pendingTokens / 1e3);
1342
+ const recalledCount = recalledResult.recalledCount;
1343
+ const statusColor = getStatusIndicatorColor(isOnline, isCommitting);
1344
+ const statusGlow = getStatusGlow(isOnline, isCommitting);
1345
+ const tooltipTitle = formatTooltipTitle({
1346
+ isOnline,
1347
+ isCommitting,
1348
+ recalledCount,
1349
+ pendingTokens
1350
+ });
1351
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1352
+ "div",
1353
+ {
1354
+ className,
1355
+ style: {
1356
+ position: "relative",
1357
+ display: "inline-flex",
1358
+ alignItems: "center"
1359
+ },
1360
+ ref: popoverRef,
1361
+ children: [
1362
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1363
+ "button",
1364
+ {
1365
+ type: "button",
1366
+ onClick: () => setIsOpen(!isOpen),
1367
+ "aria-expanded": isOpen,
1368
+ "aria-haspopup": "dialog",
1369
+ style: {
1370
+ display: "inline-flex",
1371
+ alignItems: "center",
1372
+ gap: "6px",
1373
+ height: "26px",
1374
+ padding: "0 8px",
1375
+ fontSize: "12px",
1376
+ fontFamily: "var(--dsw-font-mono, monospace)",
1377
+ borderRadius: "6px",
1378
+ background: "var(--dsw-surface-base, rgba(255, 255, 255, 0.05))",
1379
+ border: "1px solid var(--dsw-border-subtle, rgba(255, 255, 255, 0.1))",
1380
+ color: "var(--dsw-text-muted, #94a3b8)",
1381
+ cursor: "pointer",
1382
+ transition: "all 0.15s ease",
1383
+ userSelect: "none"
1384
+ },
1385
+ title: tooltipTitle,
1386
+ "aria-label": tooltipTitle,
1387
+ children: [
1388
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1389
+ "span",
1390
+ {
1391
+ "data-testid": "status-dot",
1392
+ style: {
1393
+ width: "7px",
1394
+ height: "7px",
1395
+ borderRadius: "50%",
1396
+ backgroundColor: statusColor,
1397
+ boxShadow: statusGlow,
1398
+ flexShrink: 0
1399
+ }
1400
+ }
1401
+ ),
1402
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1403
+ "span",
1404
+ {
1405
+ style: { fontWeight: 600, color: "var(--dsw-text-default, #e2e8f0)" },
1406
+ children: isOnline ? "OV:" : "OV"
1407
+ }
1408
+ ),
1409
+ " ",
1410
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: isOnline ? `${recalledCount} rec \xB7 ${pendingTokensK}k pend` : "offline" })
1411
+ ]
1412
+ }
1413
+ ),
1414
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1415
+ OpenVikingStatusPopover,
1416
+ {
1417
+ sessionId,
1418
+ health,
1419
+ sessionData,
1420
+ recalledResult,
1421
+ endpoint: apiClient.endpoint,
1422
+ isCommitting,
1423
+ commitError,
1424
+ onCommitNow: handleCommitNow,
1425
+ onClose: () => setIsOpen(false)
1426
+ }
1427
+ )
1428
+ ]
1429
+ }
1430
+ );
1431
+ }
1432
+
1433
+ // src/client/index.tsx
1434
+ var name = "@dipertq/dsh-openviking-status";
1435
+ var inject = ["slots"];
1436
+ function apply(ctx) {
1437
+ ctx.effect(
1438
+ () => ctx.slots.inject(
1439
+ "conversation.input.right",
1440
+ () => ctx.slots.register(
1441
+ {
1442
+ name: "conversation.input.right",
1443
+ id: "openviking-status",
1444
+ order: 50,
1445
+ label: "OpenViking"
1446
+ },
1447
+ OpenVikingStatusChip
1448
+ )
1449
+ ),
1450
+ "openviking-status: composer chip"
1451
+ );
1452
+ }
1453
+ return module.exports;
1454
+ },
1455
+ });
1456
+ //# sourceMappingURL=client.cjs.map