kiro-memory 1.7.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -7
- package/package.json +3 -5
- package/plugin/dist/cli/contextkit.js +177 -65
- package/plugin/dist/hooks/agentSpawn.js +177 -65
- package/plugin/dist/hooks/kiro-hooks.js +177 -65
- package/plugin/dist/hooks/postToolUse.js +415 -106
- package/plugin/dist/hooks/stop.js +213 -74
- package/plugin/dist/hooks/userPromptSubmit.js +177 -65
- package/plugin/dist/index.js +177 -65
- package/plugin/dist/sdk/index.js +177 -65
- package/plugin/dist/servers/mcp-server.js +28 -0
- package/plugin/dist/services/search/HybridSearch.js +66 -40
- package/plugin/dist/services/search/index.js +66 -40
- package/plugin/dist/services/sqlite/Database.js +23 -0
- package/plugin/dist/services/sqlite/Observations.js +48 -36
- package/plugin/dist/services/sqlite/Search.js +17 -4
- package/plugin/dist/services/sqlite/index.js +88 -40
- package/plugin/dist/viewer.html +5 -0
- package/plugin/dist/viewer.js +529 -250
- package/plugin/dist/worker-service.js +263 -44
package/plugin/dist/viewer.js
CHANGED
|
@@ -1083,7 +1083,7 @@ var require_react_development = __commonJS({
|
|
|
1083
1083
|
}
|
|
1084
1084
|
return dispatcher.useContext(Context);
|
|
1085
1085
|
}
|
|
1086
|
-
function
|
|
1086
|
+
function useState11(initialState) {
|
|
1087
1087
|
var dispatcher = resolveDispatcher();
|
|
1088
1088
|
return dispatcher.useState(initialState);
|
|
1089
1089
|
}
|
|
@@ -1095,7 +1095,7 @@ var require_react_development = __commonJS({
|
|
|
1095
1095
|
var dispatcher = resolveDispatcher();
|
|
1096
1096
|
return dispatcher.useRef(initialValue);
|
|
1097
1097
|
}
|
|
1098
|
-
function
|
|
1098
|
+
function useEffect10(create, deps) {
|
|
1099
1099
|
var dispatcher = resolveDispatcher();
|
|
1100
1100
|
return dispatcher.useEffect(create, deps);
|
|
1101
1101
|
}
|
|
@@ -1107,11 +1107,11 @@ var require_react_development = __commonJS({
|
|
|
1107
1107
|
var dispatcher = resolveDispatcher();
|
|
1108
1108
|
return dispatcher.useLayoutEffect(create, deps);
|
|
1109
1109
|
}
|
|
1110
|
-
function
|
|
1110
|
+
function useCallback6(callback, deps) {
|
|
1111
1111
|
var dispatcher = resolveDispatcher();
|
|
1112
1112
|
return dispatcher.useCallback(callback, deps);
|
|
1113
1113
|
}
|
|
1114
|
-
function
|
|
1114
|
+
function useMemo3(create, deps) {
|
|
1115
1115
|
var dispatcher = resolveDispatcher();
|
|
1116
1116
|
return dispatcher.useMemo(create, deps);
|
|
1117
1117
|
}
|
|
@@ -1874,19 +1874,19 @@ var require_react_development = __commonJS({
|
|
|
1874
1874
|
exports.memo = memo;
|
|
1875
1875
|
exports.startTransition = startTransition;
|
|
1876
1876
|
exports.unstable_act = act;
|
|
1877
|
-
exports.useCallback =
|
|
1877
|
+
exports.useCallback = useCallback6;
|
|
1878
1878
|
exports.useContext = useContext;
|
|
1879
1879
|
exports.useDebugValue = useDebugValue;
|
|
1880
1880
|
exports.useDeferredValue = useDeferredValue;
|
|
1881
|
-
exports.useEffect =
|
|
1881
|
+
exports.useEffect = useEffect10;
|
|
1882
1882
|
exports.useId = useId;
|
|
1883
1883
|
exports.useImperativeHandle = useImperativeHandle;
|
|
1884
1884
|
exports.useInsertionEffect = useInsertionEffect;
|
|
1885
1885
|
exports.useLayoutEffect = useLayoutEffect;
|
|
1886
|
-
exports.useMemo =
|
|
1886
|
+
exports.useMemo = useMemo3;
|
|
1887
1887
|
exports.useReducer = useReducer;
|
|
1888
1888
|
exports.useRef = useRef5;
|
|
1889
|
-
exports.useState =
|
|
1889
|
+
exports.useState = useState11;
|
|
1890
1890
|
exports.useSyncExternalStore = useSyncExternalStore;
|
|
1891
1891
|
exports.useTransition = useTransition;
|
|
1892
1892
|
exports.version = ReactVersion;
|
|
@@ -2382,9 +2382,9 @@ var require_react_dom_development = __commonJS({
|
|
|
2382
2382
|
if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === "function") {
|
|
2383
2383
|
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
|
|
2384
2384
|
}
|
|
2385
|
-
var
|
|
2385
|
+
var React9 = require_react();
|
|
2386
2386
|
var Scheduler = require_scheduler();
|
|
2387
|
-
var ReactSharedInternals =
|
|
2387
|
+
var ReactSharedInternals = React9.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
2388
2388
|
var suppressWarning = false;
|
|
2389
2389
|
function setSuppressWarning(newSuppressWarning) {
|
|
2390
2390
|
{
|
|
@@ -3991,7 +3991,7 @@ var require_react_dom_development = __commonJS({
|
|
|
3991
3991
|
{
|
|
3992
3992
|
if (props.value == null) {
|
|
3993
3993
|
if (typeof props.children === "object" && props.children !== null) {
|
|
3994
|
-
|
|
3994
|
+
React9.Children.forEach(props.children, function(child) {
|
|
3995
3995
|
if (child == null) {
|
|
3996
3996
|
return;
|
|
3997
3997
|
}
|
|
@@ -23581,11 +23581,11 @@ var require_client = __commonJS({
|
|
|
23581
23581
|
});
|
|
23582
23582
|
|
|
23583
23583
|
// src/ui/viewer/index.tsx
|
|
23584
|
-
var
|
|
23584
|
+
var import_react12 = __toESM(require_react(), 1);
|
|
23585
23585
|
var import_client = __toESM(require_client(), 1);
|
|
23586
23586
|
|
|
23587
23587
|
// src/ui/viewer/App.tsx
|
|
23588
|
-
var
|
|
23588
|
+
var import_react11 = __toESM(require_react(), 1);
|
|
23589
23589
|
|
|
23590
23590
|
// src/ui/viewer/components/Header.tsx
|
|
23591
23591
|
var import_react2 = __toESM(require_react(), 1);
|
|
@@ -23609,9 +23609,9 @@ function getTypeBadgeClasses(type) {
|
|
|
23609
23609
|
};
|
|
23610
23610
|
return map[type] || { bg: "bg-zinc-500/10 dark:bg-zinc-500/10", text: "text-zinc-600 dark:text-zinc-400", dot: "bg-zinc-500" };
|
|
23611
23611
|
}
|
|
23612
|
-
function timeAgo(
|
|
23613
|
-
const
|
|
23614
|
-
const diff = Math.max(0, now -
|
|
23612
|
+
function timeAgo(epoch) {
|
|
23613
|
+
const epochMs = epoch > 1e12 ? epoch : epoch * 1e3;
|
|
23614
|
+
const diff = Math.max(0, (Date.now() - epochMs) / 1e3);
|
|
23615
23615
|
if (diff < 60) return "just now";
|
|
23616
23616
|
if (diff < 3600) {
|
|
23617
23617
|
const m = Math.floor(diff / 60);
|
|
@@ -23630,14 +23630,31 @@ function timeAgo(epochSeconds) {
|
|
|
23630
23630
|
const w = Math.floor(diff / 604800);
|
|
23631
23631
|
return `${w}w ago`;
|
|
23632
23632
|
}
|
|
23633
|
-
const date = new Date(
|
|
23633
|
+
const date = new Date(epochMs);
|
|
23634
23634
|
return date.toLocaleDateString("en-US", { day: "numeric", month: "short" });
|
|
23635
23635
|
}
|
|
23636
|
+
function formatTokenCount(tokens) {
|
|
23637
|
+
if (tokens >= 1e6) return `${(tokens / 1e6).toFixed(1)}M`;
|
|
23638
|
+
if (tokens >= 1e3) return `${(tokens / 1e3).toFixed(1)}k`;
|
|
23639
|
+
return String(tokens);
|
|
23640
|
+
}
|
|
23641
|
+
function formatDuration(minutes) {
|
|
23642
|
+
if (minutes < 1) return "<1m";
|
|
23643
|
+
if (minutes < 60) return `${Math.round(minutes)}m`;
|
|
23644
|
+
const h = Math.floor(minutes / 60);
|
|
23645
|
+
const m = Math.round(minutes % 60);
|
|
23646
|
+
return m > 0 ? `${h}h ${m}m` : `${h}h`;
|
|
23647
|
+
}
|
|
23636
23648
|
|
|
23637
23649
|
// src/ui/viewer/components/SearchBar.tsx
|
|
23650
|
+
var SOURCE_BADGE = {
|
|
23651
|
+
vector: { bg: "bg-violet-500/15", text: "text-violet-400", label: "semantic" },
|
|
23652
|
+
keyword: { bg: "bg-amber-500/15", text: "text-amber-400", label: "keyword" },
|
|
23653
|
+
hybrid: { bg: "bg-cyan-500/15", text: "text-cyan-400", label: "hybrid" }
|
|
23654
|
+
};
|
|
23638
23655
|
function SearchBar() {
|
|
23639
23656
|
const [query, setQuery] = (0, import_react.useState)("");
|
|
23640
|
-
const [results, setResults] = (0, import_react.useState)(
|
|
23657
|
+
const [results, setResults] = (0, import_react.useState)([]);
|
|
23641
23658
|
const [isOpen, setIsOpen] = (0, import_react.useState)(false);
|
|
23642
23659
|
const [isSearching, setIsSearching] = (0, import_react.useState)(false);
|
|
23643
23660
|
const [selectedIndex, setSelectedIndex] = (0, import_react.useState)(0);
|
|
@@ -23645,14 +23662,15 @@ function SearchBar() {
|
|
|
23645
23662
|
const debounceRef = (0, import_react.useRef)();
|
|
23646
23663
|
const doSearch = (0, import_react.useCallback)(async (q) => {
|
|
23647
23664
|
if (!q.trim()) {
|
|
23648
|
-
setResults(
|
|
23665
|
+
setResults([]);
|
|
23649
23666
|
return;
|
|
23650
23667
|
}
|
|
23651
23668
|
setIsSearching(true);
|
|
23652
23669
|
try {
|
|
23653
|
-
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}&limit=
|
|
23670
|
+
const res = await fetch(`/api/hybrid-search?q=${encodeURIComponent(q)}&limit=10`);
|
|
23654
23671
|
if (res.ok) {
|
|
23655
|
-
|
|
23672
|
+
const data = await res.json();
|
|
23673
|
+
setResults(data.results || []);
|
|
23656
23674
|
setSelectedIndex(0);
|
|
23657
23675
|
}
|
|
23658
23676
|
} catch (err) {
|
|
@@ -23670,9 +23688,21 @@ function SearchBar() {
|
|
|
23670
23688
|
const close = (0, import_react.useCallback)(() => {
|
|
23671
23689
|
setIsOpen(false);
|
|
23672
23690
|
setQuery("");
|
|
23673
|
-
setResults(
|
|
23691
|
+
setResults([]);
|
|
23674
23692
|
setSelectedIndex(0);
|
|
23675
23693
|
}, []);
|
|
23694
|
+
const total = results.length;
|
|
23695
|
+
const openSelected = (0, import_react.useCallback)(() => {
|
|
23696
|
+
if (total === 0) return;
|
|
23697
|
+
const item = results[selectedIndex];
|
|
23698
|
+
if (!item) return;
|
|
23699
|
+
const targetId = `obs-${item.id}`;
|
|
23700
|
+
close();
|
|
23701
|
+
setTimeout(() => {
|
|
23702
|
+
const el = document.querySelector(`[data-id="${targetId}"]`);
|
|
23703
|
+
if (el) el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
23704
|
+
}, 100);
|
|
23705
|
+
}, [results, selectedIndex, total, close]);
|
|
23676
23706
|
(0, import_react.useEffect)(() => {
|
|
23677
23707
|
const handler = (e) => {
|
|
23678
23708
|
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
@@ -23681,11 +23711,23 @@ function SearchBar() {
|
|
|
23681
23711
|
setTimeout(() => inputRef.current?.focus(), 50);
|
|
23682
23712
|
}
|
|
23683
23713
|
if (e.key === "Escape") close();
|
|
23714
|
+
if (!isOpen) return;
|
|
23715
|
+
if (e.key === "ArrowDown") {
|
|
23716
|
+
e.preventDefault();
|
|
23717
|
+
setSelectedIndex((prev) => Math.min(prev + 1, total - 1));
|
|
23718
|
+
}
|
|
23719
|
+
if (e.key === "ArrowUp") {
|
|
23720
|
+
e.preventDefault();
|
|
23721
|
+
setSelectedIndex((prev) => Math.max(prev - 1, 0));
|
|
23722
|
+
}
|
|
23723
|
+
if (e.key === "Enter" && total > 0) {
|
|
23724
|
+
e.preventDefault();
|
|
23725
|
+
openSelected();
|
|
23726
|
+
}
|
|
23684
23727
|
};
|
|
23685
23728
|
window.addEventListener("keydown", handler);
|
|
23686
23729
|
return () => window.removeEventListener("keydown", handler);
|
|
23687
|
-
}, [close]);
|
|
23688
|
-
const total = results ? results.observations.length + results.summaries.length : 0;
|
|
23730
|
+
}, [close, isOpen, total, openSelected]);
|
|
23689
23731
|
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
|
|
23690
23732
|
"button",
|
|
23691
23733
|
{
|
|
@@ -23693,12 +23735,13 @@ function SearchBar() {
|
|
|
23693
23735
|
setIsOpen(true);
|
|
23694
23736
|
setTimeout(() => inputRef.current?.focus(), 50);
|
|
23695
23737
|
},
|
|
23696
|
-
className: "flex items-center gap-2.5 flex-1 max-w-md px-3 py-2 rounded-lg bg-surface-2 border border-border text-zinc-500 hover:text-zinc-300 hover:border-border-hover transition-all cursor-text"
|
|
23738
|
+
className: "flex items-center gap-2.5 flex-1 max-w-md px-3 py-2 rounded-lg bg-surface-2 border border-border text-zinc-500 hover:text-zinc-300 hover:border-border-hover transition-all cursor-text",
|
|
23739
|
+
"aria-label": "Search memories"
|
|
23697
23740
|
},
|
|
23698
23741
|
/* @__PURE__ */ import_react.default.createElement("svg", { className: "w-4 h-4 flex-shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("circle", { cx: "11", cy: "11", r: "8" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "m21 21-4.3-4.3" })),
|
|
23699
23742
|
/* @__PURE__ */ import_react.default.createElement("span", { className: "text-sm" }, "Search memories..."),
|
|
23700
23743
|
/* @__PURE__ */ import_react.default.createElement("kbd", { className: "ml-auto hidden sm:inline text-[11px] text-zinc-600 bg-surface-3 px-1.5 py-0.5 rounded font-mono border border-border" }, typeof navigator !== "undefined" && navigator.platform?.includes("Mac") ? "\u2318K" : "Ctrl+K")
|
|
23701
|
-
), isOpen && /* @__PURE__ */ import_react.default.createElement("div", { className: "fixed inset-0 z-[999] bg-black/60 backdrop-blur-sm animate-fade-in", onClick: close }, /* @__PURE__ */ import_react.default.createElement("div", { className: "mx-auto mt-[12vh] w-full max-w-xl animate-scale-in px-4", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-surface-1 border border-border rounded-xl shadow-2xl overflow-hidden" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-3 px-4 py-3.5 border-b border-border" }, /* @__PURE__ */ import_react.default.createElement("svg", { className: "w-5 h-5 text-accent-violet flex-shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("circle", { cx: "11", cy: "11", r: "8" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "m21 21-4.3-4.3" })), /* @__PURE__ */ import_react.default.createElement(
|
|
23744
|
+
), isOpen && /* @__PURE__ */ import_react.default.createElement("div", { className: "fixed inset-0 z-[999] bg-black/60 backdrop-blur-sm animate-fade-in", onClick: close, role: "dialog", "aria-modal": "true", "aria-label": "Search" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "mx-auto mt-[12vh] w-full max-w-xl animate-scale-in px-4", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-surface-1 border border-border rounded-xl shadow-2xl overflow-hidden" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-3 px-4 py-3.5 border-b border-border" }, /* @__PURE__ */ import_react.default.createElement("svg", { className: "w-5 h-5 text-accent-violet flex-shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("circle", { cx: "11", cy: "11", r: "8" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "m21 21-4.3-4.3" })), /* @__PURE__ */ import_react.default.createElement(
|
|
23702
23745
|
"input",
|
|
23703
23746
|
{
|
|
23704
23747
|
ref: inputRef,
|
|
@@ -23707,22 +23750,73 @@ function SearchBar() {
|
|
|
23707
23750
|
placeholder: "Search observations, summaries, concepts...",
|
|
23708
23751
|
value: query,
|
|
23709
23752
|
onChange: handleChange,
|
|
23753
|
+
role: "combobox",
|
|
23754
|
+
"aria-expanded": total > 0,
|
|
23755
|
+
"aria-controls": "search-results",
|
|
23756
|
+
"aria-activedescendant": total > 0 ? `search-item-${selectedIndex}` : void 0,
|
|
23710
23757
|
autoFocus: true
|
|
23711
23758
|
}
|
|
23712
|
-
), isSearching && /* @__PURE__ */ import_react.default.createElement("div", { className: "w-4 h-4 border-2 border-accent-violet/30 border-t-accent-violet rounded-full animate-spin" }), /* @__PURE__ */ import_react.default.createElement("kbd", { className: "text-[10px] text-zinc-500 bg-surface-3 px-1.5 py-0.5 rounded font-mono border border-border cursor-pointer", onClick: close }, "ESC")),
|
|
23713
|
-
const badge = getTypeBadgeClasses(
|
|
23714
|
-
|
|
23715
|
-
|
|
23759
|
+
), isSearching && /* @__PURE__ */ import_react.default.createElement("div", { className: "w-4 h-4 border-2 border-accent-violet/30 border-t-accent-violet rounded-full animate-spin" }), /* @__PURE__ */ import_react.default.createElement("kbd", { className: "text-[10px] text-zinc-500 bg-surface-3 px-1.5 py-0.5 rounded font-mono border border-border cursor-pointer", onClick: close }, "ESC")), /* @__PURE__ */ import_react.default.createElement("div", { id: "search-results", className: "max-h-[360px] overflow-y-auto", role: "listbox" }, total === 0 && !isSearching && query.trim() && /* @__PURE__ */ import_react.default.createElement("div", { className: "px-4 py-10 text-center" }, /* @__PURE__ */ import_react.default.createElement("p", { className: "text-sm text-zinc-500" }, 'No results for "', query, '"')), results.map((item, idx) => {
|
|
23760
|
+
const badge = getTypeBadgeClasses(item.type);
|
|
23761
|
+
const srcBadge = SOURCE_BADGE[item.source] || SOURCE_BADGE.keyword;
|
|
23762
|
+
return /* @__PURE__ */ import_react.default.createElement(
|
|
23763
|
+
"div",
|
|
23764
|
+
{
|
|
23765
|
+
key: item.id,
|
|
23766
|
+
id: `search-item-${idx}`,
|
|
23767
|
+
role: "option",
|
|
23768
|
+
"aria-selected": idx === selectedIndex,
|
|
23769
|
+
className: `flex items-start gap-3 px-4 py-2.5 cursor-pointer transition-colors ${idx === selectedIndex ? "bg-surface-2" : "hover:bg-surface-2/50"}`,
|
|
23770
|
+
onClick: () => {
|
|
23771
|
+
setSelectedIndex(idx);
|
|
23772
|
+
openSelected();
|
|
23773
|
+
}
|
|
23774
|
+
},
|
|
23775
|
+
/* @__PURE__ */ import_react.default.createElement("div", { className: `w-2 h-2 rounded-full mt-1.5 flex-shrink-0 ${badge.dot}` }),
|
|
23776
|
+
/* @__PURE__ */ import_react.default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "text-sm text-zinc-200 truncate" }, item.title), item.content && /* @__PURE__ */ import_react.default.createElement("div", { className: "text-xs text-zinc-500 truncate mt-0.5" }, item.content.substring(0, 120)), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-2 mt-1" }, /* @__PURE__ */ import_react.default.createElement("span", { className: `text-[10px] font-medium px-1.5 py-0.5 rounded ${badge.bg} ${badge.text}` }, item.type), /* @__PURE__ */ import_react.default.createElement("span", { className: `text-[10px] font-medium px-1.5 py-0.5 rounded ${srcBadge.bg} ${srcBadge.text}` }, srcBadge.label), /* @__PURE__ */ import_react.default.createElement("span", { className: "text-[10px] text-zinc-600 font-mono" }, timeAgo(item.created_at_epoch)), item.score > 0 && /* @__PURE__ */ import_react.default.createElement("span", { className: "text-[10px] text-zinc-700 font-mono" }, (item.score * 100).toFixed(0), "%")))
|
|
23777
|
+
);
|
|
23778
|
+
})), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-4 px-4 py-2.5 border-t border-border text-[11px] text-zinc-600" }, /* @__PURE__ */ import_react.default.createElement("span", null, /* @__PURE__ */ import_react.default.createElement("kbd", { className: "px-1 py-0.5 rounded bg-surface-3 border border-border font-mono mr-1" }, "\u2191\u2193"), "navigate"), /* @__PURE__ */ import_react.default.createElement("span", null, /* @__PURE__ */ import_react.default.createElement("kbd", { className: "px-1 py-0.5 rounded bg-surface-3 border border-border font-mono mr-1" }, "\u21B5"), "open"), /* @__PURE__ */ import_react.default.createElement("span", null, /* @__PURE__ */ import_react.default.createElement("kbd", { className: "px-1 py-0.5 rounded bg-surface-3 border border-border font-mono mr-1" }, "esc"), "close"))))));
|
|
23716
23779
|
}
|
|
23717
23780
|
|
|
23718
23781
|
// src/ui/viewer/components/Header.tsx
|
|
23719
|
-
function
|
|
23720
|
-
|
|
23782
|
+
function formatAgo(ms) {
|
|
23783
|
+
if (ms <= 0) return "";
|
|
23784
|
+
const sec = Math.floor(ms / 1e3);
|
|
23785
|
+
if (sec < 5) return "just now";
|
|
23786
|
+
if (sec < 60) return `${sec}s ago`;
|
|
23787
|
+
const min = Math.floor(sec / 60);
|
|
23788
|
+
if (min < 60) return `${min}m ago`;
|
|
23789
|
+
return `${Math.floor(min / 60)}h ago`;
|
|
23790
|
+
}
|
|
23791
|
+
function Header({ isConnected, lastEventTime, resolvedTheme, themePreference, onThemeChange, currentView, onViewChange, onMenuToggle }) {
|
|
23792
|
+
const cycleTheme = () => {
|
|
23793
|
+
const order = ["dark", "light", "system"];
|
|
23794
|
+
const current = order.indexOf(themePreference);
|
|
23795
|
+
onThemeChange(order[(current + 1) % order.length]);
|
|
23796
|
+
};
|
|
23797
|
+
const [now, setNow] = (0, import_react2.useState)(Date.now());
|
|
23798
|
+
(0, import_react2.useEffect)(() => {
|
|
23799
|
+
const t = setInterval(() => setNow(Date.now()), 5e3);
|
|
23800
|
+
return () => clearInterval(t);
|
|
23801
|
+
}, []);
|
|
23802
|
+
const agoText = lastEventTime > 0 ? formatAgo(now - lastEventTime) : "";
|
|
23803
|
+
const isFresh = lastEventTime > 0 && now - lastEventTime < 3e3;
|
|
23804
|
+
return /* @__PURE__ */ import_react2.default.createElement("header", { className: "flex items-center gap-4 px-4 md:px-6 h-14 bg-surface-1 border-b border-border z-50" }, onMenuToggle && /* @__PURE__ */ import_react2.default.createElement(
|
|
23805
|
+
"button",
|
|
23806
|
+
{
|
|
23807
|
+
onClick: onMenuToggle,
|
|
23808
|
+
className: "md:hidden w-8 h-8 rounded-lg bg-surface-2 border border-border text-zinc-400 hover:text-zinc-100 hover:bg-surface-3 transition-all flex items-center justify-center flex-shrink-0",
|
|
23809
|
+
"aria-label": "Apri menu laterale"
|
|
23810
|
+
},
|
|
23811
|
+
/* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("line", { x1: "4", y1: "6", x2: "20", y2: "6" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "4", y1: "12", x2: "20", y2: "12" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "4", y1: "18", x2: "20", y2: "18" }))
|
|
23812
|
+
), /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex items-center gap-3 flex-shrink-0" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "w-8 h-8 rounded-lg bg-accent-violet flex items-center justify-center" }, /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-[18px] h-[18px] text-white", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 2a4 4 0 0 1 4 4c0 1.95-1.4 3.58-3.25 3.93a1 1 0 0 0-.75.97V13" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 2a4 4 0 0 0-4 4c0 1.95 1.4 3.58 3.25 3.93a1 1 0 0 1 .75.97V13" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M9 18h6" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M10 22h4" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 13v5" }))), /* @__PURE__ */ import_react2.default.createElement("div", null, /* @__PURE__ */ import_react2.default.createElement("h1", { className: "text-[15px] font-bold text-zinc-100 leading-none" }, "Kiro Memory"), /* @__PURE__ */ import_react2.default.createElement("span", { className: "text-[11px] text-zinc-500 mt-0.5 block" }, "Memory Dashboard"))), /* @__PURE__ */ import_react2.default.createElement("div", { className: "hidden md:block w-px h-6 bg-border" }), /* @__PURE__ */ import_react2.default.createElement(SearchBar, null), /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex-1" }), /* @__PURE__ */ import_react2.default.createElement("div", { className: "hidden sm:flex items-center gap-2 px-3 py-1.5 rounded-lg bg-surface-2 border border-border" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: `w-2 h-2 rounded-full transition-all ${isFresh ? "bg-accent-green scale-125" : isConnected ? "bg-accent-green animate-pulse-dot" : "bg-zinc-500"}` }), /* @__PURE__ */ import_react2.default.createElement("span", { className: `text-xs font-medium ${isConnected ? "text-accent-green" : "text-zinc-500"}` }, isConnected ? "Live" : "Offline"), agoText && /* @__PURE__ */ import_react2.default.createElement("span", { className: "text-[10px] text-zinc-600 ml-1" }, agoText)), /* @__PURE__ */ import_react2.default.createElement("div", { className: "hidden sm:flex items-center rounded-lg bg-surface-2 border border-border p-0.5", role: "tablist", "aria-label": "View mode" }, /* @__PURE__ */ import_react2.default.createElement(
|
|
23721
23813
|
"button",
|
|
23722
23814
|
{
|
|
23723
23815
|
onClick: () => onViewChange("feed"),
|
|
23724
23816
|
className: `flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all ${currentView === "feed" ? "bg-surface-3 text-zinc-100 shadow-sm" : "text-zinc-500 hover:text-zinc-300"}`,
|
|
23725
|
-
|
|
23817
|
+
role: "tab",
|
|
23818
|
+
"aria-selected": currentView === "feed",
|
|
23819
|
+
"aria-label": "Memory Feed"
|
|
23726
23820
|
},
|
|
23727
23821
|
/* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("line", { x1: "8", y1: "6", x2: "21", y2: "6" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "8", y1: "12", x2: "21", y2: "12" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "8", y1: "18", x2: "21", y2: "18" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "3", y1: "6", x2: "3.01", y2: "6" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "3", y1: "12", x2: "3.01", y2: "12" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "3", y1: "18", x2: "3.01", y2: "18" })),
|
|
23728
23822
|
"Feed"
|
|
@@ -23731,34 +23825,44 @@ function Header({ isConnected, resolvedTheme, onThemeToggle, currentView, onView
|
|
|
23731
23825
|
{
|
|
23732
23826
|
onClick: () => onViewChange("analytics"),
|
|
23733
23827
|
className: `flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all ${currentView === "analytics" ? "bg-surface-3 text-zinc-100 shadow-sm" : "text-zinc-500 hover:text-zinc-300"}`,
|
|
23734
|
-
|
|
23828
|
+
role: "tab",
|
|
23829
|
+
"aria-selected": currentView === "analytics",
|
|
23830
|
+
"aria-label": "Analytics Dashboard"
|
|
23735
23831
|
},
|
|
23736
23832
|
/* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("line", { x1: "18", y1: "20", x2: "18", y2: "10" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "12", y1: "20", x2: "12", y2: "4" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "6", y1: "20", x2: "6", y2: "14" })),
|
|
23737
23833
|
"Analytics"
|
|
23834
|
+
), /* @__PURE__ */ import_react2.default.createElement(
|
|
23835
|
+
"button",
|
|
23836
|
+
{
|
|
23837
|
+
onClick: () => onViewChange("sessions"),
|
|
23838
|
+
className: `flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all ${currentView === "sessions" ? "bg-surface-3 text-zinc-100 shadow-sm" : "text-zinc-500 hover:text-zinc-300"}`,
|
|
23839
|
+
role: "tab",
|
|
23840
|
+
"aria-selected": currentView === "sessions",
|
|
23841
|
+
"aria-label": "Sessions"
|
|
23842
|
+
},
|
|
23843
|
+
/* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ import_react2.default.createElement("polyline", { points: "12 6 12 12 16 14" })),
|
|
23844
|
+
"Sessions"
|
|
23738
23845
|
)), /* @__PURE__ */ import_react2.default.createElement(
|
|
23739
23846
|
"button",
|
|
23740
23847
|
{
|
|
23741
|
-
onClick:
|
|
23848
|
+
onClick: cycleTheme,
|
|
23742
23849
|
className: "w-8 h-8 rounded-lg bg-surface-2 border border-border text-zinc-400 hover:text-zinc-100 hover:bg-surface-3 hover:border-border-hover transition-all flex items-center justify-center",
|
|
23743
|
-
title:
|
|
23850
|
+
title: `Theme: ${themePreference}`,
|
|
23851
|
+
"aria-label": `Theme: ${themePreference}. Click to change`
|
|
23744
23852
|
},
|
|
23745
|
-
resolvedTheme === "dark" ? /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("circle", { cx: "12", cy: "12", r: "4" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" })) : /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" }))
|
|
23853
|
+
themePreference === "system" ? /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "8", y1: "21", x2: "16", y2: "21" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "12", y1: "17", x2: "12", y2: "21" })) : resolvedTheme === "dark" ? /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("circle", { cx: "12", cy: "12", r: "4" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" })) : /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" }))
|
|
23746
23854
|
));
|
|
23747
23855
|
}
|
|
23748
23856
|
|
|
23749
23857
|
// src/ui/viewer/components/Sidebar.tsx
|
|
23750
23858
|
var import_react3 = __toESM(require_react(), 1);
|
|
23751
23859
|
var TYPE_CONFIG = {
|
|
23752
|
-
"file-write": { color: "bg-accent-green", label: "
|
|
23753
|
-
"file-read": { color: "bg-accent-cyan", label: "
|
|
23860
|
+
"file-write": { color: "bg-accent-green", label: "Changes" },
|
|
23861
|
+
"file-read": { color: "bg-accent-cyan", label: "Reads" },
|
|
23754
23862
|
"command": { color: "bg-accent-amber", label: "Commands" },
|
|
23755
23863
|
"research": { color: "bg-accent-blue", label: "Research" },
|
|
23756
23864
|
"delegation": { color: "bg-accent-violet", label: "Delegations" },
|
|
23757
|
-
"tool-use": { color: "bg-zinc-400", label: "
|
|
23758
|
-
"constraint": { color: "bg-red-500", label: "Constraints" },
|
|
23759
|
-
"decision": { color: "bg-orange-500", label: "Decisions" },
|
|
23760
|
-
"heuristic": { color: "bg-indigo-500", label: "Heuristics" },
|
|
23761
|
-
"rejected": { color: "bg-slate-500", label: "Rejected" }
|
|
23865
|
+
"tool-use": { color: "bg-zinc-400", label: "Tools" }
|
|
23762
23866
|
};
|
|
23763
23867
|
var PROJECT_COLORS = [
|
|
23764
23868
|
{ bg: "bg-accent-violet/15", text: "text-accent-violet", ring: "ring-accent-violet/30" },
|
|
@@ -23786,6 +23890,7 @@ function Sidebar({
|
|
|
23786
23890
|
}) {
|
|
23787
23891
|
const [editingProject, setEditingProject] = (0, import_react3.useState)(null);
|
|
23788
23892
|
const [editValue, setEditValue] = (0, import_react3.useState)("");
|
|
23893
|
+
const [renameFeedback, setRenameFeedback] = (0, import_react3.useState)(null);
|
|
23789
23894
|
const editInputRef = (0, import_react3.useRef)(null);
|
|
23790
23895
|
(0, import_react3.useEffect)(() => {
|
|
23791
23896
|
if (editingProject && editInputRef.current) {
|
|
@@ -23800,7 +23905,13 @@ function Sidebar({
|
|
|
23800
23905
|
};
|
|
23801
23906
|
const confirmEdit = async () => {
|
|
23802
23907
|
if (editingProject && editValue.trim()) {
|
|
23803
|
-
|
|
23908
|
+
try {
|
|
23909
|
+
await onRenameProject(editingProject, editValue.trim());
|
|
23910
|
+
setRenameFeedback({ project: editingProject, success: true });
|
|
23911
|
+
} catch {
|
|
23912
|
+
setRenameFeedback({ project: editingProject, success: false });
|
|
23913
|
+
}
|
|
23914
|
+
setTimeout(() => setRenameFeedback(null), 2e3);
|
|
23804
23915
|
}
|
|
23805
23916
|
setEditingProject(null);
|
|
23806
23917
|
};
|
|
@@ -23845,9 +23956,11 @@ function Sidebar({
|
|
|
23845
23956
|
},
|
|
23846
23957
|
/* @__PURE__ */ import_react3.default.createElement("div", { className: `w-7 h-7 rounded-md flex items-center justify-center flex-shrink-0 text-[11px] font-bold ${pc.bg} ${pc.text}` }, initials),
|
|
23847
23958
|
/* @__PURE__ */ import_react3.default.createElement("span", { className: "flex-1 truncate" }, getDisplayName(project)),
|
|
23959
|
+
renameFeedback?.project === project && /* @__PURE__ */ import_react3.default.createElement("span", { className: `text-[10px] font-medium animate-fade-in ${renameFeedback.success ? "text-accent-green" : "text-accent-rose"}` }, renameFeedback.success ? "Saved" : "Error"),
|
|
23848
23960
|
/* @__PURE__ */ import_react3.default.createElement(
|
|
23849
|
-
"
|
|
23961
|
+
"button",
|
|
23850
23962
|
{
|
|
23963
|
+
type: "button",
|
|
23851
23964
|
onClick: (e) => startEditing(project, e),
|
|
23852
23965
|
className: "opacity-0 group-hover:opacity-100 text-zinc-600 hover:text-zinc-300 transition-all p-0.5",
|
|
23853
23966
|
title: "Rename"
|
|
@@ -23861,11 +23974,11 @@ function Sidebar({
|
|
|
23861
23974
|
"label",
|
|
23862
23975
|
{
|
|
23863
23976
|
key: type,
|
|
23864
|
-
className: `flex items-center gap-3 px-3 py-2 rounded-lg text-sm cursor-pointer transition-all ${isActive ? "text-zinc-300 hover:text-zinc-100" : "text-zinc-
|
|
23977
|
+
className: `flex items-center gap-3 px-3 py-2 rounded-lg text-sm cursor-pointer transition-all ${isActive ? "text-zinc-300 hover:text-zinc-100" : "text-zinc-500 hover:text-zinc-400"}`
|
|
23865
23978
|
},
|
|
23866
|
-
/* @__PURE__ */ import_react3.default.createElement("
|
|
23867
|
-
/* @__PURE__ */ import_react3.default.createElement("
|
|
23868
|
-
/* @__PURE__ */ import_react3.default.createElement("div", { className: `w-2.5 h-2.5 rounded-full flex-shrink-0 ${config.color} ${isActive ? "opacity-100" : "opacity-30"}
|
|
23979
|
+
/* @__PURE__ */ import_react3.default.createElement("input", { type: "checkbox", checked: isActive, onChange: () => onToggleType(type), className: "sr-only", "aria-label": `Filter ${config.label}` }),
|
|
23980
|
+
/* @__PURE__ */ import_react3.default.createElement("div", { className: `w-4 h-4 rounded border flex items-center justify-center flex-shrink-0 transition-all ${isActive ? "bg-accent-violet border-accent-violet" : "bg-transparent border-zinc-600"}`, "aria-hidden": "true" }, isActive && /* @__PURE__ */ import_react3.default.createElement("svg", { className: "w-2.5 h-2.5 text-white", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3" }, /* @__PURE__ */ import_react3.default.createElement("polyline", { points: "20 6 9 17 4 12" }))),
|
|
23981
|
+
/* @__PURE__ */ import_react3.default.createElement("div", { className: `w-2.5 h-2.5 rounded-full flex-shrink-0 ${config.color} ${isActive ? "opacity-100" : "opacity-30"}`, "aria-hidden": "true" }),
|
|
23869
23982
|
/* @__PURE__ */ import_react3.default.createElement("span", { className: "flex-1" }, config.label)
|
|
23870
23983
|
);
|
|
23871
23984
|
}))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mx-4 h-px bg-border" }), /* @__PURE__ */ import_react3.default.createElement("div", { className: "p-4" }, /* @__PURE__ */ import_react3.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-3 px-2" }, "Statistics"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "grid grid-cols-2 gap-2" }, [
|
|
@@ -23873,38 +23986,134 @@ function Sidebar({
|
|
|
23873
23986
|
{ label: "Summaries", value: stats.summaries, color: "text-accent-cyan" },
|
|
23874
23987
|
{ label: "Prompts", value: stats.prompts, color: "text-accent-amber" },
|
|
23875
23988
|
{ label: "Projects", value: projects.length, color: "text-accent-green" }
|
|
23876
|
-
].map((item) => /* @__PURE__ */ import_react3.default.createElement("div", { key: item.label, className: "rounded-lg bg-surface-2 border border-border px-3 py-3 text-center" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: `text-xl font-bold tabular-nums ${item.color}` }, item.value), /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-[10px] uppercase tracking-wider text-zinc-600 mt-1" }, item.label))))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "
|
|
23989
|
+
].map((item) => /* @__PURE__ */ import_react3.default.createElement("div", { key: item.label, className: "rounded-lg bg-surface-2 border border-border px-3 py-3 text-center" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: `text-xl font-bold tabular-nums ${item.color}` }, item.value), /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-[10px] uppercase tracking-wider text-zinc-600 mt-1" }, item.label))))), (stats.tokenEconomics.discoveryTokens > 0 || stats.tokenEconomics.readTokens > 0) && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement("div", { className: "mx-4 h-px bg-border" }), /* @__PURE__ */ import_react3.default.createElement("div", { className: "p-4" }, /* @__PURE__ */ import_react3.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-3 px-2" }, "Token Economics"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "rounded-lg bg-surface-2 border border-border px-3 py-2.5" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "flex items-center justify-between mb-1.5" }, /* @__PURE__ */ import_react3.default.createElement("span", { className: "text-[10px] uppercase tracking-wider text-zinc-600" }, "Discovery"), /* @__PURE__ */ import_react3.default.createElement("span", { className: "text-xs font-bold text-amber-400 tabular-nums" }, formatTokenCount(stats.tokenEconomics.discoveryTokens))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "flex items-center justify-between mb-1.5" }, /* @__PURE__ */ import_react3.default.createElement("span", { className: "text-[10px] uppercase tracking-wider text-zinc-600" }, "Read cost"), /* @__PURE__ */ import_react3.default.createElement("span", { className: "text-xs font-bold text-cyan-400 tabular-nums" }, formatTokenCount(stats.tokenEconomics.readTokens))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "h-px bg-border my-1.5" }), /* @__PURE__ */ import_react3.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ import_react3.default.createElement("span", { className: "text-[10px] uppercase tracking-wider text-zinc-600" }, "Savings"), /* @__PURE__ */ import_react3.default.createElement("span", { className: "text-xs font-bold text-emerald-400 tabular-nums" }, formatTokenCount(stats.tokenEconomics.savings)))), stats.tokenEconomics.discoveryTokens > 0 && /* @__PURE__ */ import_react3.default.createElement("div", { className: "rounded-md overflow-hidden h-2 bg-surface-3" }, /* @__PURE__ */ import_react3.default.createElement(
|
|
23990
|
+
"div",
|
|
23991
|
+
{
|
|
23992
|
+
className: "h-full bg-gradient-to-r from-emerald-500 to-emerald-400 transition-all",
|
|
23993
|
+
style: { width: `${Math.min(100, Math.round(stats.tokenEconomics.readTokens / stats.tokenEconomics.discoveryTokens * 100))}%` }
|
|
23994
|
+
}
|
|
23995
|
+
)), stats.tokenEconomics.discoveryTokens > 0 && /* @__PURE__ */ import_react3.default.createElement("p", { className: "text-[10px] text-zinc-600 text-center" }, Math.round((1 - stats.tokenEconomics.readTokens / stats.tokenEconomics.discoveryTokens) * 100), "% token reduction")))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mt-auto px-4 py-4 space-y-2" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "flex items-center justify-center gap-3" }, /* @__PURE__ */ import_react3.default.createElement("a", { href: "https://github.com/auriti-web-design/kiro-memory", target: "_blank", rel: "noopener noreferrer", className: "text-zinc-600 hover:text-zinc-400 transition-colors", title: "GitHub" }, /* @__PURE__ */ import_react3.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ import_react3.default.createElement("path", { d: "M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2z" }))), /* @__PURE__ */ import_react3.default.createElement("a", { href: "https://auritidesign.it/docs/kiro-memory/", target: "_blank", rel: "noopener noreferrer", className: "text-zinc-600 hover:text-zinc-400 transition-colors", title: "Documentazione" }, /* @__PURE__ */ import_react3.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react3.default.createElement("path", { d: "M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" }), /* @__PURE__ */ import_react3.default.createElement("path", { d: "M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" })))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-[10px] text-zinc-700 font-mono text-center" }, "Kiro Memory v1.8.0")));
|
|
23877
23996
|
}
|
|
23878
23997
|
|
|
23879
23998
|
// src/ui/viewer/components/Feed.tsx
|
|
23880
23999
|
var import_react4 = __toESM(require_react(), 1);
|
|
23881
24000
|
var TYPE_STYLES = {
|
|
23882
|
-
"file-write": { border: "border-l-emerald-500", bg: "bg-emerald-500/10", text: "text-emerald-400", dot: "bg-emerald-500", label: "
|
|
23883
|
-
"file-read": { border: "border-l-cyan-500", bg: "bg-cyan-500/10", text: "text-cyan-400", dot: "bg-cyan-500", label: "
|
|
24001
|
+
"file-write": { border: "border-l-emerald-500", bg: "bg-emerald-500/10", text: "text-emerald-400", dot: "bg-emerald-500", label: "change" },
|
|
24002
|
+
"file-read": { border: "border-l-cyan-500", bg: "bg-cyan-500/10", text: "text-cyan-400", dot: "bg-cyan-500", label: "read" },
|
|
23884
24003
|
"command": { border: "border-l-amber-500", bg: "bg-amber-500/10", text: "text-amber-400", dot: "bg-amber-500", label: "command" },
|
|
23885
24004
|
"research": { border: "border-l-blue-500", bg: "bg-blue-500/10", text: "text-blue-400", dot: "bg-blue-500", label: "research" },
|
|
23886
24005
|
"delegation": { border: "border-l-violet-500", bg: "bg-violet-500/10", text: "text-violet-400", dot: "bg-violet-500", label: "delegation" },
|
|
23887
|
-
"tool-use": { border: "border-l-zinc-500", bg: "bg-zinc-500/10", text: "text-zinc-400", dot: "bg-zinc-500", label: "tool
|
|
23888
|
-
"constraint": { border: "border-l-red-500", bg: "bg-red-500/10", text: "text-red-400", dot: "bg-red-500", label: "constraint" },
|
|
23889
|
-
"decision": { border: "border-l-orange-500", bg: "bg-orange-500/10", text: "text-orange-400", dot: "bg-orange-500", label: "decision" },
|
|
23890
|
-
"heuristic": { border: "border-l-indigo-500", bg: "bg-indigo-500/10", text: "text-indigo-400", dot: "bg-indigo-500", label: "heuristic" },
|
|
23891
|
-
"rejected": { border: "border-l-slate-500", bg: "bg-slate-500/10", text: "text-slate-400", dot: "bg-slate-500", label: "rejected" }
|
|
24006
|
+
"tool-use": { border: "border-l-zinc-500", bg: "bg-zinc-500/10", text: "text-zinc-400", dot: "bg-zinc-500", label: "tool" }
|
|
23892
24007
|
};
|
|
23893
24008
|
function getTypeStyle(type) {
|
|
23894
24009
|
return TYPE_STYLES[type] || TYPE_STYLES["tool-use"];
|
|
23895
24010
|
}
|
|
24011
|
+
var CONCEPT_COLORS = {
|
|
24012
|
+
"testing": "bg-emerald-500/15 text-emerald-400 ring-emerald-500/25",
|
|
24013
|
+
"ui-component": "bg-violet-500/15 text-violet-400 ring-violet-500/25",
|
|
24014
|
+
"hooks": "bg-violet-500/15 text-violet-400 ring-violet-500/25",
|
|
24015
|
+
"database": "bg-amber-500/15 text-amber-400 ring-amber-500/25",
|
|
24016
|
+
"api": "bg-blue-500/15 text-blue-400 ring-blue-500/25",
|
|
24017
|
+
"configuration": "bg-zinc-500/15 text-zinc-400 ring-zinc-500/25",
|
|
24018
|
+
"styling": "bg-pink-500/15 text-pink-400 ring-pink-500/25",
|
|
24019
|
+
"types": "bg-cyan-500/15 text-cyan-400 ring-cyan-500/25",
|
|
24020
|
+
"sdk": "bg-indigo-500/15 text-indigo-400 ring-indigo-500/25",
|
|
24021
|
+
"build": "bg-orange-500/15 text-orange-400 ring-orange-500/25",
|
|
24022
|
+
"devops": "bg-rose-500/15 text-rose-400 ring-rose-500/25",
|
|
24023
|
+
"documentation": "bg-teal-500/15 text-teal-400 ring-teal-500/25",
|
|
24024
|
+
"search": "bg-sky-500/15 text-sky-400 ring-sky-500/25",
|
|
24025
|
+
"backend": "bg-lime-500/15 text-lime-400 ring-lime-500/25",
|
|
24026
|
+
"git": "bg-orange-500/15 text-orange-400 ring-orange-500/25",
|
|
24027
|
+
"dependencies": "bg-yellow-500/15 text-yellow-400 ring-yellow-500/25",
|
|
24028
|
+
"debugging": "bg-red-500/15 text-red-400 ring-red-500/25",
|
|
24029
|
+
"code-quality": "bg-emerald-500/15 text-emerald-400 ring-emerald-500/25",
|
|
24030
|
+
"networking": "bg-blue-500/15 text-blue-400 ring-blue-500/25",
|
|
24031
|
+
"module-system": "bg-indigo-500/15 text-indigo-400 ring-indigo-500/25",
|
|
24032
|
+
"tech-debt": "bg-red-500/15 text-red-400 ring-red-500/25",
|
|
24033
|
+
"security": "bg-red-500/15 text-red-400 ring-red-500/25",
|
|
24034
|
+
"performance": "bg-amber-500/15 text-amber-400 ring-amber-500/25",
|
|
24035
|
+
"error-handling": "bg-rose-500/15 text-rose-400 ring-rose-500/25"
|
|
24036
|
+
};
|
|
24037
|
+
function basename(filePath) {
|
|
24038
|
+
return filePath.split("/").pop() || filePath;
|
|
24039
|
+
}
|
|
24040
|
+
function stripProjectRoot(path) {
|
|
24041
|
+
return path.replace(/^\/home\/[^/]+\/[^/]+\//, "");
|
|
24042
|
+
}
|
|
24043
|
+
function generateNarrative(obs) {
|
|
24044
|
+
if (obs.narrative) return obs.narrative;
|
|
24045
|
+
const title = obs.title || "";
|
|
24046
|
+
switch (obs.type) {
|
|
24047
|
+
case "file-write": {
|
|
24048
|
+
if (title.startsWith("Written: ") || title.startsWith("Modified ") || title.startsWith("Created ")) {
|
|
24049
|
+
const path = title.replace(/^(Written|Modified|Created):?\s*/, "");
|
|
24050
|
+
const fileName = basename(path);
|
|
24051
|
+
const isEdit = obs.text ? obs.text.includes('"old_string"') : true;
|
|
24052
|
+
return `${isEdit ? "Modified" : "Created"} **${fileName}**`;
|
|
24053
|
+
}
|
|
24054
|
+
return title || "File changed";
|
|
24055
|
+
}
|
|
24056
|
+
case "file-read": {
|
|
24057
|
+
if (title.startsWith("Searched for ") || title.startsWith("Searched codebase")) return title;
|
|
24058
|
+
if (title.startsWith("Read: ")) return `Read **${basename(title.replace("Read: ", ""))}**`;
|
|
24059
|
+
return title ? `Read **${basename(title)}**` : "File read";
|
|
24060
|
+
}
|
|
24061
|
+
case "command": {
|
|
24062
|
+
if (title.startsWith("Executed: ")) {
|
|
24063
|
+
const cmd = title.replace("Executed: ", "").split("|")[0].split("2>&1")[0].split("&&")[0].trim();
|
|
24064
|
+
const shortCmd = cmd.length > 70 ? cmd.substring(0, 67) + "..." : cmd;
|
|
24065
|
+
return `Ran \`${shortCmd}\``;
|
|
24066
|
+
}
|
|
24067
|
+
return title || "Command executed";
|
|
24068
|
+
}
|
|
24069
|
+
case "research": {
|
|
24070
|
+
if (title.startsWith("Searched: ")) return `Web search: "${title.replace("Searched: ", "")}"`;
|
|
24071
|
+
if (title.startsWith("Fetched ")) return `Fetched content from ${title.replace("Fetched ", "")}`;
|
|
24072
|
+
return title || "Research performed";
|
|
24073
|
+
}
|
|
24074
|
+
case "delegation": {
|
|
24075
|
+
const shortTitle = title.length > 100 ? title.substring(0, 97) + "..." : title;
|
|
24076
|
+
return `Delegated: ${shortTitle || "sub-task"}`;
|
|
24077
|
+
}
|
|
24078
|
+
default:
|
|
24079
|
+
return title || `Tool executed (${obs.type})`;
|
|
24080
|
+
}
|
|
24081
|
+
}
|
|
24082
|
+
function getDetailLine(obs) {
|
|
24083
|
+
const parts = [];
|
|
24084
|
+
if (obs.files_modified) {
|
|
24085
|
+
const files = obs.files_modified.split(", ").map(stripProjectRoot);
|
|
24086
|
+
parts.push(`${files.length} file${files.length > 1 ? "s" : ""} modified`);
|
|
24087
|
+
}
|
|
24088
|
+
if (obs.files_read) {
|
|
24089
|
+
const files = obs.files_read.split(", ").map(stripProjectRoot);
|
|
24090
|
+
if (files.length > 1) parts.push(`${files.length} files read`);
|
|
24091
|
+
}
|
|
24092
|
+
return parts.length > 0 ? parts.join(" \xB7 ") : null;
|
|
24093
|
+
}
|
|
24094
|
+
function renderMarkdown(text) {
|
|
24095
|
+
return text.split("**").map((segment, i) => {
|
|
24096
|
+
if (i % 2 === 1) return /* @__PURE__ */ import_react4.default.createElement("strong", { key: i, className: "text-zinc-100 font-semibold" }, segment);
|
|
24097
|
+
return segment.split("`").map(
|
|
24098
|
+
(part, j) => j % 2 === 1 ? /* @__PURE__ */ import_react4.default.createElement("code", { key: `${i}-${j}`, className: "text-amber-400/80 bg-amber-500/10 px-1 py-0.5 rounded text-[12px]" }, part) : /* @__PURE__ */ import_react4.default.createElement("span", { key: `${i}-${j}` }, part)
|
|
24099
|
+
);
|
|
24100
|
+
});
|
|
24101
|
+
}
|
|
23896
24102
|
function Feed({ observations, summaries, prompts, onLoadMore, isLoading, hasMore, getDisplayName }) {
|
|
23897
|
-
const items =
|
|
23898
|
-
(
|
|
24103
|
+
const items = (0, import_react4.useMemo)(
|
|
24104
|
+
() => [...observations, ...summaries, ...prompts].sort(
|
|
24105
|
+
(a, b) => b.created_at_epoch - a.created_at_epoch
|
|
24106
|
+
),
|
|
24107
|
+
[observations, summaries, prompts]
|
|
23899
24108
|
);
|
|
23900
|
-
return /* @__PURE__ */ import_react4.default.createElement("div", { className: "space-y-3" }, items.map((item, index) => {
|
|
24109
|
+
return /* @__PURE__ */ import_react4.default.createElement("div", { className: "space-y-3", "aria-live": "polite", "aria-label": "Memory feed" }, items.map((item, index) => {
|
|
23901
24110
|
const stagger = index < 8 ? `stagger-${index + 1}` : "";
|
|
23902
24111
|
if ("type" in item && "title" in item) {
|
|
23903
|
-
return /* @__PURE__ */ import_react4.default.createElement("div", { key: `obs-${item.id}`, className: `
|
|
24112
|
+
return /* @__PURE__ */ import_react4.default.createElement("div", { key: `obs-${item.id}`, "data-id": `obs-${item.id}`, className: `animate-slide-up ${stagger}` }, /* @__PURE__ */ import_react4.default.createElement(ObservationCard, { obs: item, getDisplayName }));
|
|
23904
24113
|
} else if ("request" in item) {
|
|
23905
|
-
return /* @__PURE__ */ import_react4.default.createElement("div", { key: `sum-${item.id}`, className: `
|
|
24114
|
+
return /* @__PURE__ */ import_react4.default.createElement("div", { key: `sum-${item.id}`, "data-id": `sum-${item.id}`, className: `animate-slide-up ${stagger}` }, /* @__PURE__ */ import_react4.default.createElement(SummaryCard, { summary: item, getDisplayName }));
|
|
23906
24115
|
} else {
|
|
23907
|
-
return /* @__PURE__ */ import_react4.default.createElement("div", { key: `prompt-${item.id}`, className: `
|
|
24116
|
+
return /* @__PURE__ */ import_react4.default.createElement("div", { key: `prompt-${item.id}`, "data-id": `prompt-${item.id}`, className: `animate-slide-up ${stagger}` }, /* @__PURE__ */ import_react4.default.createElement(PromptCard, { prompt: item, getDisplayName }));
|
|
23908
24117
|
}
|
|
23909
24118
|
}), hasMore && items.length > 0 && /* @__PURE__ */ import_react4.default.createElement("div", { className: "pt-2" }, /* @__PURE__ */ import_react4.default.createElement(
|
|
23910
24119
|
"button",
|
|
@@ -23918,30 +24127,25 @@ function Feed({ observations, summaries, prompts, onLoadMore, isLoading, hasMore
|
|
|
23918
24127
|
}
|
|
23919
24128
|
function ObservationCard({ obs, getDisplayName }) {
|
|
23920
24129
|
const style = getTypeStyle(obs.type);
|
|
23921
|
-
const
|
|
23922
|
-
const
|
|
23923
|
-
|
|
23924
|
-
|
|
23925
|
-
"
|
|
23926
|
-
|
|
23927
|
-
onClick: () => setExpanded(!expanded),
|
|
23928
|
-
className: "text-xs text-accent-violet hover:text-accent-violet/80 mt-1.5 font-medium transition-colors"
|
|
23929
|
-
},
|
|
23930
|
-
expanded ? "Show less" : "Show more"
|
|
23931
|
-
)), obs.narrative && /* @__PURE__ */ import_react4.default.createElement("div", { className: "mx-4 mb-3 p-3 rounded-md bg-surface-2 border border-border" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "block text-[10px] font-semibold uppercase tracking-wider text-blue-400 mb-1" }, "Narrative"), /* @__PURE__ */ import_react4.default.createElement("p", { className: "text-xs text-zinc-400 leading-relaxed" }, obs.narrative)), obs.facts && /* @__PURE__ */ import_react4.default.createElement("div", { className: "mx-4 mb-3 p-3 rounded-md bg-surface-2 border border-border" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "block text-[10px] font-semibold uppercase tracking-wider text-cyan-400 mb-1" }, "Facts"), /* @__PURE__ */ import_react4.default.createElement("p", { className: "text-xs text-zinc-400 leading-relaxed" }, obs.facts)), (obs.files_modified || obs.files_read) && /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex flex-wrap gap-2 px-4 pb-3" }, obs.files_modified && /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1.5 text-[11px] font-medium text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded-md" }, /* @__PURE__ */ import_react4.default.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z" }), /* @__PURE__ */ import_react4.default.createElement("path", { d: "M14 2v6h6" })), obs.files_modified.split(",").length, " modified"), obs.files_read && /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1.5 text-[11px] font-medium text-blue-400 bg-blue-500/10 px-2 py-0.5 rounded-md" }, /* @__PURE__ */ import_react4.default.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7Z" })), obs.files_read.split(",").length, " read")), obs.concepts && /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex flex-wrap gap-1.5 px-4 pb-4" }, obs.concepts.split(", ").map((concept, i) => /* @__PURE__ */ import_react4.default.createElement("span", { key: i, className: "text-[11px] text-zinc-500 bg-surface-3 px-2 py-0.5 rounded-md border border-border" }, concept))));
|
|
24130
|
+
const narrative = generateNarrative(obs);
|
|
24131
|
+
const detail = getDetailLine(obs);
|
|
24132
|
+
return /* @__PURE__ */ import_react4.default.createElement("div", { className: `bg-surface-1 border border-border rounded-lg border-l-[3px] ${style.border} shadow-card hover:shadow-card-hover hover:border-border-hover transition-all` }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 py-3.5" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: `inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-md ${style.bg} ${style.text}` }, /* @__PURE__ */ import_react4.default.createElement("span", { className: `w-1.5 h-1.5 rounded-full ${style.dot}` }), style.label), /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center text-[11px] font-medium px-2 py-0.5 rounded-md bg-accent-violet/10 text-accent-violet" }, getDisplayName(obs.project)), obs.is_stale === 1 && /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded-md bg-amber-500/10 text-amber-400 ring-1 ring-amber-500/25", title: "File modificato dopo l'osservazione" }, "stale"), /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-[11px] text-zinc-500 font-mono ml-auto tabular-nums" }, timeAgo(obs.created_at_epoch))), /* @__PURE__ */ import_react4.default.createElement("h4", { className: "text-sm text-zinc-200 leading-snug" }, renderMarkdown(narrative)), obs.subtitle && obs.subtitle !== obs.title && /* @__PURE__ */ import_react4.default.createElement("p", { className: "text-[12px] text-zinc-500 mt-1 font-mono truncate" }, stripProjectRoot(obs.subtitle)), detail && /* @__PURE__ */ import_react4.default.createElement("p", { className: "text-[11px] text-zinc-600 mt-1.5" }, detail), /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex items-center gap-2 mt-2.5" }, obs.concepts && /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex flex-wrap gap-1" }, obs.concepts.split(", ").map((concept, i) => {
|
|
24133
|
+
const colorClass = CONCEPT_COLORS[concept.trim()] || "bg-zinc-500/15 text-zinc-400 ring-zinc-500/25";
|
|
24134
|
+
return /* @__PURE__ */ import_react4.default.createElement("span", { key: i, className: `text-[10px] font-medium px-1.5 py-0.5 rounded ring-1 ${colorClass}` }, concept.trim());
|
|
24135
|
+
})), /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-[10px] text-zinc-700 font-mono ml-auto tabular-nums" }, "#", obs.id))));
|
|
23932
24136
|
}
|
|
23933
24137
|
function SummaryCard({ summary, getDisplayName }) {
|
|
23934
24138
|
const sections = [
|
|
23935
24139
|
{ label: "Investigated", value: summary.investigated, color: "text-blue-400" },
|
|
23936
24140
|
{ label: "Learned", value: summary.learned, color: "text-emerald-400" },
|
|
23937
24141
|
{ label: "Completed", value: summary.completed, color: "text-violet-400" },
|
|
23938
|
-
{ label: "Next
|
|
24142
|
+
{ label: "Next Steps", value: summary.next_steps, color: "text-amber-400" },
|
|
23939
24143
|
{ label: "Notes", value: summary.notes, color: "text-zinc-400" }
|
|
23940
24144
|
].filter((s) => s.value);
|
|
23941
|
-
return /* @__PURE__ */ import_react4.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg border-l-[3px] border-l-cyan-500 shadow-card hover:shadow-card-hover hover:border-border-hover transition-all" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pt-4 pb-2" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-md bg-cyan-500/10 text-cyan-400" }, /* @__PURE__ */ import_react4.default.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" }), /* @__PURE__ */ import_react4.default.createElement("path", { d: "M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" })), "Summary"), /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center text-[11px] font-medium px-2 py-0.5 rounded-md bg-accent-violet/10 text-accent-violet" }, getDisplayName(summary.project)), /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-
|
|
24145
|
+
return /* @__PURE__ */ import_react4.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg border-l-[3px] border-l-cyan-500 shadow-card hover:shadow-card-hover hover:border-border-hover transition-all" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pt-4 pb-2" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-md bg-cyan-500/10 text-cyan-400" }, /* @__PURE__ */ import_react4.default.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" }), /* @__PURE__ */ import_react4.default.createElement("path", { d: "M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" })), "Session Summary"), /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center text-[11px] font-medium px-2 py-0.5 rounded-md bg-accent-violet/10 text-accent-violet" }, getDisplayName(summary.project)), /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-[11px] text-zinc-500 font-mono ml-auto" }, timeAgo(summary.created_at_epoch))), summary.request && /* @__PURE__ */ import_react4.default.createElement("h3", { className: "text-[15px] font-semibold text-zinc-100 leading-snug" }, summary.request)), /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pb-4 space-y-2" }, sections.map(({ label, value, color }) => /* @__PURE__ */ import_react4.default.createElement("div", { key: label, className: "p-3 rounded-md bg-surface-2 border border-border" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: `block text-[10px] font-semibold uppercase tracking-wider mb-1 ${color}` }, label), /* @__PURE__ */ import_react4.default.createElement("p", { className: "text-xs text-zinc-400 leading-relaxed" }, value)))));
|
|
23942
24146
|
}
|
|
23943
24147
|
function PromptCard({ prompt, getDisplayName }) {
|
|
23944
|
-
return /* @__PURE__ */ import_react4.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg border-l-[3px] border-l-rose-500 shadow-card hover:shadow-card-hover hover:border-border-hover transition-all" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pt-4 pb-2" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-md bg-rose-500/10 text-rose-400" }, /* @__PURE__ */ import_react4.default.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })), "Prompt #", prompt.prompt_number), /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center text-[11px] font-medium px-2 py-0.5 rounded-md bg-accent-violet/10 text-accent-violet" }, getDisplayName(prompt.project)), /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-
|
|
24148
|
+
return /* @__PURE__ */ import_react4.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg border-l-[3px] border-l-rose-500 shadow-card hover:shadow-card-hover hover:border-border-hover transition-all" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pt-4 pb-2" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-md bg-rose-500/10 text-rose-400" }, /* @__PURE__ */ import_react4.default.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })), "Prompt #", prompt.prompt_number), /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center text-[11px] font-medium px-2 py-0.5 rounded-md bg-accent-violet/10 text-accent-violet" }, getDisplayName(prompt.project)), /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-[11px] text-zinc-500 font-mono ml-auto" }, timeAgo(prompt.created_at_epoch)))), /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pb-4" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "p-3 rounded-md bg-surface-0 border border-border text-sm text-zinc-300 leading-relaxed whitespace-pre-wrap break-words" }, prompt.prompt_text)));
|
|
23945
24149
|
}
|
|
23946
24150
|
|
|
23947
24151
|
// src/ui/viewer/components/Analytics.tsx
|
|
@@ -23949,6 +24153,7 @@ var import_react6 = __toESM(require_react(), 1);
|
|
|
23949
24153
|
|
|
23950
24154
|
// src/ui/viewer/hooks/useAnalytics.ts
|
|
23951
24155
|
var import_react5 = __toESM(require_react(), 1);
|
|
24156
|
+
var POLL_INTERVAL = 3e4;
|
|
23952
24157
|
function useAnalytics(project) {
|
|
23953
24158
|
const [data, setData] = (0, import_react5.useState)({
|
|
23954
24159
|
overview: null,
|
|
@@ -23958,7 +24163,6 @@ function useAnalytics(project) {
|
|
|
23958
24163
|
isLoading: true
|
|
23959
24164
|
});
|
|
23960
24165
|
const mountedRef = (0, import_react5.useRef)(true);
|
|
23961
|
-
const debounceRef = (0, import_react5.useRef)(null);
|
|
23962
24166
|
const fetchAnalytics = (0, import_react5.useCallback)(async () => {
|
|
23963
24167
|
if (!mountedRef.current) return;
|
|
23964
24168
|
const params = project ? `?project=${encodeURIComponent(project)}` : "";
|
|
@@ -23982,30 +24186,18 @@ function useAnalytics(project) {
|
|
|
23982
24186
|
}
|
|
23983
24187
|
}
|
|
23984
24188
|
}, [project]);
|
|
23985
|
-
const debouncedRefresh = (0, import_react5.useCallback)(() => {
|
|
23986
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
23987
|
-
debounceRef.current = setTimeout(() => {
|
|
23988
|
-
fetchAnalytics();
|
|
23989
|
-
}, 2e3);
|
|
23990
|
-
}, [fetchAnalytics]);
|
|
23991
24189
|
(0, import_react5.useEffect)(() => {
|
|
23992
24190
|
mountedRef.current = true;
|
|
23993
24191
|
setData((prev) => ({ ...prev, isLoading: true }));
|
|
23994
24192
|
fetchAnalytics();
|
|
23995
|
-
const
|
|
23996
|
-
|
|
23997
|
-
|
|
23998
|
-
eventSource.addEventListener("summary-created", onUpdate);
|
|
23999
|
-
eventSource.addEventListener("session-created", onUpdate);
|
|
24193
|
+
const interval = setInterval(() => {
|
|
24194
|
+
if (mountedRef.current) fetchAnalytics();
|
|
24195
|
+
}, POLL_INTERVAL);
|
|
24000
24196
|
return () => {
|
|
24001
24197
|
mountedRef.current = false;
|
|
24002
|
-
|
|
24003
|
-
eventSource.removeEventListener("observation-created", onUpdate);
|
|
24004
|
-
eventSource.removeEventListener("summary-created", onUpdate);
|
|
24005
|
-
eventSource.removeEventListener("session-created", onUpdate);
|
|
24006
|
-
eventSource.close();
|
|
24198
|
+
clearInterval(interval);
|
|
24007
24199
|
};
|
|
24008
|
-
}, [fetchAnalytics
|
|
24200
|
+
}, [fetchAnalytics]);
|
|
24009
24201
|
return data;
|
|
24010
24202
|
}
|
|
24011
24203
|
|
|
@@ -24072,7 +24264,7 @@ function Analytics({ currentFilter, getDisplayName }) {
|
|
|
24072
24264
|
value: sessionStats ? `${sessionStats.total > 0 ? Math.round(sessionStats.completed / sessionStats.total * 100) : 0}%` : "\u2014",
|
|
24073
24265
|
color: "text-accent-green"
|
|
24074
24266
|
}
|
|
24075
|
-
)), timeline.length > 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Activity Timeline (30 days)"), /* @__PURE__ */ import_react6.default.createElement(TimelineChart, { entries: timeline })), typeDistribution.length > 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Observation Types"), /* @__PURE__ */ import_react6.default.createElement(TypeDistributionChart, { entries: typeDistribution })), sessionStats && sessionStats.total > 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Sessions"), /* @__PURE__ */ import_react6.default.createElement(SessionStatsPanel, { stats: sessionStats })));
|
|
24267
|
+
)), overview.tokenEconomics && overview.tokenEconomics.discoveryTokens > 0 && /* @__PURE__ */ import_react6.default.createElement(TokenEconomicsPanel, { economics: overview.tokenEconomics }), timeline.length > 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Activity Timeline (30 days)"), /* @__PURE__ */ import_react6.default.createElement(TimelineChart, { entries: timeline })), typeDistribution.length > 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Observation Types"), /* @__PURE__ */ import_react6.default.createElement(TypeDistributionChart, { entries: typeDistribution })), sessionStats && sessionStats.total > 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Sessions"), /* @__PURE__ */ import_react6.default.createElement(SessionStatsPanel, { stats: sessionStats })));
|
|
24076
24268
|
}
|
|
24077
24269
|
function StatCard({ label, value, sub, color }) {
|
|
24078
24270
|
return /* @__PURE__ */ import_react6.default.createElement("div", { className: "rounded-lg bg-surface-1 border border-border px-4 py-4" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: `text-2xl font-bold tabular-nums ${color}` }, value.toLocaleString()), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mt-1" }, label), sub && /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[10px] text-zinc-600 mt-0.5" }, sub));
|
|
@@ -24083,80 +24275,31 @@ function MiniStat({ label, value, color }) {
|
|
|
24083
24275
|
function TimelineChart({ entries }) {
|
|
24084
24276
|
const [hoveredIndex, setHoveredIndex] = (0, import_react6.useState)(null);
|
|
24085
24277
|
const maxCount = Math.max(...entries.map((e) => e.count), 1);
|
|
24086
|
-
const barWidth = Math.max(4, Math.min(16, Math.floor(600 / entries.length) - 2));
|
|
24087
24278
|
const chartHeight = 120;
|
|
24088
|
-
const
|
|
24089
|
-
return /* @__PURE__ */ import_react6.default.createElement("div", { className: "
|
|
24090
|
-
|
|
24091
|
-
|
|
24092
|
-
|
|
24093
|
-
|
|
24094
|
-
|
|
24095
|
-
|
|
24096
|
-
|
|
24097
|
-
|
|
24098
|
-
|
|
24099
|
-
|
|
24100
|
-
|
|
24101
|
-
|
|
24102
|
-
|
|
24103
|
-
return /* @__PURE__ */ import_react6.default.createElement(
|
|
24104
|
-
"g",
|
|
24105
|
-
{
|
|
24106
|
-
key: entry.day,
|
|
24107
|
-
onMouseEnter: () => setHoveredIndex(i),
|
|
24108
|
-
onMouseLeave: () => setHoveredIndex(null)
|
|
24109
|
-
},
|
|
24110
|
-
/* @__PURE__ */ import_react6.default.createElement(
|
|
24111
|
-
"rect",
|
|
24112
|
-
{
|
|
24113
|
-
x,
|
|
24114
|
-
y,
|
|
24115
|
-
width: barWidth,
|
|
24116
|
-
height: barHeight,
|
|
24117
|
-
rx: 2,
|
|
24118
|
-
className: `transition-all ${isHovered ? "fill-accent-violet" : "fill-accent-violet/60"}`
|
|
24119
|
-
}
|
|
24120
|
-
),
|
|
24121
|
-
isHovered && /* @__PURE__ */ import_react6.default.createElement("g", null, /* @__PURE__ */ import_react6.default.createElement(
|
|
24122
|
-
"rect",
|
|
24123
|
-
{
|
|
24124
|
-
x: Math.max(0, x - 30),
|
|
24125
|
-
y: Math.max(0, y - 28),
|
|
24126
|
-
width: 70,
|
|
24127
|
-
height: 22,
|
|
24128
|
-
rx: 4,
|
|
24129
|
-
className: "fill-surface-3"
|
|
24130
|
-
}
|
|
24131
|
-
), /* @__PURE__ */ import_react6.default.createElement(
|
|
24132
|
-
"text",
|
|
24133
|
-
{
|
|
24134
|
-
x: Math.max(35, x + barWidth / 2),
|
|
24135
|
-
y: Math.max(15, y - 13),
|
|
24136
|
-
textAnchor: "middle",
|
|
24137
|
-
className: "fill-zinc-200 text-[10px] font-mono"
|
|
24138
|
-
},
|
|
24139
|
-
entry.count,
|
|
24140
|
-
" \xB7 ",
|
|
24141
|
-
entry.day.slice(5)
|
|
24142
|
-
))
|
|
24143
|
-
);
|
|
24144
|
-
}),
|
|
24145
|
-
entries.map((entry, i) => {
|
|
24146
|
-
if (i % Math.max(1, Math.floor(entries.length / 6)) !== 0) return null;
|
|
24147
|
-
return /* @__PURE__ */ import_react6.default.createElement(
|
|
24148
|
-
"text",
|
|
24279
|
+
const labelInterval = Math.max(1, Math.floor(entries.length / 6));
|
|
24280
|
+
return /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "h-5 text-center" }, hoveredIndex !== null && entries[hoveredIndex] && /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-[11px] font-mono text-zinc-300 bg-surface-3 px-2.5 py-1 rounded" }, entries[hoveredIndex].day.slice(5), " \u2014 ", /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-accent-violet font-semibold" }, entries[hoveredIndex].count), " observations")), /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-end gap-[2px]", style: { height: chartHeight } }, entries.map((entry, i) => {
|
|
24281
|
+
const heightPct = Math.max(1.5, entry.count / maxCount * 100);
|
|
24282
|
+
const isHovered = hoveredIndex === i;
|
|
24283
|
+
return /* @__PURE__ */ import_react6.default.createElement(
|
|
24284
|
+
"div",
|
|
24285
|
+
{
|
|
24286
|
+
key: entry.day,
|
|
24287
|
+
className: "flex-1 min-w-0 cursor-pointer transition-all duration-150",
|
|
24288
|
+
style: { height: `${heightPct}%` },
|
|
24289
|
+
onMouseEnter: () => setHoveredIndex(i),
|
|
24290
|
+
onMouseLeave: () => setHoveredIndex(null)
|
|
24291
|
+
},
|
|
24292
|
+
/* @__PURE__ */ import_react6.default.createElement(
|
|
24293
|
+
"div",
|
|
24149
24294
|
{
|
|
24150
|
-
|
|
24151
|
-
|
|
24152
|
-
|
|
24153
|
-
|
|
24154
|
-
|
|
24155
|
-
|
|
24156
|
-
|
|
24157
|
-
|
|
24158
|
-
})
|
|
24159
|
-
));
|
|
24295
|
+
className: `w-full h-full rounded-t transition-colors ${isHovered ? "bg-accent-violet" : "bg-accent-violet/50"}`
|
|
24296
|
+
}
|
|
24297
|
+
)
|
|
24298
|
+
);
|
|
24299
|
+
})), /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex" }, entries.map((entry, i) => {
|
|
24300
|
+
const showLabel = i % labelInterval === 0 || i === entries.length - 1;
|
|
24301
|
+
return /* @__PURE__ */ import_react6.default.createElement("div", { key: `label-${i}`, className: "flex-1 min-w-0 text-center" }, showLabel && /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-[10px] font-mono text-zinc-600" }, entry.day.slice(5)));
|
|
24302
|
+
})));
|
|
24160
24303
|
}
|
|
24161
24304
|
function TypeDistributionChart({ entries }) {
|
|
24162
24305
|
const total = entries.reduce((sum, e) => sum + e.count, 0);
|
|
@@ -24184,29 +24327,112 @@ function SessionStatsPanel({ stats }) {
|
|
|
24184
24327
|
}
|
|
24185
24328
|
)), /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-center justify-between mt-1" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-[10px] text-zinc-600" }, stats.completed, " completed"), /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-[10px] text-zinc-600" }, stats.total, " total"))), /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-center gap-3 p-3 rounded-lg bg-surface-2 border border-border" }, /* @__PURE__ */ import_react6.default.createElement("svg", { className: "w-4 h-4 text-accent-cyan flex-shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react6.default.createElement("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ import_react6.default.createElement("polyline", { points: "12 6 12 12 16 14" })), /* @__PURE__ */ import_react6.default.createElement("div", null, /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-sm font-semibold text-zinc-200" }, formatDuration(stats.avgDurationMinutes)), /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-xs text-zinc-500 ml-2" }, "avg session duration"))));
|
|
24186
24329
|
}
|
|
24187
|
-
function
|
|
24188
|
-
|
|
24189
|
-
|
|
24190
|
-
|
|
24191
|
-
|
|
24192
|
-
|
|
24330
|
+
function TokenEconomicsPanel({ economics }) {
|
|
24331
|
+
const { discoveryTokens, readTokens, savings, reductionPct } = economics;
|
|
24332
|
+
return /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Token Economics"), /* @__PURE__ */ import_react6.default.createElement("div", { className: "grid grid-cols-3 gap-3 mb-4" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "rounded-lg bg-surface-2 border border-border px-4 py-3 text-center" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-lg font-bold text-amber-400 tabular-nums" }, formatTokenCount(discoveryTokens)), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[10px] uppercase tracking-wider text-zinc-600 mt-0.5" }, "Discovery"), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[9px] text-zinc-700 mt-0.5" }, "tokens spent")), /* @__PURE__ */ import_react6.default.createElement("div", { className: "rounded-lg bg-surface-2 border border-border px-4 py-3 text-center" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-lg font-bold text-cyan-400 tabular-nums" }, formatTokenCount(readTokens)), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[10px] uppercase tracking-wider text-zinc-600 mt-0.5" }, "Read Cost"), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[9px] text-zinc-700 mt-0.5" }, "to reuse context")), /* @__PURE__ */ import_react6.default.createElement("div", { className: "rounded-lg bg-surface-2 border border-border px-4 py-3 text-center" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-lg font-bold text-emerald-400 tabular-nums" }, formatTokenCount(savings)), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[10px] uppercase tracking-wider text-zinc-600 mt-0.5" }, "Savings"), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[9px] text-zinc-700 mt-0.5" }, "tokens saved"))), /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-center justify-between text-[10px] text-zinc-500" }, /* @__PURE__ */ import_react6.default.createElement("span", null, "Read cost vs Discovery cost"), /* @__PURE__ */ import_react6.default.createElement("span", { className: "font-bold text-emerald-400" }, reductionPct, "% reduction")), /* @__PURE__ */ import_react6.default.createElement("div", { className: "h-3 bg-surface-3 rounded-full overflow-hidden flex" }, /* @__PURE__ */ import_react6.default.createElement(
|
|
24333
|
+
"div",
|
|
24334
|
+
{
|
|
24335
|
+
className: "h-full bg-gradient-to-r from-cyan-500 to-cyan-400 rounded-l-full transition-all",
|
|
24336
|
+
style: { width: `${Math.min(100, discoveryTokens > 0 ? Math.round(readTokens / discoveryTokens * 100) : 0)}%` }
|
|
24337
|
+
}
|
|
24338
|
+
), /* @__PURE__ */ import_react6.default.createElement(
|
|
24339
|
+
"div",
|
|
24340
|
+
{
|
|
24341
|
+
className: "h-full bg-gradient-to-r from-emerald-500/40 to-emerald-400/40 rounded-r-full transition-all flex-1"
|
|
24342
|
+
}
|
|
24343
|
+
)), /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-center justify-between text-[9px] text-zinc-700" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "w-2 h-2 rounded-full bg-cyan-500 inline-block" }), "Read: ", formatTokenCount(readTokens)), /* @__PURE__ */ import_react6.default.createElement("span", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "w-2 h-2 rounded-full bg-emerald-500/40 inline-block" }), "Saved: ", formatTokenCount(savings)))));
|
|
24193
24344
|
}
|
|
24194
24345
|
|
|
24195
|
-
// src/ui/viewer/
|
|
24346
|
+
// src/ui/viewer/components/Sessions.tsx
|
|
24196
24347
|
var import_react7 = __toESM(require_react(), 1);
|
|
24348
|
+
var STATUS_STYLES = {
|
|
24349
|
+
active: { dot: "bg-accent-green animate-pulse-dot", text: "text-accent-green", label: "Active" },
|
|
24350
|
+
completed: { dot: "bg-accent-blue", text: "text-accent-blue", label: "Completed" },
|
|
24351
|
+
failed: { dot: "bg-accent-rose", text: "text-accent-rose", label: "Failed" }
|
|
24352
|
+
};
|
|
24353
|
+
function Sessions({ currentFilter, getDisplayName }) {
|
|
24354
|
+
const [sessions, setSessions] = (0, import_react7.useState)([]);
|
|
24355
|
+
const [isLoading, setIsLoading] = (0, import_react7.useState)(true);
|
|
24356
|
+
const [expandedId, setExpandedId] = (0, import_react7.useState)(null);
|
|
24357
|
+
(0, import_react7.useEffect)(() => {
|
|
24358
|
+
setIsLoading(true);
|
|
24359
|
+
const params = currentFilter ? `?project=${encodeURIComponent(currentFilter)}` : "";
|
|
24360
|
+
fetch(`/api/sessions${params}`).then((r) => r.ok ? r.json() : []).then((data) => setSessions(Array.isArray(data) ? data : [])).catch(() => setSessions([])).finally(() => setIsLoading(false));
|
|
24361
|
+
}, [currentFilter]);
|
|
24362
|
+
if (isLoading) {
|
|
24363
|
+
return /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex flex-col items-center justify-center py-20" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "w-6 h-6 border-2 border-accent-violet/30 border-t-accent-violet rounded-full animate-spin mb-4" }), /* @__PURE__ */ import_react7.default.createElement("p", { className: "text-sm text-zinc-500" }, "Loading sessions..."));
|
|
24364
|
+
}
|
|
24365
|
+
if (sessions.length === 0) {
|
|
24366
|
+
return /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex flex-col items-center justify-center py-20 text-center" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "w-16 h-16 rounded-2xl bg-surface-2 border border-border flex items-center justify-center mb-5" }, /* @__PURE__ */ import_react7.default.createElement("svg", { className: "w-7 h-7 text-zinc-600", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }, /* @__PURE__ */ import_react7.default.createElement("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ import_react7.default.createElement("polyline", { points: "12 6 12 12 16 14" }))), /* @__PURE__ */ import_react7.default.createElement("p", { className: "text-base font-semibold text-zinc-300 mb-2" }, "No sessions found"), /* @__PURE__ */ import_react7.default.createElement("p", { className: "text-sm text-zinc-500 max-w-xs leading-relaxed" }, "Start a coding session to see it tracked here."));
|
|
24367
|
+
}
|
|
24368
|
+
const total = sessions.length;
|
|
24369
|
+
const completed = sessions.filter((s) => s.status === "completed").length;
|
|
24370
|
+
const active = sessions.filter((s) => s.status === "active").length;
|
|
24371
|
+
const avgDuration = (() => {
|
|
24372
|
+
const completedSessions = sessions.filter((s) => s.completed_at_epoch && s.started_at_epoch);
|
|
24373
|
+
if (completedSessions.length === 0) return 0;
|
|
24374
|
+
const totalMs = completedSessions.reduce((sum, s) => sum + ((s.completed_at_epoch || 0) - s.started_at_epoch), 0);
|
|
24375
|
+
return totalMs / completedSessions.length / 6e4;
|
|
24376
|
+
})();
|
|
24377
|
+
return /* @__PURE__ */ import_react7.default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "grid grid-cols-2 lg:grid-cols-4 gap-3" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "rounded-lg bg-surface-1 border border-border px-4 py-4" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-2xl font-bold tabular-nums text-accent-violet" }, total), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mt-1" }, "Total")), /* @__PURE__ */ import_react7.default.createElement("div", { className: "rounded-lg bg-surface-1 border border-border px-4 py-4" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-2xl font-bold tabular-nums text-accent-green" }, active), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mt-1" }, "Active")), /* @__PURE__ */ import_react7.default.createElement("div", { className: "rounded-lg bg-surface-1 border border-border px-4 py-4" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-2xl font-bold tabular-nums text-accent-blue" }, completed), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mt-1" }, "Completed")), /* @__PURE__ */ import_react7.default.createElement("div", { className: "rounded-lg bg-surface-1 border border-border px-4 py-4" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-2xl font-bold tabular-nums text-accent-cyan" }, formatDuration(avgDuration)), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mt-1" }, "Avg Duration"))), /* @__PURE__ */ import_react7.default.createElement("div", { className: "space-y-2" }, sessions.map((session) => {
|
|
24378
|
+
const style = STATUS_STYLES[session.status] || STATUS_STYLES.active;
|
|
24379
|
+
const isExpanded = expandedId === session.id;
|
|
24380
|
+
const duration = session.completed_at_epoch ? (session.completed_at_epoch - session.started_at_epoch) / 6e4 : (Date.now() - session.started_at_epoch) / 6e4;
|
|
24381
|
+
return /* @__PURE__ */ import_react7.default.createElement(
|
|
24382
|
+
"div",
|
|
24383
|
+
{
|
|
24384
|
+
key: session.id,
|
|
24385
|
+
className: "bg-surface-1 border border-border rounded-lg overflow-hidden transition-all hover:border-border-hover"
|
|
24386
|
+
},
|
|
24387
|
+
/* @__PURE__ */ import_react7.default.createElement(
|
|
24388
|
+
"button",
|
|
24389
|
+
{
|
|
24390
|
+
onClick: () => setExpandedId(isExpanded ? null : session.id),
|
|
24391
|
+
className: "w-full flex items-center gap-3 px-4 py-3 text-left",
|
|
24392
|
+
"aria-expanded": isExpanded
|
|
24393
|
+
},
|
|
24394
|
+
/* @__PURE__ */ import_react7.default.createElement("div", { className: `w-2.5 h-2.5 rounded-full flex-shrink-0 ${style.dot}` }),
|
|
24395
|
+
/* @__PURE__ */ import_react7.default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-sm font-medium text-zinc-200 truncate" }, session.user_prompt || "Session"), /* @__PURE__ */ import_react7.default.createElement("span", { className: `text-[10px] font-medium px-1.5 py-0.5 rounded ${style.text} bg-surface-3` }, style.label)), /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex items-center gap-3 mt-1" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-[11px] text-zinc-500" }, getDisplayName(session.project)), /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-[10px] text-zinc-600 font-mono" }, timeAgo(session.started_at_epoch)), /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-[10px] text-zinc-600 font-mono" }, formatDuration(duration)))),
|
|
24396
|
+
/* @__PURE__ */ import_react7.default.createElement(
|
|
24397
|
+
"svg",
|
|
24398
|
+
{
|
|
24399
|
+
className: `w-4 h-4 text-zinc-600 transition-transform ${isExpanded ? "rotate-180" : ""}`,
|
|
24400
|
+
viewBox: "0 0 24 24",
|
|
24401
|
+
fill: "none",
|
|
24402
|
+
stroke: "currentColor",
|
|
24403
|
+
strokeWidth: "2"
|
|
24404
|
+
},
|
|
24405
|
+
/* @__PURE__ */ import_react7.default.createElement("path", { d: "m6 9 6 6 6-6" })
|
|
24406
|
+
)
|
|
24407
|
+
),
|
|
24408
|
+
isExpanded && /* @__PURE__ */ import_react7.default.createElement("div", { className: "px-4 pb-3 pt-0 border-t border-border space-y-2 animate-fade-in" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "grid grid-cols-2 gap-3 text-xs" }, /* @__PURE__ */ import_react7.default.createElement("div", null, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-zinc-600" }, "Session ID"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-zinc-400 font-mono text-[11px] truncate" }, session.content_session_id)), /* @__PURE__ */ import_react7.default.createElement("div", null, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-zinc-600" }, "Memory ID"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-zinc-400 font-mono text-[11px] truncate" }, session.memory_session_id || "\u2014")), /* @__PURE__ */ import_react7.default.createElement("div", null, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-zinc-600" }, "Started"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-zinc-400 font-mono text-[11px]" }, new Date(session.started_at_epoch).toLocaleString())), /* @__PURE__ */ import_react7.default.createElement("div", null, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-zinc-600" }, "Completed"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-zinc-400 font-mono text-[11px]" }, session.completed_at_epoch ? new Date(session.completed_at_epoch).toLocaleString() : "\u2014"))), session.user_prompt && /* @__PURE__ */ import_react7.default.createElement("div", null, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-[11px] text-zinc-600" }, "Prompt"), /* @__PURE__ */ import_react7.default.createElement("p", { className: "text-xs text-zinc-400 mt-0.5 leading-relaxed" }, session.user_prompt)))
|
|
24409
|
+
);
|
|
24410
|
+
})));
|
|
24411
|
+
}
|
|
24412
|
+
|
|
24413
|
+
// src/ui/viewer/hooks/useSSE.ts
|
|
24414
|
+
var import_react8 = __toESM(require_react(), 1);
|
|
24415
|
+
var POLL_INTERVAL2 = 3e4;
|
|
24197
24416
|
function useSSE() {
|
|
24198
|
-
const [state, setState] = (0,
|
|
24417
|
+
const [state, setState] = (0, import_react8.useState)({
|
|
24199
24418
|
observations: [],
|
|
24200
24419
|
summaries: [],
|
|
24201
24420
|
prompts: [],
|
|
24202
24421
|
projects: [],
|
|
24203
|
-
isConnected: false
|
|
24422
|
+
isConnected: false,
|
|
24423
|
+
lastEventTime: 0
|
|
24204
24424
|
});
|
|
24205
|
-
const mountedRef = (0,
|
|
24206
|
-
(0,
|
|
24425
|
+
const mountedRef = (0, import_react8.useRef)(true);
|
|
24426
|
+
const touchLastEvent = (0, import_react8.useCallback)(() => {
|
|
24427
|
+
if (mountedRef.current) {
|
|
24428
|
+
setState((prev) => ({ ...prev, lastEventTime: Date.now() }));
|
|
24429
|
+
}
|
|
24430
|
+
}, []);
|
|
24431
|
+
(0, import_react8.useEffect)(() => {
|
|
24207
24432
|
mountedRef.current = true;
|
|
24208
24433
|
let eventSource = null;
|
|
24209
24434
|
let retryTimeout = null;
|
|
24435
|
+
let pollInterval = null;
|
|
24210
24436
|
let retryCount = 0;
|
|
24211
24437
|
const MAX_RETRY_DELAY = 3e4;
|
|
24212
24438
|
const fetchObservations = async () => {
|
|
@@ -24214,7 +24440,7 @@ function useSSE() {
|
|
|
24214
24440
|
const res = await fetch("/api/observations?limit=50");
|
|
24215
24441
|
if (res.ok && mountedRef.current) {
|
|
24216
24442
|
const observations = await res.json();
|
|
24217
|
-
setState((prev) => ({ ...prev, observations }));
|
|
24443
|
+
setState((prev) => ({ ...prev, observations, lastEventTime: Date.now() }));
|
|
24218
24444
|
}
|
|
24219
24445
|
} catch (err) {
|
|
24220
24446
|
console.error("Failed to fetch observations:", err);
|
|
@@ -24269,6 +24495,15 @@ function useSSE() {
|
|
|
24269
24495
|
const onPrompt = () => {
|
|
24270
24496
|
fetchPrompts();
|
|
24271
24497
|
};
|
|
24498
|
+
const onSession = () => {
|
|
24499
|
+
fetchProjects();
|
|
24500
|
+
};
|
|
24501
|
+
const startPolling = () => {
|
|
24502
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
24503
|
+
pollInterval = setInterval(() => {
|
|
24504
|
+
if (mountedRef.current) fetchAll();
|
|
24505
|
+
}, POLL_INTERVAL2);
|
|
24506
|
+
};
|
|
24272
24507
|
let wasConnected = false;
|
|
24273
24508
|
const connect = () => {
|
|
24274
24509
|
if (!mountedRef.current) return;
|
|
@@ -24280,7 +24515,7 @@ function useSSE() {
|
|
|
24280
24515
|
}
|
|
24281
24516
|
wasConnected = true;
|
|
24282
24517
|
retryCount = 0;
|
|
24283
|
-
setState((prev) => ({ ...prev, isConnected: true }));
|
|
24518
|
+
setState((prev) => ({ ...prev, isConnected: true, lastEventTime: Date.now() }));
|
|
24284
24519
|
};
|
|
24285
24520
|
eventSource.onerror = () => {
|
|
24286
24521
|
if (!mountedRef.current) return;
|
|
@@ -24289,6 +24524,7 @@ function useSSE() {
|
|
|
24289
24524
|
eventSource.removeEventListener("observation-created", onObservation);
|
|
24290
24525
|
eventSource.removeEventListener("summary-created", onSummary);
|
|
24291
24526
|
eventSource.removeEventListener("prompt-created", onPrompt);
|
|
24527
|
+
eventSource.removeEventListener("session-created", onSession);
|
|
24292
24528
|
eventSource.close();
|
|
24293
24529
|
}
|
|
24294
24530
|
eventSource = null;
|
|
@@ -24299,35 +24535,39 @@ function useSSE() {
|
|
|
24299
24535
|
eventSource.addEventListener("observation-created", onObservation);
|
|
24300
24536
|
eventSource.addEventListener("summary-created", onSummary);
|
|
24301
24537
|
eventSource.addEventListener("prompt-created", onPrompt);
|
|
24538
|
+
eventSource.addEventListener("session-created", onSession);
|
|
24302
24539
|
};
|
|
24303
24540
|
fetchAll();
|
|
24304
24541
|
connect();
|
|
24542
|
+
startPolling();
|
|
24305
24543
|
return () => {
|
|
24306
24544
|
mountedRef.current = false;
|
|
24307
24545
|
if (eventSource) {
|
|
24308
24546
|
eventSource.removeEventListener("observation-created", onObservation);
|
|
24309
24547
|
eventSource.removeEventListener("summary-created", onSummary);
|
|
24310
24548
|
eventSource.removeEventListener("prompt-created", onPrompt);
|
|
24549
|
+
eventSource.removeEventListener("session-created", onSession);
|
|
24311
24550
|
eventSource.close();
|
|
24312
24551
|
}
|
|
24313
24552
|
if (retryTimeout) clearTimeout(retryTimeout);
|
|
24553
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
24314
24554
|
};
|
|
24315
24555
|
}, []);
|
|
24316
24556
|
return state;
|
|
24317
24557
|
}
|
|
24318
24558
|
|
|
24319
24559
|
// src/ui/viewer/hooks/useTheme.ts
|
|
24320
|
-
var
|
|
24560
|
+
var import_react9 = __toESM(require_react(), 1);
|
|
24321
24561
|
function useTheme() {
|
|
24322
|
-
const [preference, setPreference] = (0,
|
|
24323
|
-
const [resolvedTheme, setResolvedTheme] = (0,
|
|
24324
|
-
(0,
|
|
24562
|
+
const [preference, setPreference] = (0, import_react9.useState)("dark");
|
|
24563
|
+
const [resolvedTheme, setResolvedTheme] = (0, import_react9.useState)("dark");
|
|
24564
|
+
(0, import_react9.useEffect)(() => {
|
|
24325
24565
|
const saved = localStorage.getItem("kiro-memory-theme");
|
|
24326
24566
|
if (saved) {
|
|
24327
24567
|
setPreference(saved);
|
|
24328
24568
|
}
|
|
24329
24569
|
}, []);
|
|
24330
|
-
(0,
|
|
24570
|
+
(0, import_react9.useEffect)(() => {
|
|
24331
24571
|
let resolved;
|
|
24332
24572
|
if (preference === "system") {
|
|
24333
24573
|
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
@@ -24342,7 +24582,7 @@ function useTheme() {
|
|
|
24342
24582
|
document.documentElement.classList.remove("dark");
|
|
24343
24583
|
}
|
|
24344
24584
|
}, [preference]);
|
|
24345
|
-
(0,
|
|
24585
|
+
(0, import_react9.useEffect)(() => {
|
|
24346
24586
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
24347
24587
|
const handler = (e) => {
|
|
24348
24588
|
if (preference === "system") {
|
|
@@ -24366,11 +24606,11 @@ function useTheme() {
|
|
|
24366
24606
|
}
|
|
24367
24607
|
|
|
24368
24608
|
// src/ui/viewer/hooks/useProjectAliases.ts
|
|
24369
|
-
var
|
|
24609
|
+
var import_react10 = __toESM(require_react(), 1);
|
|
24370
24610
|
function useProjectAliases() {
|
|
24371
|
-
const [aliases, setAliases] = (0,
|
|
24372
|
-
const [isLoading, setIsLoading] = (0,
|
|
24373
|
-
(0,
|
|
24611
|
+
const [aliases, setAliases] = (0, import_react10.useState)({});
|
|
24612
|
+
const [isLoading, setIsLoading] = (0, import_react10.useState)(false);
|
|
24613
|
+
(0, import_react10.useEffect)(() => {
|
|
24374
24614
|
const fetchAliases = async () => {
|
|
24375
24615
|
try {
|
|
24376
24616
|
const res = await fetch("/api/project-aliases");
|
|
@@ -24384,10 +24624,10 @@ function useProjectAliases() {
|
|
|
24384
24624
|
};
|
|
24385
24625
|
fetchAliases();
|
|
24386
24626
|
}, []);
|
|
24387
|
-
const getDisplayName = (0,
|
|
24627
|
+
const getDisplayName = (0, import_react10.useCallback)((project) => {
|
|
24388
24628
|
return aliases[project] || project;
|
|
24389
24629
|
}, [aliases]);
|
|
24390
|
-
const updateAlias = (0,
|
|
24630
|
+
const updateAlias = (0, import_react10.useCallback)(async (project, displayName) => {
|
|
24391
24631
|
setIsLoading(true);
|
|
24392
24632
|
try {
|
|
24393
24633
|
const res = await fetch(`/api/project-aliases/${encodeURIComponent(project)}`, {
|
|
@@ -24433,39 +24673,50 @@ function mergeAndDeduplicateByProject(liveData, paginatedData) {
|
|
|
24433
24673
|
// src/ui/viewer/App.tsx
|
|
24434
24674
|
var TYPE_FILTERS = ["file-write", "file-read", "command", "research", "delegation", "tool-use"];
|
|
24435
24675
|
function App() {
|
|
24436
|
-
const [currentFilter, setCurrentFilter] = (0,
|
|
24437
|
-
const [currentView, setCurrentView] = (0,
|
|
24438
|
-
const [activeTypes, setActiveTypes] = (0,
|
|
24439
|
-
const [paginatedObservations, setPaginatedObservations] = (0,
|
|
24440
|
-
const [paginatedSummaries, setPaginatedSummaries] = (0,
|
|
24441
|
-
const [paginatedPrompts, setPaginatedPrompts] = (0,
|
|
24442
|
-
const [isLoadingMore, setIsLoadingMore] = (0,
|
|
24443
|
-
const [hasMore, setHasMore] = (0,
|
|
24444
|
-
const
|
|
24445
|
-
const {
|
|
24676
|
+
const [currentFilter, setCurrentFilter] = (0, import_react11.useState)("");
|
|
24677
|
+
const [currentView, setCurrentView] = (0, import_react11.useState)("feed");
|
|
24678
|
+
const [activeTypes, setActiveTypes] = (0, import_react11.useState)(new Set(TYPE_FILTERS));
|
|
24679
|
+
const [paginatedObservations, setPaginatedObservations] = (0, import_react11.useState)([]);
|
|
24680
|
+
const [paginatedSummaries, setPaginatedSummaries] = (0, import_react11.useState)([]);
|
|
24681
|
+
const [paginatedPrompts, setPaginatedPrompts] = (0, import_react11.useState)([]);
|
|
24682
|
+
const [isLoadingMore, setIsLoadingMore] = (0, import_react11.useState)(false);
|
|
24683
|
+
const [hasMore, setHasMore] = (0, import_react11.useState)({ observations: true, summaries: true, prompts: true });
|
|
24684
|
+
const [isMobileMenuOpen, setIsMobileMenuOpen] = (0, import_react11.useState)(false);
|
|
24685
|
+
const { observations, summaries, prompts, projects, isConnected, lastEventTime } = useSSE();
|
|
24686
|
+
const { preference: themePreference, resolvedTheme, setThemePreference } = useTheme();
|
|
24446
24687
|
const { getDisplayName, updateAlias } = useProjectAliases();
|
|
24447
|
-
const allObservations = (0,
|
|
24688
|
+
const allObservations = (0, import_react11.useMemo)(() => {
|
|
24448
24689
|
if (currentFilter) return paginatedObservations;
|
|
24449
24690
|
return mergeAndDeduplicateByProject(observations, paginatedObservations);
|
|
24450
24691
|
}, [observations, paginatedObservations, currentFilter]);
|
|
24451
|
-
const allSummaries = (0,
|
|
24692
|
+
const allSummaries = (0, import_react11.useMemo)(() => {
|
|
24452
24693
|
if (currentFilter) return paginatedSummaries;
|
|
24453
24694
|
return mergeAndDeduplicateByProject(summaries, paginatedSummaries);
|
|
24454
24695
|
}, [summaries, paginatedSummaries, currentFilter]);
|
|
24455
|
-
const allPrompts = (0,
|
|
24696
|
+
const allPrompts = (0, import_react11.useMemo)(() => {
|
|
24456
24697
|
if (currentFilter) return paginatedPrompts;
|
|
24457
24698
|
return mergeAndDeduplicateByProject(prompts, paginatedPrompts);
|
|
24458
24699
|
}, [prompts, paginatedPrompts, currentFilter]);
|
|
24459
|
-
const filteredObservations = (0,
|
|
24700
|
+
const filteredObservations = (0, import_react11.useMemo)(
|
|
24460
24701
|
() => allObservations.filter((o) => activeTypes.has(o.type)),
|
|
24461
24702
|
[allObservations, activeTypes]
|
|
24462
24703
|
);
|
|
24463
|
-
const stats = (0,
|
|
24464
|
-
|
|
24465
|
-
|
|
24466
|
-
|
|
24467
|
-
|
|
24468
|
-
|
|
24704
|
+
const [stats, setStats] = (0, import_react11.useState)({ observations: 0, summaries: 0, prompts: 0, tokenEconomics: { discoveryTokens: 0, readTokens: 0, savings: 0 } });
|
|
24705
|
+
(0, import_react11.useEffect)(() => {
|
|
24706
|
+
const params = currentFilter ? `?project=${encodeURIComponent(currentFilter)}` : "";
|
|
24707
|
+
fetch(`/api/analytics/overview${params}`).then((r) => r.ok ? r.json() : null).then((data) => {
|
|
24708
|
+
if (data) {
|
|
24709
|
+
setStats({
|
|
24710
|
+
observations: data.observations || 0,
|
|
24711
|
+
summaries: data.summaries || 0,
|
|
24712
|
+
prompts: data.prompts || 0,
|
|
24713
|
+
tokenEconomics: data.tokenEconomics || { discoveryTokens: 0, readTokens: 0, savings: 0 }
|
|
24714
|
+
});
|
|
24715
|
+
}
|
|
24716
|
+
}).catch(() => {
|
|
24717
|
+
});
|
|
24718
|
+
}, [currentFilter, allObservations.length]);
|
|
24719
|
+
const toggleType = (0, import_react11.useCallback)((type) => {
|
|
24469
24720
|
setActiveTypes((prev) => {
|
|
24470
24721
|
const next = new Set(prev);
|
|
24471
24722
|
if (next.has(type)) next.delete(type);
|
|
@@ -24473,7 +24724,7 @@ function App() {
|
|
|
24473
24724
|
return next;
|
|
24474
24725
|
});
|
|
24475
24726
|
}, []);
|
|
24476
|
-
const fetchForProject = (0,
|
|
24727
|
+
const fetchForProject = (0, import_react11.useCallback)(async (project) => {
|
|
24477
24728
|
setIsLoadingMore(true);
|
|
24478
24729
|
try {
|
|
24479
24730
|
const params = new URLSearchParams({
|
|
@@ -24495,63 +24746,64 @@ function App() {
|
|
|
24495
24746
|
setIsLoadingMore(false);
|
|
24496
24747
|
}
|
|
24497
24748
|
}, []);
|
|
24498
|
-
const handleLoadMore = (0,
|
|
24749
|
+
const handleLoadMore = (0, import_react11.useCallback)(async () => {
|
|
24499
24750
|
if (isLoadingMore) return;
|
|
24751
|
+
if (!hasMore.observations && !hasMore.summaries && !hasMore.prompts) return;
|
|
24500
24752
|
setIsLoadingMore(true);
|
|
24501
24753
|
try {
|
|
24502
|
-
const
|
|
24503
|
-
const
|
|
24504
|
-
|
|
24505
|
-
limit:
|
|
24506
|
-
|
|
24507
|
-
|
|
24508
|
-
const [obsRes, sumRes, promptRes] = await Promise.all([
|
|
24509
|
-
fetch(`/api/observations?${params}`),
|
|
24510
|
-
fetch(`/api/summaries?${params}`),
|
|
24511
|
-
fetch(`/api/prompts?${params}`)
|
|
24754
|
+
const limit = "20";
|
|
24755
|
+
const projectParam = currentFilter ? `&project=${encodeURIComponent(currentFilter)}` : "";
|
|
24756
|
+
const fetches = await Promise.all([
|
|
24757
|
+
hasMore.observations ? fetch(`/api/observations?offset=${paginatedObservations.length}&limit=${limit}${projectParam}`) : null,
|
|
24758
|
+
hasMore.summaries ? fetch(`/api/summaries?offset=${paginatedSummaries.length}&limit=${limit}${projectParam}`) : null,
|
|
24759
|
+
hasMore.prompts ? fetch(`/api/prompts?offset=${paginatedPrompts.length}&limit=${limit}${projectParam}`) : null
|
|
24512
24760
|
]);
|
|
24513
|
-
|
|
24514
|
-
|
|
24761
|
+
const [obsRes, sumRes, promptRes] = fetches;
|
|
24762
|
+
const nextHasMore = { ...hasMore };
|
|
24763
|
+
if (obsRes?.ok) {
|
|
24515
24764
|
const newObs = await obsRes.json();
|
|
24516
|
-
|
|
24517
|
-
setPaginatedObservations((prev) => [...prev, ...newObs]);
|
|
24765
|
+
if (newObs.length === 0) nextHasMore.observations = false;
|
|
24766
|
+
else setPaginatedObservations((prev) => [...prev, ...newObs]);
|
|
24518
24767
|
}
|
|
24519
|
-
if (sumRes
|
|
24768
|
+
if (sumRes?.ok) {
|
|
24520
24769
|
const newSum = await sumRes.json();
|
|
24521
|
-
|
|
24522
|
-
setPaginatedSummaries((prev) => [...prev, ...newSum]);
|
|
24770
|
+
if (newSum.length === 0) nextHasMore.summaries = false;
|
|
24771
|
+
else setPaginatedSummaries((prev) => [...prev, ...newSum]);
|
|
24523
24772
|
}
|
|
24524
|
-
if (promptRes
|
|
24773
|
+
if (promptRes?.ok) {
|
|
24525
24774
|
const newPrompts = await promptRes.json();
|
|
24526
|
-
|
|
24527
|
-
setPaginatedPrompts((prev) => [...prev, ...newPrompts]);
|
|
24775
|
+
if (newPrompts.length === 0) nextHasMore.prompts = false;
|
|
24776
|
+
else setPaginatedPrompts((prev) => [...prev, ...newPrompts]);
|
|
24528
24777
|
}
|
|
24529
|
-
|
|
24778
|
+
setHasMore(nextHasMore);
|
|
24530
24779
|
} catch (error) {
|
|
24531
24780
|
console.error("Failed to load more data:", error);
|
|
24532
24781
|
} finally {
|
|
24533
24782
|
setIsLoadingMore(false);
|
|
24534
24783
|
}
|
|
24535
|
-
}, [currentFilter, paginatedObservations.length, isLoadingMore]);
|
|
24536
|
-
(0,
|
|
24784
|
+
}, [currentFilter, paginatedObservations.length, paginatedSummaries.length, paginatedPrompts.length, isLoadingMore, hasMore]);
|
|
24785
|
+
(0, import_react11.useEffect)(() => {
|
|
24537
24786
|
setPaginatedObservations([]);
|
|
24538
24787
|
setPaginatedSummaries([]);
|
|
24539
24788
|
setPaginatedPrompts([]);
|
|
24540
|
-
setHasMore(true);
|
|
24789
|
+
setHasMore({ observations: true, summaries: true, prompts: true });
|
|
24541
24790
|
if (currentFilter) {
|
|
24542
24791
|
fetchForProject(currentFilter);
|
|
24543
24792
|
}
|
|
24544
24793
|
}, [currentFilter, fetchForProject]);
|
|
24545
|
-
return /* @__PURE__ */
|
|
24794
|
+
return /* @__PURE__ */ import_react11.default.createElement("div", { className: "h-screen overflow-hidden flex flex-col bg-surface-0" }, /* @__PURE__ */ import_react11.default.createElement(
|
|
24546
24795
|
Header,
|
|
24547
24796
|
{
|
|
24548
24797
|
isConnected,
|
|
24798
|
+
lastEventTime,
|
|
24549
24799
|
resolvedTheme,
|
|
24550
|
-
|
|
24800
|
+
themePreference,
|
|
24801
|
+
onThemeChange: setThemePreference,
|
|
24551
24802
|
currentView,
|
|
24552
|
-
onViewChange: setCurrentView
|
|
24803
|
+
onViewChange: setCurrentView,
|
|
24804
|
+
onMenuToggle: () => setIsMobileMenuOpen((prev) => !prev)
|
|
24553
24805
|
}
|
|
24554
|
-
), /* @__PURE__ */
|
|
24806
|
+
), /* @__PURE__ */ import_react11.default.createElement("div", { className: "flex flex-1 overflow-hidden" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "hidden md:flex w-[260px] flex-shrink-0" }, /* @__PURE__ */ import_react11.default.createElement(
|
|
24555
24807
|
Sidebar,
|
|
24556
24808
|
{
|
|
24557
24809
|
projects,
|
|
@@ -24563,15 +24815,36 @@ function App() {
|
|
|
24563
24815
|
getDisplayName,
|
|
24564
24816
|
onRenameProject: updateAlias
|
|
24565
24817
|
}
|
|
24566
|
-
)),
|
|
24818
|
+
)), isMobileMenuOpen && /* @__PURE__ */ import_react11.default.createElement(import_react11.default.Fragment, null, /* @__PURE__ */ import_react11.default.createElement(
|
|
24819
|
+
"div",
|
|
24820
|
+
{
|
|
24821
|
+
className: "fixed inset-0 bg-black/50 z-40 md:hidden",
|
|
24822
|
+
onClick: () => setIsMobileMenuOpen(false)
|
|
24823
|
+
}
|
|
24824
|
+
), /* @__PURE__ */ import_react11.default.createElement("div", { className: "fixed inset-y-0 left-0 w-[280px] z-50 md:hidden animate-slide-in-left" }, /* @__PURE__ */ import_react11.default.createElement(
|
|
24825
|
+
Sidebar,
|
|
24826
|
+
{
|
|
24827
|
+
projects,
|
|
24828
|
+
currentFilter,
|
|
24829
|
+
onFilterChange: (p) => {
|
|
24830
|
+
setCurrentFilter(p);
|
|
24831
|
+
setIsMobileMenuOpen(false);
|
|
24832
|
+
},
|
|
24833
|
+
activeTypes,
|
|
24834
|
+
onToggleType: toggleType,
|
|
24835
|
+
stats,
|
|
24836
|
+
getDisplayName,
|
|
24837
|
+
onRenameProject: updateAlias
|
|
24838
|
+
}
|
|
24839
|
+
))), /* @__PURE__ */ import_react11.default.createElement("main", { className: "flex-1 overflow-y-auto bg-surface-0" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "max-w-3xl mx-auto px-6 py-6" }, currentFilter && /* @__PURE__ */ import_react11.default.createElement("div", { className: "flex items-center gap-3 mb-6 animate-fade-in" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "w-9 h-9 rounded-lg bg-accent-violet/15 flex items-center justify-center" }, /* @__PURE__ */ import_react11.default.createElement("span", { className: "text-xs font-bold text-accent-violet" }, getDisplayName(currentFilter).substring(0, 2).toUpperCase())), /* @__PURE__ */ import_react11.default.createElement("div", null, /* @__PURE__ */ import_react11.default.createElement("h2", { className: "text-lg font-bold text-zinc-100" }, getDisplayName(currentFilter)), currentFilter !== getDisplayName(currentFilter) && /* @__PURE__ */ import_react11.default.createElement("span", { className: "text-[11px] font-mono text-zinc-600" }, currentFilter))), /* @__PURE__ */ import_react11.default.createElement(
|
|
24567
24840
|
"button",
|
|
24568
24841
|
{
|
|
24569
24842
|
onClick: () => setCurrentFilter(""),
|
|
24570
24843
|
className: "ml-auto flex items-center gap-1.5 text-xs text-zinc-500 hover:text-zinc-300 transition-colors px-3 py-1.5 rounded-lg hover:bg-surface-2 border border-transparent hover:border-border"
|
|
24571
24844
|
},
|
|
24572
|
-
/* @__PURE__ */
|
|
24845
|
+
/* @__PURE__ */ import_react11.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ import_react11.default.createElement("path", { d: "M18 6 6 18M6 6l12 12" })),
|
|
24573
24846
|
"Clear filter"
|
|
24574
|
-
)), currentView === "feed" ? /* @__PURE__ */
|
|
24847
|
+
)), currentView === "feed" ? /* @__PURE__ */ import_react11.default.createElement(
|
|
24575
24848
|
Feed,
|
|
24576
24849
|
{
|
|
24577
24850
|
observations: filteredObservations,
|
|
@@ -24579,10 +24852,16 @@ function App() {
|
|
|
24579
24852
|
prompts: allPrompts,
|
|
24580
24853
|
onLoadMore: handleLoadMore,
|
|
24581
24854
|
isLoading: isLoadingMore,
|
|
24582
|
-
hasMore,
|
|
24855
|
+
hasMore: hasMore.observations || hasMore.summaries || hasMore.prompts,
|
|
24856
|
+
getDisplayName
|
|
24857
|
+
}
|
|
24858
|
+
) : currentView === "sessions" ? /* @__PURE__ */ import_react11.default.createElement(
|
|
24859
|
+
Sessions,
|
|
24860
|
+
{
|
|
24861
|
+
currentFilter,
|
|
24583
24862
|
getDisplayName
|
|
24584
24863
|
}
|
|
24585
|
-
) : /* @__PURE__ */
|
|
24864
|
+
) : /* @__PURE__ */ import_react11.default.createElement(
|
|
24586
24865
|
Analytics,
|
|
24587
24866
|
{
|
|
24588
24867
|
currentFilter,
|
|
@@ -24594,7 +24873,7 @@ function App() {
|
|
|
24594
24873
|
// src/ui/viewer/index.tsx
|
|
24595
24874
|
var root = import_client.default.createRoot(document.getElementById("root"));
|
|
24596
24875
|
root.render(
|
|
24597
|
-
/* @__PURE__ */
|
|
24876
|
+
/* @__PURE__ */ import_react12.default.createElement(import_react12.default.StrictMode, null, /* @__PURE__ */ import_react12.default.createElement(App, null))
|
|
24598
24877
|
);
|
|
24599
24878
|
/*! Bundled license information:
|
|
24600
24879
|
|