@burdenoff/microfe-vibecontrols 2026.525.4 → 2026.525.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/agent-manager/AgentManagerSessionItem.js +63 -62
- package/dist/components/agent-manager/AgentManagerSessionItem.js.map +1 -1
- package/dist/components/ai/AiSectionTabs.js +29 -0
- package/dist/components/ai/AiSectionTabs.js.map +1 -0
- package/dist/pages/ai/AIBookmarksPage.js +160 -155
- package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
- package/dist/pages/ai/AgentManagerPage.js +115 -110
- package/dist/pages/ai/AgentManagerPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
-
import {
|
|
2
|
+
import { useVibeNavigate as t } from "../../providers/VibeControlsProvider.js";
|
|
3
|
+
import { SelectionCheckbox as n } from "../shared/SelectionCheckbox.js";
|
|
3
4
|
import "../shared/index.js";
|
|
4
|
-
import { updateSessionConfig as
|
|
5
|
-
import { useAgentManagerStore as
|
|
6
|
-
import { relativeTime as
|
|
7
|
-
import { useState as
|
|
8
|
-
import { Pin as
|
|
9
|
-
import { motion as
|
|
10
|
-
import { jsx as
|
|
5
|
+
import { updateSessionConfig as r } from "../../services/aiApi.js";
|
|
6
|
+
import { useAgentManagerStore as i } from "../../store/agentManagerStore.js";
|
|
7
|
+
import { relativeTime as a } from "../../utils/relativeTime.js";
|
|
8
|
+
import { useState as o } from "react";
|
|
9
|
+
import { Pin as s, Tag as c, Trash2 as l } from "lucide-react";
|
|
10
|
+
import { motion as u } from "framer-motion";
|
|
11
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
11
12
|
//#region src/components/agent-manager/AgentManagerSessionItem.tsx
|
|
12
|
-
var
|
|
13
|
+
var p = {
|
|
13
14
|
active: "bg-status-success-bg",
|
|
14
15
|
idle: "bg-status-warning-bg",
|
|
15
16
|
error: "bg-status-error-bg",
|
|
16
17
|
terminated: "bg-border-default",
|
|
17
18
|
processing: "bg-status-info-bg"
|
|
18
19
|
};
|
|
19
|
-
function
|
|
20
|
-
let [
|
|
21
|
-
|
|
20
|
+
function m({ session: m, agent: h, isActive: g, isSelected: _, onDelete: v }) {
|
|
21
|
+
let [y, b] = o(!1), x = e(), S = t(), C = i((e) => e.openSession), w = i((e) => e.hydrateSessionConfig), T = i((e) => e.pinTab), E = i((e) => e.toggleSessionSelection), D = i((e) => e.tabs).find((e) => e.sessionId === m.id)?.pinned ?? !1, O = p[m.status] ?? "bg-border-default", k = m.config?.tags || [], A = () => {
|
|
22
|
+
w(m.id, m), C(m.id, m.name), S(`/ai/sessions/${m.id}`);
|
|
22
23
|
};
|
|
23
|
-
return /* @__PURE__ */
|
|
24
|
+
return /* @__PURE__ */ f(u.div, {
|
|
24
25
|
layout: !0,
|
|
25
26
|
initial: {
|
|
26
27
|
opacity: 0,
|
|
@@ -35,100 +36,100 @@ function p({ session: p, agent: m, isActive: h, isSelected: g, onDelete: _ }) {
|
|
|
35
36
|
y: -4
|
|
36
37
|
},
|
|
37
38
|
transition: { duration: .15 },
|
|
38
|
-
onMouseEnter: () =>
|
|
39
|
-
onMouseLeave: () =>
|
|
40
|
-
onClick:
|
|
41
|
-
className: `group flex items-center gap-2 px-3 py-2 cursor-pointer transition-colors ${
|
|
39
|
+
onMouseEnter: () => b(!0),
|
|
40
|
+
onMouseLeave: () => b(!1),
|
|
41
|
+
onClick: A,
|
|
42
|
+
className: `group flex items-center gap-2 px-3 py-2 cursor-pointer transition-colors ${g ? "bg-bg-elevated border-l-2 border-action-primary-bg" : "hover:bg-bg-sunken border-l-2 border-transparent"}`,
|
|
42
43
|
role: "button",
|
|
43
44
|
tabIndex: 0,
|
|
44
45
|
onKeyDown: (e) => {
|
|
45
|
-
(e.key === "Enter" || e.key === " ") &&
|
|
46
|
+
(e.key === "Enter" || e.key === " ") && A();
|
|
46
47
|
},
|
|
47
48
|
children: [
|
|
48
|
-
/* @__PURE__ */
|
|
49
|
-
className: `flex-shrink-0 transition-opacity ${
|
|
49
|
+
/* @__PURE__ */ d("div", {
|
|
50
|
+
className: `flex-shrink-0 transition-opacity ${y || _ ? "opacity-100" : "opacity-0"}`,
|
|
50
51
|
onClick: (e) => {
|
|
51
|
-
e.stopPropagation(),
|
|
52
|
+
e.stopPropagation(), E(m.id);
|
|
52
53
|
},
|
|
53
|
-
children: /* @__PURE__ */
|
|
54
|
-
checked:
|
|
55
|
-
onChange: () =>
|
|
56
|
-
ariaLabel: `Select session ${
|
|
54
|
+
children: /* @__PURE__ */ d(n, {
|
|
55
|
+
checked: _,
|
|
56
|
+
onChange: () => E(m.id),
|
|
57
|
+
ariaLabel: `Select session ${m.name}`
|
|
57
58
|
})
|
|
58
59
|
}),
|
|
59
|
-
/* @__PURE__ */
|
|
60
|
-
className: `flex-shrink-0 w-2 h-2 rounded-full ${
|
|
61
|
-
title:
|
|
60
|
+
/* @__PURE__ */ d("span", {
|
|
61
|
+
className: `flex-shrink-0 w-2 h-2 rounded-full ${O}`,
|
|
62
|
+
title: m.status
|
|
62
63
|
}),
|
|
63
|
-
/* @__PURE__ */
|
|
64
|
+
/* @__PURE__ */ f("div", {
|
|
64
65
|
className: "flex-1 min-w-0",
|
|
65
66
|
children: [
|
|
66
|
-
/* @__PURE__ */
|
|
67
|
+
/* @__PURE__ */ f("div", {
|
|
67
68
|
className: "flex items-center gap-1.5",
|
|
68
|
-
children: [/* @__PURE__ */
|
|
69
|
+
children: [/* @__PURE__ */ d("span", {
|
|
69
70
|
className: "text-sm text-text-primary truncate font-medium",
|
|
70
|
-
children:
|
|
71
|
-
}),
|
|
71
|
+
children: m.name
|
|
72
|
+
}), D && /* @__PURE__ */ d(s, { className: "w-3 h-3 text-text-secondary flex-shrink-0" })]
|
|
72
73
|
}),
|
|
73
|
-
/* @__PURE__ */
|
|
74
|
+
/* @__PURE__ */ f("div", {
|
|
74
75
|
className: "flex items-center gap-1.5 mt-0.5",
|
|
75
76
|
children: [
|
|
76
|
-
/* @__PURE__ */
|
|
77
|
+
/* @__PURE__ */ d("span", {
|
|
77
78
|
className: "text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-secondary font-mono",
|
|
78
|
-
children:
|
|
79
|
+
children: m.agentType
|
|
79
80
|
}),
|
|
80
|
-
typeof
|
|
81
|
+
typeof m.config?.mode == "string" && /* @__PURE__ */ d("span", {
|
|
81
82
|
className: "text-[10px] px-1 py-0.5 rounded bg-bg-elevated text-text-muted uppercase font-mono",
|
|
82
|
-
children:
|
|
83
|
+
children: m.config.mode
|
|
83
84
|
}),
|
|
84
|
-
|
|
85
|
+
m.createdAt && /* @__PURE__ */ d("span", {
|
|
85
86
|
className: "text-xs text-text-tertiary truncate",
|
|
86
|
-
children:
|
|
87
|
+
children: a(m.createdAt)
|
|
87
88
|
})
|
|
88
89
|
]
|
|
89
90
|
}),
|
|
90
|
-
|
|
91
|
+
k.length > 0 && /* @__PURE__ */ f("div", {
|
|
91
92
|
className: "flex gap-0.5 mt-0.5 flex-wrap",
|
|
92
|
-
children: [
|
|
93
|
+
children: [k.slice(0, 3).map((e) => /* @__PURE__ */ d("span", {
|
|
93
94
|
className: "px-1 py-0.5 text-[10px] bg-bg-elevated text-text-tertiary rounded",
|
|
94
95
|
children: e
|
|
95
|
-
}, e)),
|
|
96
|
+
}, e)), k.length > 3 && /* @__PURE__ */ f("span", {
|
|
96
97
|
className: "text-[10px] text-text-muted",
|
|
97
|
-
children: ["+",
|
|
98
|
+
children: ["+", k.length - 3]
|
|
98
99
|
})]
|
|
99
100
|
})
|
|
100
101
|
]
|
|
101
102
|
}),
|
|
102
|
-
|
|
103
|
+
y && /* @__PURE__ */ f("div", {
|
|
103
104
|
className: "flex items-center gap-0.5 flex-shrink-0",
|
|
104
105
|
children: [
|
|
105
|
-
/* @__PURE__ */
|
|
106
|
+
/* @__PURE__ */ d("button", {
|
|
106
107
|
onClick: (e) => {
|
|
107
|
-
e.stopPropagation(),
|
|
108
|
+
e.stopPropagation(), w(m.id, m), C(m.id, m.name), T(m.id);
|
|
108
109
|
},
|
|
109
|
-
className: `p-1 rounded transition-colors ${
|
|
110
|
-
title:
|
|
111
|
-
children: /* @__PURE__ */
|
|
110
|
+
className: `p-1 rounded transition-colors ${D ? "text-action-primary-bg hover:bg-bg-elevated" : "text-text-secondary hover:text-text-primary hover:bg-bg-elevated"}`,
|
|
111
|
+
title: D ? x("vibecontrols.agentManager.sessionItem.unpin", "Unpin") : x("vibecontrols.agentManager.sessionItem.pin", "Pin"),
|
|
112
|
+
children: /* @__PURE__ */ d(s, { className: "w-3.5 h-3.5" })
|
|
112
113
|
}),
|
|
113
|
-
/* @__PURE__ */
|
|
114
|
+
/* @__PURE__ */ d("button", {
|
|
114
115
|
onClick: (e) => {
|
|
115
116
|
e.stopPropagation();
|
|
116
|
-
let t =
|
|
117
|
-
if (
|
|
118
|
-
let i =
|
|
119
|
-
|
|
117
|
+
let t = k.join(", "), n = window.prompt("Enter tags (comma-separated):", t);
|
|
118
|
+
if (n === null) return;
|
|
119
|
+
let i = n.split(",").map((e) => e.trim()).filter(Boolean);
|
|
120
|
+
r(h, m.id, { tags: i });
|
|
120
121
|
},
|
|
121
122
|
className: "p-1 rounded text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors",
|
|
122
|
-
title:
|
|
123
|
-
children: /* @__PURE__ */
|
|
123
|
+
title: x("vibecontrols.agentManager.sessionItem.editTags", "Edit tags"),
|
|
124
|
+
children: /* @__PURE__ */ d(c, { className: "w-3.5 h-3.5" })
|
|
124
125
|
}),
|
|
125
|
-
/* @__PURE__ */
|
|
126
|
+
/* @__PURE__ */ d("button", {
|
|
126
127
|
onClick: (e) => {
|
|
127
|
-
e.stopPropagation(),
|
|
128
|
+
e.stopPropagation(), v(m.id);
|
|
128
129
|
},
|
|
129
130
|
className: "p-1 rounded text-text-secondary hover:text-status-error-text hover:bg-bg-elevated transition-colors",
|
|
130
|
-
title:
|
|
131
|
-
children: /* @__PURE__ */
|
|
131
|
+
title: x("vibecontrols.agentManager.sessionItem.deleteSession", "Delete session"),
|
|
132
|
+
children: /* @__PURE__ */ d(l, { className: "w-3.5 h-3.5" })
|
|
132
133
|
})
|
|
133
134
|
]
|
|
134
135
|
})
|
|
@@ -136,6 +137,6 @@ function p({ session: p, agent: m, isActive: h, isSelected: g, onDelete: _ }) {
|
|
|
136
137
|
});
|
|
137
138
|
}
|
|
138
139
|
//#endregion
|
|
139
|
-
export {
|
|
140
|
+
export { m as AgentManagerSessionItem };
|
|
140
141
|
|
|
141
142
|
//# sourceMappingURL=AgentManagerSessionItem.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentManagerSessionItem.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerSessionItem.tsx"],"sourcesContent":["/**\n * Single session row in the sidebar session list.\n *\n * Displays status dot, name, SDK badge, model badge, and relative timestamp.\n * Click opens the session as a tab; hover reveals pin/delete actions.\n */\n\nimport { useState } from 'react';\nimport { Pin, Trash2, Tag } from 'lucide-react';\nimport { motion } from 'framer-motion';\nimport type { AISession, AgentRef } from '@/services/aiApi';\nimport { updateSessionConfig } from '@/services/aiApi';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { SelectionCheckbox } from '@/components/shared';\nimport { relativeTime } from '@/utils/relativeTime';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AgentManagerSessionItemProps {\n session: AISession;\n agent: AgentRef;\n isActive: boolean;\n isSelected: boolean;\n onDelete: (sessionId: string) => void;\n}\n\nconst STATUS_DOT_COLORS: Record<string, string> = {\n active: 'bg-status-success-bg',\n idle: 'bg-status-warning-bg',\n error: 'bg-status-error-bg',\n terminated: 'bg-border-default',\n processing: 'bg-status-info-bg',\n};\n\nexport function AgentManagerSessionItem({\n session,\n agent,\n isActive,\n isSelected,\n onDelete,\n}: AgentManagerSessionItemProps) {\n const [hovered, setHovered] = useState(false);\n const tr = useTr();\n const openSession = useAgentManagerStore((s) => s.openSession);\n const hydrateSessionConfig = useAgentManagerStore((s) => s.hydrateSessionConfig);\n const pinTab = useAgentManagerStore((s) => s.pinTab);\n const toggleSessionSelection = useAgentManagerStore((s) => s.toggleSessionSelection);\n const tabs = useAgentManagerStore((s) => s.tabs);\n\n const isPinned = tabs.find((t) => t.sessionId === session.id)?.pinned ?? false;\n const dotColor = STATUS_DOT_COLORS[session.status] ?? 'bg-border-default';\n const tags: string[] = ((session.config as Record<string, unknown>)?.tags as string[]) || [];\n\n const handleClick = () => {\n hydrateSessionConfig(session.id, session);\n openSession(session.id, session.name);\n };\n\n const handlePin = (e: React.MouseEvent) => {\n e.stopPropagation();\n hydrateSessionConfig(session.id, session);\n openSession(session.id, session.name);\n pinTab(session.id);\n };\n\n const handleDelete = (e: React.MouseEvent) => {\n e.stopPropagation();\n onDelete(session.id);\n };\n\n const handleEditTags = (e: React.MouseEvent) => {\n e.stopPropagation();\n const currentTags = tags.join(', ');\n const input = window.prompt('Enter tags (comma-separated):', currentTags);\n if (input === null) return;\n const newTags = input\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n void updateSessionConfig(agent, session.id, { tags: newTags });\n };\n\n const handleCheckbox = (e: React.MouseEvent) => {\n e.stopPropagation();\n toggleSessionSelection(session.id);\n };\n\n return (\n <motion.div\n layout\n initial={{ opacity: 0, y: 4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: 0.15 }}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onClick={handleClick}\n className={`group flex items-center gap-2 px-3 py-2 cursor-pointer transition-colors ${\n isActive\n ? 'bg-bg-elevated border-l-2 border-action-primary-bg'\n : 'hover:bg-bg-sunken border-l-2 border-transparent'\n }`}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') handleClick();\n }}\n >\n {/* Selection checkbox (on hover or when selected) */}\n <div\n className={`flex-shrink-0 transition-opacity ${hovered || isSelected ? 'opacity-100' : 'opacity-0'}`}\n onClick={handleCheckbox}\n >\n <SelectionCheckbox\n checked={isSelected}\n onChange={() => toggleSessionSelection(session.id)}\n ariaLabel={`Select session ${session.name}`}\n />\n </div>\n\n {/* Status dot */}\n <span className={`flex-shrink-0 w-2 h-2 rounded-full ${dotColor}`} title={session.status} />\n\n {/* Session info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-sm text-text-primary truncate font-medium\">{session.name}</span>\n {isPinned && <Pin className=\"w-3 h-3 text-text-secondary flex-shrink-0\" />}\n </div>\n <div className=\"flex items-center gap-1.5 mt-0.5\">\n <span className=\"text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-secondary font-mono\">\n {session.agentType}\n </span>\n {typeof (session.config as Record<string, unknown>)?.mode === 'string' && (\n <span className=\"text-[10px] px-1 py-0.5 rounded bg-bg-elevated text-text-muted uppercase font-mono\">\n {(session.config as Record<string, string>).mode}\n </span>\n )}\n {session.createdAt && (\n <span className=\"text-xs text-text-tertiary truncate\">\n {relativeTime(session.createdAt)}\n </span>\n )}\n </div>\n {tags.length > 0 && (\n <div className=\"flex gap-0.5 mt-0.5 flex-wrap\">\n {tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"px-1 py-0.5 text-[10px] bg-bg-elevated text-text-tertiary rounded\"\n >\n {tag}\n </span>\n ))}\n {tags.length > 3 && (\n <span className=\"text-[10px] text-text-muted\">+{tags.length - 3}</span>\n )}\n </div>\n )}\n </div>\n\n {/* Hover actions */}\n {hovered && (\n <div className=\"flex items-center gap-0.5 flex-shrink-0\">\n <button\n onClick={handlePin}\n className={`p-1 rounded transition-colors ${\n isPinned\n ? 'text-action-primary-bg hover:bg-bg-elevated'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-elevated'\n }`}\n title={\n isPinned\n ? tr('vibecontrols.agentManager.sessionItem.unpin', 'Unpin')\n : tr('vibecontrols.agentManager.sessionItem.pin', 'Pin')\n }\n >\n <Pin className=\"w-3.5 h-3.5\" />\n </button>\n <button\n onClick={handleEditTags}\n className=\"p-1 rounded text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors\"\n title={tr('vibecontrols.agentManager.sessionItem.editTags', 'Edit tags')}\n >\n <Tag className=\"w-3.5 h-3.5\" />\n </button>\n <button\n onClick={handleDelete}\n className=\"p-1 rounded text-text-secondary hover:text-status-error-text hover:bg-bg-elevated transition-colors\"\n title={tr('vibecontrols.agentManager.sessionItem.deleteSession', 'Delete session')}\n >\n <Trash2 className=\"w-3.5 h-3.5\" />\n </button>\n </div>\n )}\n </motion.div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AgentManagerSessionItem.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerSessionItem.tsx"],"sourcesContent":["/**\n * Single session row in the sidebar session list.\n *\n * Displays status dot, name, SDK badge, model badge, and relative timestamp.\n * Click opens the session as a tab; hover reveals pin/delete actions.\n */\n\nimport { useState } from 'react';\nimport { Pin, Trash2, Tag } from 'lucide-react';\nimport { motion } from 'framer-motion';\nimport type { AISession, AgentRef } from '@/services/aiApi';\nimport { updateSessionConfig } from '@/services/aiApi';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { SelectionCheckbox } from '@/components/shared';\nimport { relativeTime } from '@/utils/relativeTime';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useVibeNavigate } from '../../providers/VibeControlsProvider';\n\ninterface AgentManagerSessionItemProps {\n session: AISession;\n agent: AgentRef;\n isActive: boolean;\n isSelected: boolean;\n onDelete: (sessionId: string) => void;\n}\n\nconst STATUS_DOT_COLORS: Record<string, string> = {\n active: 'bg-status-success-bg',\n idle: 'bg-status-warning-bg',\n error: 'bg-status-error-bg',\n terminated: 'bg-border-default',\n processing: 'bg-status-info-bg',\n};\n\nexport function AgentManagerSessionItem({\n session,\n agent,\n isActive,\n isSelected,\n onDelete,\n}: AgentManagerSessionItemProps) {\n const [hovered, setHovered] = useState(false);\n const tr = useTr();\n const navigate = useVibeNavigate();\n const openSession = useAgentManagerStore((s) => s.openSession);\n const hydrateSessionConfig = useAgentManagerStore((s) => s.hydrateSessionConfig);\n const pinTab = useAgentManagerStore((s) => s.pinTab);\n const toggleSessionSelection = useAgentManagerStore((s) => s.toggleSessionSelection);\n const tabs = useAgentManagerStore((s) => s.tabs);\n\n const isPinned = tabs.find((t) => t.sessionId === session.id)?.pinned ?? false;\n const dotColor = STATUS_DOT_COLORS[session.status] ?? 'bg-border-default';\n const tags: string[] = ((session.config as Record<string, unknown>)?.tags as string[]) || [];\n\n const handleClick = () => {\n hydrateSessionConfig(session.id, session);\n openSession(session.id, session.name);\n // Reflect the opened session in the URL so it's deep-linkable / shareable\n // and back-button friendly. useVibeNavigate prepends the mount basePath, so\n // the path stays correct under any host mount or standalone `/`.\n // AgentManagerPage's sessionIdParam effect makes this idempotent\n // (openSession no-ops if the tab is already open).\n navigate(`/ai/sessions/${session.id}`);\n };\n\n const handlePin = (e: React.MouseEvent) => {\n e.stopPropagation();\n hydrateSessionConfig(session.id, session);\n openSession(session.id, session.name);\n pinTab(session.id);\n };\n\n const handleDelete = (e: React.MouseEvent) => {\n e.stopPropagation();\n onDelete(session.id);\n };\n\n const handleEditTags = (e: React.MouseEvent) => {\n e.stopPropagation();\n const currentTags = tags.join(', ');\n const input = window.prompt('Enter tags (comma-separated):', currentTags);\n if (input === null) return;\n const newTags = input\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n void updateSessionConfig(agent, session.id, { tags: newTags });\n };\n\n const handleCheckbox = (e: React.MouseEvent) => {\n e.stopPropagation();\n toggleSessionSelection(session.id);\n };\n\n return (\n <motion.div\n layout\n initial={{ opacity: 0, y: 4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: 0.15 }}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onClick={handleClick}\n className={`group flex items-center gap-2 px-3 py-2 cursor-pointer transition-colors ${\n isActive\n ? 'bg-bg-elevated border-l-2 border-action-primary-bg'\n : 'hover:bg-bg-sunken border-l-2 border-transparent'\n }`}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') handleClick();\n }}\n >\n {/* Selection checkbox (on hover or when selected) */}\n <div\n className={`flex-shrink-0 transition-opacity ${hovered || isSelected ? 'opacity-100' : 'opacity-0'}`}\n onClick={handleCheckbox}\n >\n <SelectionCheckbox\n checked={isSelected}\n onChange={() => toggleSessionSelection(session.id)}\n ariaLabel={`Select session ${session.name}`}\n />\n </div>\n\n {/* Status dot */}\n <span className={`flex-shrink-0 w-2 h-2 rounded-full ${dotColor}`} title={session.status} />\n\n {/* Session info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-sm text-text-primary truncate font-medium\">{session.name}</span>\n {isPinned && <Pin className=\"w-3 h-3 text-text-secondary flex-shrink-0\" />}\n </div>\n <div className=\"flex items-center gap-1.5 mt-0.5\">\n <span className=\"text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-secondary font-mono\">\n {session.agentType}\n </span>\n {typeof (session.config as Record<string, unknown>)?.mode === 'string' && (\n <span className=\"text-[10px] px-1 py-0.5 rounded bg-bg-elevated text-text-muted uppercase font-mono\">\n {(session.config as Record<string, string>).mode}\n </span>\n )}\n {session.createdAt && (\n <span className=\"text-xs text-text-tertiary truncate\">\n {relativeTime(session.createdAt)}\n </span>\n )}\n </div>\n {tags.length > 0 && (\n <div className=\"flex gap-0.5 mt-0.5 flex-wrap\">\n {tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"px-1 py-0.5 text-[10px] bg-bg-elevated text-text-tertiary rounded\"\n >\n {tag}\n </span>\n ))}\n {tags.length > 3 && (\n <span className=\"text-[10px] text-text-muted\">+{tags.length - 3}</span>\n )}\n </div>\n )}\n </div>\n\n {/* Hover actions */}\n {hovered && (\n <div className=\"flex items-center gap-0.5 flex-shrink-0\">\n <button\n onClick={handlePin}\n className={`p-1 rounded transition-colors ${\n isPinned\n ? 'text-action-primary-bg hover:bg-bg-elevated'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-elevated'\n }`}\n title={\n isPinned\n ? tr('vibecontrols.agentManager.sessionItem.unpin', 'Unpin')\n : tr('vibecontrols.agentManager.sessionItem.pin', 'Pin')\n }\n >\n <Pin className=\"w-3.5 h-3.5\" />\n </button>\n <button\n onClick={handleEditTags}\n className=\"p-1 rounded text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors\"\n title={tr('vibecontrols.agentManager.sessionItem.editTags', 'Edit tags')}\n >\n <Tag className=\"w-3.5 h-3.5\" />\n </button>\n <button\n onClick={handleDelete}\n className=\"p-1 rounded text-text-secondary hover:text-status-error-text hover:bg-bg-elevated transition-colors\"\n title={tr('vibecontrols.agentManager.sessionItem.deleteSession', 'Delete session')}\n >\n <Trash2 className=\"w-3.5 h-3.5\" />\n </button>\n </div>\n )}\n </motion.div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
+
import { useVibeNavigate as t } from "../../providers/VibeControlsProvider.js";
|
|
3
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
4
|
+
import { Tabs as r } from "@burdenoff/fe-libs/chrome";
|
|
5
|
+
//#region src/components/ai/AiSectionTabs.tsx
|
|
6
|
+
function i({ active: i }) {
|
|
7
|
+
let a = t(), o = e();
|
|
8
|
+
return /* @__PURE__ */ n("div", {
|
|
9
|
+
className: "border-b border-border-subtle px-4 sm:px-6 pt-2",
|
|
10
|
+
children: /* @__PURE__ */ n(r, {
|
|
11
|
+
items: [{
|
|
12
|
+
id: "workbench",
|
|
13
|
+
label: o("vibecontrols.pages.ai.tabWorkbench", "Workbench")
|
|
14
|
+
}, {
|
|
15
|
+
id: "bookmarks",
|
|
16
|
+
label: o("vibecontrols.pages.ai.tabBookmarks", "Bookmarks")
|
|
17
|
+
}],
|
|
18
|
+
activeTabId: i,
|
|
19
|
+
onTabChange: (e) => {
|
|
20
|
+
e !== i && a(e === "bookmarks" ? "/ai/bookmarks" : "/ai");
|
|
21
|
+
},
|
|
22
|
+
variant: "underline"
|
|
23
|
+
})
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { i as AiSectionTabs };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=AiSectionTabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AiSectionTabs.js","names":[],"sources":["../../../src/components/ai/AiSectionTabs.tsx"],"sourcesContent":["/**\n * Tab strip for the AI section — switches between the workbench (`/ai`) and\n * saved bookmarks (`/ai/bookmarks`). Mirrors the Agents landing tab pattern\n * (fe-libs Tabs, underline variant). The active tab is supplied by the host\n * page since the two tabs map to two routes rather than to swapped content on\n * one page. Navigation goes through useVibeNavigate so paths are basePath-aware\n * (correct under any host mount or standalone `/`).\n */\nimport { Tabs, type TabItem } from '@burdenoff/fe-libs/chrome';\nimport { useVibeNavigate } from '../../providers/VibeControlsProvider';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AiSectionTabsProps {\n active: 'workbench' | 'bookmarks';\n}\n\nexport function AiSectionTabs({ active }: AiSectionTabsProps) {\n const navigate = useVibeNavigate();\n const tr = useTr();\n\n const tabs: TabItem[] = [\n { id: 'workbench', label: tr('vibecontrols.pages.ai.tabWorkbench', 'Workbench') },\n { id: 'bookmarks', label: tr('vibecontrols.pages.ai.tabBookmarks', 'Bookmarks') },\n ];\n\n const handleTabChange = (id: string) => {\n if (id === active) return;\n navigate(id === 'bookmarks' ? '/ai/bookmarks' : '/ai');\n };\n\n return (\n <div className=\"border-b border-border-subtle px-4 sm:px-6 pt-2\">\n <Tabs items={tabs} activeTabId={active} onTabChange={handleTabChange} variant=\"underline\" />\n </div>\n );\n}\n"],"mappings":";;;;;AAgBA,SAAgB,EAAc,EAAE,aAA8B;CAC5D,IAAM,IAAW,GAAiB,EAC5B,IAAK,GAAO;AAYlB,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GAAM,OAZc,CACtB;IAAE,IAAI;IAAa,OAAO,EAAG,sCAAsC,YAAY;IAAE,EACjF;IAAE,IAAI;IAAa,OAAO,EAAG,sCAAsC,YAAY;IAAE,CAClF;GASsB,aAAa;GAAQ,cAPnB,MAAe;AAClC,UAAO,KACX,EAAS,MAAO,cAAc,kBAAkB,MAAM;;GAKkB,SAAQ;GAAc,CAAA;EACxF,CAAA"}
|
|
@@ -2,183 +2,188 @@ import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
|
2
2
|
import { useDeleteAgentMessageBookmarkMutation as t, useMyAgentMessageBookmarksQuery as n, useUpdateAgentMessageBookmarkMutation as r } from "../../generated/wspace-operations.js";
|
|
3
3
|
import i from "../../hooks/useCachedInitialData.js";
|
|
4
4
|
import { relativeTime as a } from "../../utils/relativeTime.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { AiSectionTabs as o } from "../../components/ai/AiSectionTabs.js";
|
|
6
|
+
import { useMemo as s, useState as c } from "react";
|
|
7
|
+
import { Bookmark as l, Check as u, ChevronRight as d, Pencil as f, Trash2 as p, X as m } from "lucide-react";
|
|
8
|
+
import { useNavigate as h } from "react-router-dom";
|
|
9
|
+
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
9
10
|
//#region src/pages/ai/AIBookmarksPage.tsx
|
|
10
|
-
var
|
|
11
|
-
let
|
|
11
|
+
var y = 100, b = () => {
|
|
12
|
+
let b = h(), x = e(), { agents: S } = i(), C = s(() => new Map(S.map((e) => [e.id, e.name ?? e.id])), [S]), { data: w, loading: T } = n({
|
|
12
13
|
variables: { pagination: {
|
|
13
|
-
limit:
|
|
14
|
+
limit: y,
|
|
14
15
|
offset: 0
|
|
15
16
|
} },
|
|
16
17
|
fetchPolicy: "cache-and-network"
|
|
17
|
-
}), [
|
|
18
|
+
}), [E] = r({ refetchQueries: ["MyAgentMessageBookmarks"] }), [D] = t({ refetchQueries: ["MyAgentMessageBookmarks"] }), [O, k] = c(null), [A, j] = c(""), M = s(() => w?.myAgentMessageBookmarks.items ?? [], [w?.myAgentMessageBookmarks.items]), N = s(() => {
|
|
18
19
|
let e = /* @__PURE__ */ new Map();
|
|
19
|
-
for (let t of
|
|
20
|
+
for (let t of M) {
|
|
20
21
|
let n = e.get(t.sessionId) ?? [];
|
|
21
22
|
n.push(t), e.set(t.sessionId, n);
|
|
22
23
|
}
|
|
23
24
|
return Array.from(e.entries());
|
|
24
|
-
}, [
|
|
25
|
-
|
|
26
|
-
}, P = async (e) => {
|
|
27
|
-
await T({ variables: {
|
|
28
|
-
id: e,
|
|
29
|
-
input: { label: k || null }
|
|
30
|
-
} }), O(null), A("");
|
|
25
|
+
}, [M]), P = (e, t) => {
|
|
26
|
+
k(e), j(t ?? "");
|
|
31
27
|
}, F = async (e) => {
|
|
32
|
-
await E({ variables: {
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
await E({ variables: {
|
|
29
|
+
id: e,
|
|
30
|
+
input: { label: A || null }
|
|
31
|
+
} }), k(null), j("");
|
|
32
|
+
}, I = async (e) => {
|
|
33
|
+
await D({ variables: { id: e } });
|
|
34
|
+
}, L = (e, t) => {
|
|
35
|
+
b(`/vibecontrols/ai/sessions/${e}?messageId=${t}`);
|
|
35
36
|
};
|
|
36
|
-
return /* @__PURE__ */
|
|
37
|
+
return /* @__PURE__ */ v("div", {
|
|
37
38
|
className: "flex flex-col h-full min-h-0",
|
|
38
39
|
"data-testid": "ai-bookmarks-page",
|
|
39
|
-
children: [
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
className: "
|
|
43
|
-
children: [/* @__PURE__ */
|
|
44
|
-
className: "
|
|
45
|
-
children:
|
|
40
|
+
children: [
|
|
41
|
+
/* @__PURE__ */ _(o, { active: "bookmarks" }),
|
|
42
|
+
/* @__PURE__ */ v("header", {
|
|
43
|
+
className: "px-6 py-4 border-b border-border-default flex-shrink-0",
|
|
44
|
+
children: [/* @__PURE__ */ v("div", {
|
|
45
|
+
className: "flex items-center gap-2",
|
|
46
|
+
children: [/* @__PURE__ */ _(l, { className: "w-5 h-5 text-text-secondary" }), /* @__PURE__ */ _("h1", {
|
|
47
|
+
className: "text-lg font-semibold text-text-primary",
|
|
48
|
+
children: x("vibecontrols.bookmarks.pageTitle", "Saved messages")
|
|
49
|
+
})]
|
|
50
|
+
}), /* @__PURE__ */ _("p", {
|
|
51
|
+
className: "text-xs text-text-secondary mt-1",
|
|
52
|
+
children: x("vibecontrols.bookmarks.pageSubtitle", "Every AI Chat message you have bookmarked across all sessions.")
|
|
46
53
|
})]
|
|
47
|
-
}),
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
54
|
+
}),
|
|
55
|
+
/* @__PURE__ */ v("div", {
|
|
56
|
+
className: "flex-1 min-h-0 overflow-y-auto",
|
|
57
|
+
children: [
|
|
58
|
+
T && M.length === 0 && /* @__PURE__ */ _("div", {
|
|
59
|
+
className: "px-6 py-8 text-sm text-text-tertiary",
|
|
60
|
+
children: x("vibecontrols.common.loading", "Loading...")
|
|
61
|
+
}),
|
|
62
|
+
!T && M.length === 0 && /* @__PURE__ */ v("div", {
|
|
63
|
+
className: "px-6 py-16 flex flex-col items-center text-center gap-2",
|
|
64
|
+
children: [
|
|
65
|
+
/* @__PURE__ */ _(l, { className: "w-10 h-10 text-text-muted" }),
|
|
66
|
+
/* @__PURE__ */ _("p", {
|
|
67
|
+
className: "text-base font-medium text-text-primary",
|
|
68
|
+
children: x("vibecontrols.bookmarks.empty", "You haven't bookmarked any messages yet")
|
|
69
|
+
}),
|
|
70
|
+
/* @__PURE__ */ _("p", {
|
|
71
|
+
className: "text-xs text-text-secondary max-w-md",
|
|
72
|
+
children: x("vibecontrols.bookmarks.emptyHint", "Open any AI Chat session and click the bookmark icon on a message to save it here.")
|
|
73
|
+
})
|
|
74
|
+
]
|
|
75
|
+
}),
|
|
76
|
+
/* @__PURE__ */ _("div", {
|
|
77
|
+
className: "divide-y divide-border-subtle",
|
|
78
|
+
children: N.map(([e, t]) => {
|
|
79
|
+
let n = t[0]?.agentId ?? "", r = C.get(n) ?? n;
|
|
80
|
+
return /* @__PURE__ */ v("section", {
|
|
81
|
+
className: "px-6 py-4",
|
|
82
|
+
children: [/* @__PURE__ */ v("div", {
|
|
83
|
+
className: "flex items-center justify-between mb-2",
|
|
84
|
+
children: [/* @__PURE__ */ v("div", {
|
|
85
|
+
className: "min-w-0",
|
|
86
|
+
children: [/* @__PURE__ */ _("p", {
|
|
87
|
+
className: "text-xs font-mono text-text-tertiary truncate",
|
|
88
|
+
children: e
|
|
89
|
+
}), /* @__PURE__ */ _("p", {
|
|
90
|
+
className: "text-sm font-medium text-text-primary",
|
|
91
|
+
children: r
|
|
92
|
+
})]
|
|
93
|
+
}), /* @__PURE__ */ _("button", {
|
|
94
|
+
type: "button",
|
|
95
|
+
onClick: () => b(`/vibecontrols/ai/sessions/${e}`),
|
|
96
|
+
className: "text-xs text-action-primary-text hover:underline",
|
|
97
|
+
children: x("vibecontrols.bookmarks.openInSession", "Open session")
|
|
88
98
|
})]
|
|
89
|
-
}), /* @__PURE__ */
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
99
|
+
}), /* @__PURE__ */ _("ul", {
|
|
100
|
+
className: "space-y-1",
|
|
101
|
+
children: t.map((e) => {
|
|
102
|
+
let t = O === e.id;
|
|
103
|
+
return /* @__PURE__ */ _("li", {
|
|
104
|
+
className: "rounded-md border border-border-subtle bg-bg-surface px-3 py-2 hover:border-action-primary-bg/30 transition-colors",
|
|
105
|
+
children: /* @__PURE__ */ v("div", {
|
|
106
|
+
className: "flex items-start gap-2",
|
|
107
|
+
children: [/* @__PURE__ */ v("div", {
|
|
108
|
+
className: "flex-1 min-w-0",
|
|
109
|
+
children: [t ? /* @__PURE__ */ _("input", {
|
|
110
|
+
autoFocus: !0,
|
|
111
|
+
value: A,
|
|
112
|
+
onChange: (e) => j(e.target.value),
|
|
113
|
+
onKeyDown: (t) => {
|
|
114
|
+
t.key === "Enter" && F(e.id), t.key === "Escape" && (k(null), j(""));
|
|
115
|
+
},
|
|
116
|
+
placeholder: x("vibecontrols.agentManager.bookmarksDrawer.labelPlaceholder", "Add a label (optional)"),
|
|
117
|
+
className: "w-full bg-bg-sunken border border-border-subtle rounded px-2 py-1 text-sm text-text-primary placeholder:text-text-tertiary focus:outline-none focus:border-action-primary-bg"
|
|
118
|
+
}) : /* @__PURE__ */ _("p", {
|
|
119
|
+
className: "text-sm text-text-primary truncate",
|
|
120
|
+
children: e.label || /* @__PURE__ */ _("span", {
|
|
121
|
+
className: "text-text-tertiary italic",
|
|
122
|
+
children: x("vibecontrols.bookmarks.unlabeled", "Unlabeled bookmark")
|
|
123
|
+
})
|
|
124
|
+
}), /* @__PURE__ */ v("p", {
|
|
125
|
+
className: "text-[10px] text-text-tertiary mt-1 font-mono truncate",
|
|
126
|
+
children: [
|
|
127
|
+
"msg:",
|
|
128
|
+
e.messageId,
|
|
129
|
+
" · ",
|
|
130
|
+
a(e.createdAt)
|
|
131
|
+
]
|
|
132
|
+
})]
|
|
133
|
+
}), /* @__PURE__ */ v("div", {
|
|
134
|
+
className: "flex items-center gap-0.5 shrink-0",
|
|
122
135
|
children: [
|
|
123
|
-
"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
136
|
+
!t && /* @__PURE__ */ _("button", {
|
|
137
|
+
type: "button",
|
|
138
|
+
onClick: () => L(e.sessionId, e.messageId),
|
|
139
|
+
className: "p-1 rounded hover:bg-bg-sunken transition-colors",
|
|
140
|
+
title: x("vibecontrols.bookmarks.jumpToMessage", "Jump to message in session"),
|
|
141
|
+
"aria-label": x("vibecontrols.bookmarks.jumpToMessage", "Jump to message"),
|
|
142
|
+
children: /* @__PURE__ */ _(d, { className: "w-3.5 h-3.5 text-text-secondary" })
|
|
143
|
+
}),
|
|
144
|
+
t ? /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _("button", {
|
|
145
|
+
type: "button",
|
|
146
|
+
onClick: () => void F(e.id),
|
|
147
|
+
className: "p-1 rounded hover:bg-bg-sunken transition-colors",
|
|
148
|
+
"aria-label": x("vibecontrols.common.save", "Save"),
|
|
149
|
+
children: /* @__PURE__ */ _(u, { className: "w-3.5 h-3.5 text-status-success-text" })
|
|
150
|
+
}), /* @__PURE__ */ _("button", {
|
|
151
|
+
type: "button",
|
|
152
|
+
onClick: () => {
|
|
153
|
+
k(null), j("");
|
|
154
|
+
},
|
|
155
|
+
className: "p-1 rounded hover:bg-bg-sunken transition-colors",
|
|
156
|
+
"aria-label": x("vibecontrols.common.cancel", "Cancel"),
|
|
157
|
+
children: /* @__PURE__ */ _(m, { className: "w-3.5 h-3.5 text-text-secondary" })
|
|
158
|
+
})] }) : /* @__PURE__ */ _("button", {
|
|
159
|
+
type: "button",
|
|
160
|
+
onClick: () => P(e.id, e.label),
|
|
161
|
+
className: "p-1 rounded hover:bg-bg-sunken transition-colors",
|
|
162
|
+
"aria-label": x("vibecontrols.agentManager.bookmarksDrawer.editLabel", "Edit label"),
|
|
163
|
+
children: /* @__PURE__ */ _(f, { className: "w-3.5 h-3.5 text-text-secondary" })
|
|
164
|
+
}),
|
|
165
|
+
/* @__PURE__ */ _("button", {
|
|
166
|
+
type: "button",
|
|
167
|
+
onClick: () => void I(e.id),
|
|
168
|
+
className: "p-1 rounded hover:bg-bg-sunken transition-colors",
|
|
169
|
+
"aria-label": x("vibecontrols.agentManager.messageItem.removeBookmark", "Remove bookmark"),
|
|
170
|
+
children: /* @__PURE__ */ _(p, { className: "w-3.5 h-3.5 text-status-danger-text" })
|
|
171
|
+
})
|
|
127
172
|
]
|
|
128
173
|
})]
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
className: "p-1 rounded hover:bg-bg-sunken transition-colors",
|
|
136
|
-
title: b("vibecontrols.bookmarks.jumpToMessage", "Jump to message in session"),
|
|
137
|
-
"aria-label": b("vibecontrols.bookmarks.jumpToMessage", "Jump to message"),
|
|
138
|
-
children: /* @__PURE__ */ g(u, { className: "w-3.5 h-3.5 text-text-secondary" })
|
|
139
|
-
}),
|
|
140
|
-
t ? /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("button", {
|
|
141
|
-
type: "button",
|
|
142
|
-
onClick: () => void P(e.id),
|
|
143
|
-
className: "p-1 rounded hover:bg-bg-sunken transition-colors",
|
|
144
|
-
"aria-label": b("vibecontrols.common.save", "Save"),
|
|
145
|
-
children: /* @__PURE__ */ g(l, { className: "w-3.5 h-3.5 text-status-success-text" })
|
|
146
|
-
}), /* @__PURE__ */ g("button", {
|
|
147
|
-
type: "button",
|
|
148
|
-
onClick: () => {
|
|
149
|
-
O(null), A("");
|
|
150
|
-
},
|
|
151
|
-
className: "p-1 rounded hover:bg-bg-sunken transition-colors",
|
|
152
|
-
"aria-label": b("vibecontrols.common.cancel", "Cancel"),
|
|
153
|
-
children: /* @__PURE__ */ g(p, { className: "w-3.5 h-3.5 text-text-secondary" })
|
|
154
|
-
})] }) : /* @__PURE__ */ g("button", {
|
|
155
|
-
type: "button",
|
|
156
|
-
onClick: () => N(e.id, e.label),
|
|
157
|
-
className: "p-1 rounded hover:bg-bg-sunken transition-colors",
|
|
158
|
-
"aria-label": b("vibecontrols.agentManager.bookmarksDrawer.editLabel", "Edit label"),
|
|
159
|
-
children: /* @__PURE__ */ g(d, { className: "w-3.5 h-3.5 text-text-secondary" })
|
|
160
|
-
}),
|
|
161
|
-
/* @__PURE__ */ g("button", {
|
|
162
|
-
type: "button",
|
|
163
|
-
onClick: () => void F(e.id),
|
|
164
|
-
className: "p-1 rounded hover:bg-bg-sunken transition-colors",
|
|
165
|
-
"aria-label": b("vibecontrols.agentManager.messageItem.removeBookmark", "Remove bookmark"),
|
|
166
|
-
children: /* @__PURE__ */ g(f, { className: "w-3.5 h-3.5 text-status-danger-text" })
|
|
167
|
-
})
|
|
168
|
-
]
|
|
169
|
-
})]
|
|
170
|
-
})
|
|
171
|
-
}, e.id);
|
|
172
|
-
})
|
|
173
|
-
})]
|
|
174
|
-
}, e);
|
|
174
|
+
})
|
|
175
|
+
}, e.id);
|
|
176
|
+
})
|
|
177
|
+
})]
|
|
178
|
+
}, e);
|
|
179
|
+
})
|
|
175
180
|
})
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
181
|
+
]
|
|
182
|
+
})
|
|
183
|
+
]
|
|
179
184
|
});
|
|
180
185
|
};
|
|
181
186
|
//#endregion
|
|
182
|
-
export {
|
|
187
|
+
export { b as AIBookmarksPage };
|
|
183
188
|
|
|
184
189
|
//# sourceMappingURL=AIBookmarksPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIBookmarksPage.js","names":[],"sources":["../../../src/pages/ai/AIBookmarksPage.tsx"],"sourcesContent":["/**\n * Cross-session bookmarks view: lists every message the user has bookmarked\n * across all AI Chat sessions. Clicking a row navigates to the session and\n * the chat pane scrolls to the message via ?messageId=...\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { Bookmark, ChevronRight, Pencil, Trash2, Check, X as XIcon } from 'lucide-react';\nimport { relativeTime } from '@/utils/relativeTime';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport {\n useDeleteAgentMessageBookmarkMutation,\n useMyAgentMessageBookmarksQuery,\n useUpdateAgentMessageBookmarkMutation,\n} from '../../generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\n\nconst PAGE_SIZE = 100;\n\ninterface BookmarkRow {\n id: string;\n agentId: string;\n sessionId: string;\n messageId: string;\n label: string | null | undefined;\n createdAt: string | Date;\n}\n\nexport const AIBookmarksPage: FC = () => {\n const navigate = useNavigate();\n const tr = useTr();\n const { agents } = useCachedInitialData();\n const agentNameById = useMemo(() => new Map(agents.map((a) => [a.id, a.name ?? a.id])), [agents]);\n\n const { data, loading } = useMyAgentMessageBookmarksQuery({\n variables: { pagination: { limit: PAGE_SIZE, offset: 0 } },\n fetchPolicy: 'cache-and-network',\n });\n\n const [updateMutation] = useUpdateAgentMessageBookmarkMutation({\n refetchQueries: ['MyAgentMessageBookmarks'],\n });\n const [deleteMutation] = useDeleteAgentMessageBookmarkMutation({\n refetchQueries: ['MyAgentMessageBookmarks'],\n });\n\n const [editingId, setEditingId] = useState<string | null>(null);\n const [labelDraft, setLabelDraft] = useState('');\n\n const items = useMemo(\n () => (data?.myAgentMessageBookmarks.items ?? []) as BookmarkRow[],\n [data?.myAgentMessageBookmarks.items]\n );\n\n const grouped = useMemo(() => {\n const map = new Map<string, BookmarkRow[]>();\n for (const bm of items) {\n const arr = map.get(bm.sessionId) ?? [];\n arr.push(bm);\n map.set(bm.sessionId, arr);\n }\n return Array.from(map.entries());\n }, [items]);\n\n const startEdit = (id: string, current: string | null | undefined) => {\n setEditingId(id);\n setLabelDraft(current ?? '');\n };\n\n const saveEdit = async (id: string) => {\n await updateMutation({ variables: { id, input: { label: labelDraft || null } } });\n setEditingId(null);\n setLabelDraft('');\n };\n\n const removeBookmark = async (id: string) => {\n await deleteMutation({ variables: { id } });\n };\n\n const openInSession = (sessionId: string, messageId: string) => {\n navigate(`/vibecontrols/ai/sessions/${sessionId}?messageId=${messageId}`);\n };\n\n return (\n <div className=\"flex flex-col h-full min-h-0\" data-testid=\"ai-bookmarks-page\">\n <header className=\"px-6 py-4 border-b border-border-default flex-shrink-0\">\n <div className=\"flex items-center gap-2\">\n <Bookmark className=\"w-5 h-5 text-text-secondary\" />\n <h1 className=\"text-lg font-semibold text-text-primary\">\n {tr('vibecontrols.bookmarks.pageTitle', 'Saved messages')}\n </h1>\n </div>\n <p className=\"text-xs text-text-secondary mt-1\">\n {tr(\n 'vibecontrols.bookmarks.pageSubtitle',\n 'Every AI Chat message you have bookmarked across all sessions.'\n )}\n </p>\n </header>\n\n <div className=\"flex-1 min-h-0 overflow-y-auto\">\n {loading && items.length === 0 && (\n <div className=\"px-6 py-8 text-sm text-text-tertiary\">\n {tr('vibecontrols.common.loading', 'Loading...')}\n </div>\n )}\n\n {!loading && items.length === 0 && (\n <div className=\"px-6 py-16 flex flex-col items-center text-center gap-2\">\n <Bookmark className=\"w-10 h-10 text-text-muted\" />\n <p className=\"text-base font-medium text-text-primary\">\n {tr('vibecontrols.bookmarks.empty', \"You haven't bookmarked any messages yet\")}\n </p>\n <p className=\"text-xs text-text-secondary max-w-md\">\n {tr(\n 'vibecontrols.bookmarks.emptyHint',\n 'Open any AI Chat session and click the bookmark icon on a message to save it here.'\n )}\n </p>\n </div>\n )}\n\n <div className=\"divide-y divide-border-subtle\">\n {grouped.map(([sessionId, sessionBookmarks]) => {\n const agentId = sessionBookmarks[0]?.agentId ?? '';\n const agentName = agentNameById.get(agentId) ?? agentId;\n\n return (\n <section key={sessionId} className=\"px-6 py-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <div className=\"min-w-0\">\n <p className=\"text-xs font-mono text-text-tertiary truncate\">{sessionId}</p>\n <p className=\"text-sm font-medium text-text-primary\">{agentName}</p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/vibecontrols/ai/sessions/${sessionId}`)}\n className=\"text-xs text-action-primary-text hover:underline\"\n >\n {tr('vibecontrols.bookmarks.openInSession', 'Open session')}\n </button>\n </div>\n\n <ul className=\"space-y-1\">\n {sessionBookmarks.map((bm) => {\n const isEditing = editingId === bm.id;\n return (\n <li\n key={bm.id}\n className=\"rounded-md border border-border-subtle bg-bg-surface px-3 py-2 hover:border-action-primary-bg/30 transition-colors\"\n >\n <div className=\"flex items-start gap-2\">\n <div className=\"flex-1 min-w-0\">\n {isEditing ? (\n <input\n autoFocus\n value={labelDraft}\n onChange={(e) => setLabelDraft(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') void saveEdit(bm.id);\n if (e.key === 'Escape') {\n setEditingId(null);\n setLabelDraft('');\n }\n }}\n placeholder={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.labelPlaceholder',\n 'Add a label (optional)'\n )}\n className=\"w-full bg-bg-sunken border border-border-subtle rounded px-2 py-1 text-sm text-text-primary placeholder:text-text-tertiary focus:outline-none focus:border-action-primary-bg\"\n />\n ) : (\n <p className=\"text-sm text-text-primary truncate\">\n {bm.label || (\n <span className=\"text-text-tertiary italic\">\n {tr('vibecontrols.bookmarks.unlabeled', 'Unlabeled bookmark')}\n </span>\n )}\n </p>\n )}\n <p className=\"text-[10px] text-text-tertiary mt-1 font-mono truncate\">\n msg:{bm.messageId} · {relativeTime(bm.createdAt)}\n </p>\n </div>\n\n <div className=\"flex items-center gap-0.5 shrink-0\">\n {!isEditing && (\n <button\n type=\"button\"\n onClick={() => openInSession(bm.sessionId, bm.messageId)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n title={tr(\n 'vibecontrols.bookmarks.jumpToMessage',\n 'Jump to message in session'\n )}\n aria-label={tr(\n 'vibecontrols.bookmarks.jumpToMessage',\n 'Jump to message'\n )}\n >\n <ChevronRight className=\"w-3.5 h-3.5 text-text-secondary\" />\n </button>\n )}\n\n {isEditing ? (\n <>\n <button\n type=\"button\"\n onClick={() => void saveEdit(bm.id)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr('vibecontrols.common.save', 'Save')}\n >\n <Check className=\"w-3.5 h-3.5 text-status-success-text\" />\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setEditingId(null);\n setLabelDraft('');\n }}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr('vibecontrols.common.cancel', 'Cancel')}\n >\n <XIcon className=\"w-3.5 h-3.5 text-text-secondary\" />\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n onClick={() => startEdit(bm.id, bm.label)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.editLabel',\n 'Edit label'\n )}\n >\n <Pencil className=\"w-3.5 h-3.5 text-text-secondary\" />\n </button>\n )}\n\n <button\n type=\"button\"\n onClick={() => void removeBookmark(bm.id)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr(\n 'vibecontrols.agentManager.messageItem.removeBookmark',\n 'Remove bookmark'\n )}\n >\n <Trash2 className=\"w-3.5 h-3.5 text-status-danger-text\" />\n </button>\n </div>\n </div>\n </li>\n );\n })}\n </ul>\n </section>\n );\n })}\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AIBookmarksPage.js","names":[],"sources":["../../../src/pages/ai/AIBookmarksPage.tsx"],"sourcesContent":["/**\n * Cross-session bookmarks view: lists every message the user has bookmarked\n * across all AI Chat sessions. Clicking a row navigates to the session and\n * the chat pane scrolls to the message via ?messageId=...\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { Bookmark, ChevronRight, Pencil, Trash2, Check, X as XIcon } from 'lucide-react';\nimport { relativeTime } from '@/utils/relativeTime';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport {\n useDeleteAgentMessageBookmarkMutation,\n useMyAgentMessageBookmarksQuery,\n useUpdateAgentMessageBookmarkMutation,\n} from '../../generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { AiSectionTabs } from '@/components/ai/AiSectionTabs';\n\nconst PAGE_SIZE = 100;\n\ninterface BookmarkRow {\n id: string;\n agentId: string;\n sessionId: string;\n messageId: string;\n label: string | null | undefined;\n createdAt: string | Date;\n}\n\nexport const AIBookmarksPage: FC = () => {\n const navigate = useNavigate();\n const tr = useTr();\n const { agents } = useCachedInitialData();\n const agentNameById = useMemo(() => new Map(agents.map((a) => [a.id, a.name ?? a.id])), [agents]);\n\n const { data, loading } = useMyAgentMessageBookmarksQuery({\n variables: { pagination: { limit: PAGE_SIZE, offset: 0 } },\n fetchPolicy: 'cache-and-network',\n });\n\n const [updateMutation] = useUpdateAgentMessageBookmarkMutation({\n refetchQueries: ['MyAgentMessageBookmarks'],\n });\n const [deleteMutation] = useDeleteAgentMessageBookmarkMutation({\n refetchQueries: ['MyAgentMessageBookmarks'],\n });\n\n const [editingId, setEditingId] = useState<string | null>(null);\n const [labelDraft, setLabelDraft] = useState('');\n\n const items = useMemo(\n () => (data?.myAgentMessageBookmarks.items ?? []) as BookmarkRow[],\n [data?.myAgentMessageBookmarks.items]\n );\n\n const grouped = useMemo(() => {\n const map = new Map<string, BookmarkRow[]>();\n for (const bm of items) {\n const arr = map.get(bm.sessionId) ?? [];\n arr.push(bm);\n map.set(bm.sessionId, arr);\n }\n return Array.from(map.entries());\n }, [items]);\n\n const startEdit = (id: string, current: string | null | undefined) => {\n setEditingId(id);\n setLabelDraft(current ?? '');\n };\n\n const saveEdit = async (id: string) => {\n await updateMutation({ variables: { id, input: { label: labelDraft || null } } });\n setEditingId(null);\n setLabelDraft('');\n };\n\n const removeBookmark = async (id: string) => {\n await deleteMutation({ variables: { id } });\n };\n\n const openInSession = (sessionId: string, messageId: string) => {\n navigate(`/vibecontrols/ai/sessions/${sessionId}?messageId=${messageId}`);\n };\n\n return (\n <div className=\"flex flex-col h-full min-h-0\" data-testid=\"ai-bookmarks-page\">\n <AiSectionTabs active=\"bookmarks\" />\n <header className=\"px-6 py-4 border-b border-border-default flex-shrink-0\">\n <div className=\"flex items-center gap-2\">\n <Bookmark className=\"w-5 h-5 text-text-secondary\" />\n <h1 className=\"text-lg font-semibold text-text-primary\">\n {tr('vibecontrols.bookmarks.pageTitle', 'Saved messages')}\n </h1>\n </div>\n <p className=\"text-xs text-text-secondary mt-1\">\n {tr(\n 'vibecontrols.bookmarks.pageSubtitle',\n 'Every AI Chat message you have bookmarked across all sessions.'\n )}\n </p>\n </header>\n\n <div className=\"flex-1 min-h-0 overflow-y-auto\">\n {loading && items.length === 0 && (\n <div className=\"px-6 py-8 text-sm text-text-tertiary\">\n {tr('vibecontrols.common.loading', 'Loading...')}\n </div>\n )}\n\n {!loading && items.length === 0 && (\n <div className=\"px-6 py-16 flex flex-col items-center text-center gap-2\">\n <Bookmark className=\"w-10 h-10 text-text-muted\" />\n <p className=\"text-base font-medium text-text-primary\">\n {tr('vibecontrols.bookmarks.empty', \"You haven't bookmarked any messages yet\")}\n </p>\n <p className=\"text-xs text-text-secondary max-w-md\">\n {tr(\n 'vibecontrols.bookmarks.emptyHint',\n 'Open any AI Chat session and click the bookmark icon on a message to save it here.'\n )}\n </p>\n </div>\n )}\n\n <div className=\"divide-y divide-border-subtle\">\n {grouped.map(([sessionId, sessionBookmarks]) => {\n const agentId = sessionBookmarks[0]?.agentId ?? '';\n const agentName = agentNameById.get(agentId) ?? agentId;\n\n return (\n <section key={sessionId} className=\"px-6 py-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <div className=\"min-w-0\">\n <p className=\"text-xs font-mono text-text-tertiary truncate\">{sessionId}</p>\n <p className=\"text-sm font-medium text-text-primary\">{agentName}</p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/vibecontrols/ai/sessions/${sessionId}`)}\n className=\"text-xs text-action-primary-text hover:underline\"\n >\n {tr('vibecontrols.bookmarks.openInSession', 'Open session')}\n </button>\n </div>\n\n <ul className=\"space-y-1\">\n {sessionBookmarks.map((bm) => {\n const isEditing = editingId === bm.id;\n return (\n <li\n key={bm.id}\n className=\"rounded-md border border-border-subtle bg-bg-surface px-3 py-2 hover:border-action-primary-bg/30 transition-colors\"\n >\n <div className=\"flex items-start gap-2\">\n <div className=\"flex-1 min-w-0\">\n {isEditing ? (\n <input\n autoFocus\n value={labelDraft}\n onChange={(e) => setLabelDraft(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') void saveEdit(bm.id);\n if (e.key === 'Escape') {\n setEditingId(null);\n setLabelDraft('');\n }\n }}\n placeholder={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.labelPlaceholder',\n 'Add a label (optional)'\n )}\n className=\"w-full bg-bg-sunken border border-border-subtle rounded px-2 py-1 text-sm text-text-primary placeholder:text-text-tertiary focus:outline-none focus:border-action-primary-bg\"\n />\n ) : (\n <p className=\"text-sm text-text-primary truncate\">\n {bm.label || (\n <span className=\"text-text-tertiary italic\">\n {tr('vibecontrols.bookmarks.unlabeled', 'Unlabeled bookmark')}\n </span>\n )}\n </p>\n )}\n <p className=\"text-[10px] text-text-tertiary mt-1 font-mono truncate\">\n msg:{bm.messageId} · {relativeTime(bm.createdAt)}\n </p>\n </div>\n\n <div className=\"flex items-center gap-0.5 shrink-0\">\n {!isEditing && (\n <button\n type=\"button\"\n onClick={() => openInSession(bm.sessionId, bm.messageId)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n title={tr(\n 'vibecontrols.bookmarks.jumpToMessage',\n 'Jump to message in session'\n )}\n aria-label={tr(\n 'vibecontrols.bookmarks.jumpToMessage',\n 'Jump to message'\n )}\n >\n <ChevronRight className=\"w-3.5 h-3.5 text-text-secondary\" />\n </button>\n )}\n\n {isEditing ? (\n <>\n <button\n type=\"button\"\n onClick={() => void saveEdit(bm.id)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr('vibecontrols.common.save', 'Save')}\n >\n <Check className=\"w-3.5 h-3.5 text-status-success-text\" />\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setEditingId(null);\n setLabelDraft('');\n }}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr('vibecontrols.common.cancel', 'Cancel')}\n >\n <XIcon className=\"w-3.5 h-3.5 text-text-secondary\" />\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n onClick={() => startEdit(bm.id, bm.label)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr(\n 'vibecontrols.agentManager.bookmarksDrawer.editLabel',\n 'Edit label'\n )}\n >\n <Pencil className=\"w-3.5 h-3.5 text-text-secondary\" />\n </button>\n )}\n\n <button\n type=\"button\"\n onClick={() => void removeBookmark(bm.id)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr(\n 'vibecontrols.agentManager.messageItem.removeBookmark',\n 'Remove bookmark'\n )}\n >\n <Trash2 className=\"w-3.5 h-3.5 text-status-danger-text\" />\n </button>\n </div>\n </div>\n </li>\n );\n })}\n </ul>\n </section>\n );\n })}\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -20,157 +20,162 @@ import { AgentManagerSidebar as b } from "../../components/agent-manager/AgentMa
|
|
|
20
20
|
import { BridleConfigPanel as x } from "../../components/agent-manager/BridleConfigPanel.js";
|
|
21
21
|
import { AgentManagerCenter as S } from "../../components/agent-manager/AgentManagerCenter.js";
|
|
22
22
|
import { AgentManagerDetailPanel as C } from "../../components/agent-manager/AgentManagerDetailPanel.js";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
23
|
+
import { AiSectionTabs as w } from "../../components/ai/AiSectionTabs.js";
|
|
24
|
+
import { ClaudeCredentialsCard as T } from "../../components/ai/ClaudeCredentialsCard.js";
|
|
25
|
+
import { AIModeToggle as E } from "../../components/ai/AIModeToggle.js";
|
|
26
|
+
import { useAgentManagerKeyboard as D } from "../../hooks/useAgentManagerKeyboard.js";
|
|
27
|
+
import { Suspense as O, useCallback as k, useEffect as A, useMemo as j } from "react";
|
|
28
|
+
import { MessagesSquare as M } from "lucide-react";
|
|
29
|
+
import { useParams as N } from "react-router-dom";
|
|
30
|
+
import { jsx as P, jsxs as F } from "react/jsx-runtime";
|
|
31
|
+
import { MotionPage as I } from "@burdenoff/fe-libs/shared/motion";
|
|
31
32
|
//#region src/pages/ai/AgentManagerPage.tsx
|
|
32
|
-
function
|
|
33
|
-
let { tab:
|
|
34
|
-
if (
|
|
35
|
-
let e =
|
|
33
|
+
function L() {
|
|
34
|
+
let { tab: L, sessionId: R } = N(), z = e(), B = t(), { agents: V, loading: H, error: U } = r(), W = n((e) => e.activeAgentId), G = j(() => {
|
|
35
|
+
if (W) {
|
|
36
|
+
let e = V.find((e) => e.id === W);
|
|
36
37
|
if (e) return e;
|
|
37
38
|
}
|
|
38
|
-
return
|
|
39
|
-
}, [
|
|
40
|
-
id:
|
|
41
|
-
profile:
|
|
42
|
-
}), [
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
return V.find((e) => e.isActive && e.tunnelUrl && e.agentApiKey) ?? null;
|
|
40
|
+
}, [V, W]), K = G?.id ?? "", { active: q } = u(G?.id ?? null), J = j(() => ({
|
|
41
|
+
id: K,
|
|
42
|
+
profile: q
|
|
43
|
+
}), [K, q]), Y = v((e) => e.openSession), X = v((e) => e.activeTabSessionId), { initialLoading: Z } = l(j(() => [d, ...f], []), K || null, q);
|
|
44
|
+
A(() => {
|
|
45
|
+
R && K && Y(R, `Session ${R.slice(0, 8)}`);
|
|
45
46
|
}, [
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
R,
|
|
48
|
+
K,
|
|
49
|
+
Y
|
|
49
50
|
]);
|
|
50
|
-
let
|
|
51
|
-
if (
|
|
52
|
-
await _(
|
|
51
|
+
let Q = k(async (e) => {
|
|
52
|
+
if (K) try {
|
|
53
|
+
await _(J, e), o() && s("success");
|
|
53
54
|
} catch {
|
|
54
55
|
o() && s("error");
|
|
55
56
|
}
|
|
56
|
-
}, [
|
|
57
|
-
if (
|
|
58
|
-
await g(
|
|
57
|
+
}, [J]), $ = k(async (e) => {
|
|
58
|
+
if (K) try {
|
|
59
|
+
await g(J, e), o() && s("success");
|
|
59
60
|
} catch {
|
|
60
61
|
o() && s("error");
|
|
61
62
|
}
|
|
62
|
-
}, [
|
|
63
|
-
return
|
|
63
|
+
}, [J]);
|
|
64
|
+
return D(), H || K && Z ? /* @__PURE__ */ P(I, {
|
|
64
65
|
className: "flex items-center justify-center h-full bg-bg-surface",
|
|
65
|
-
children: /* @__PURE__ */
|
|
66
|
-
}) :
|
|
66
|
+
children: /* @__PURE__ */ P(i, { message: z("vibecontrols.pages.ai.detectingAgent", "Detecting agent and plugin availability…") })
|
|
67
|
+
}) : U ? /* @__PURE__ */ P(I, {
|
|
67
68
|
className: "flex items-center justify-center h-full bg-bg-surface",
|
|
68
|
-
children: /* @__PURE__ */
|
|
69
|
+
children: /* @__PURE__ */ P(a, {
|
|
69
70
|
title: "Failed to load agents",
|
|
70
|
-
message:
|
|
71
|
+
message: U.message
|
|
71
72
|
})
|
|
72
|
-
}) :
|
|
73
|
+
}) : V.length === 0 ? /* @__PURE__ */ P(I, {
|
|
73
74
|
className: "flex items-center justify-center h-full bg-bg-surface p-8",
|
|
74
|
-
children: /* @__PURE__ */
|
|
75
|
-
icon: /* @__PURE__ */
|
|
76
|
-
title:
|
|
77
|
-
description:
|
|
78
|
-
action: /* @__PURE__ */
|
|
79
|
-
onClick: () =>
|
|
75
|
+
children: /* @__PURE__ */ P(c, {
|
|
76
|
+
icon: /* @__PURE__ */ P(M, { className: "w-8 h-8 text-text-secondary" }),
|
|
77
|
+
title: z("vibecontrols.pages.ai.noActiveAgentAvailable", "No agents available"),
|
|
78
|
+
description: z("vibecontrols.pages.ai.agentManagerRequiresAgent", "The Agent Manager needs at least one vibecontrols agent. Add one or start an existing one."),
|
|
79
|
+
action: /* @__PURE__ */ P("button", {
|
|
80
|
+
onClick: () => B("/agents"),
|
|
80
81
|
className: "px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors text-sm",
|
|
81
|
-
children:
|
|
82
|
+
children: z("vibecontrols.pages.ai.goToAgents", "Go to Agents")
|
|
82
83
|
})
|
|
83
84
|
})
|
|
84
|
-
}) :
|
|
85
|
+
}) : L === "config" ? /* @__PURE__ */ F(I, {
|
|
85
86
|
className: "flex flex-col h-[calc(100vh-6.5rem)] bg-bg-surface overflow-hidden",
|
|
86
|
-
children: [/* @__PURE__ */
|
|
87
|
+
children: [/* @__PURE__ */ F("div", {
|
|
87
88
|
className: "flex items-center justify-between gap-3 border-b border-border-default px-4 sm:px-6 py-3",
|
|
88
|
-
children: [/* @__PURE__ */
|
|
89
|
+
children: [/* @__PURE__ */ F("div", { children: [/* @__PURE__ */ P("p", {
|
|
89
90
|
className: "text-xs uppercase tracking-wide text-text-secondary",
|
|
90
|
-
children:
|
|
91
|
-
}), /* @__PURE__ */
|
|
91
|
+
children: z("vibecontrols.pages.ai.contextLabel", "Active context")
|
|
92
|
+
}), /* @__PURE__ */ P("h2", {
|
|
92
93
|
className: "text-sm font-semibold text-text-primary mt-0.5",
|
|
93
|
-
children:
|
|
94
|
-
})] }), /* @__PURE__ */
|
|
94
|
+
children: z("vibecontrols.pages.ai.configHeading", "AI configuration")
|
|
95
|
+
})] }), /* @__PURE__ */ F("div", {
|
|
95
96
|
className: "flex items-center gap-3",
|
|
96
|
-
children: [/* @__PURE__ */
|
|
97
|
+
children: [/* @__PURE__ */ P(E, {
|
|
97
98
|
aiHref: "/ai",
|
|
98
99
|
activeMode: "ai"
|
|
99
|
-
}), /* @__PURE__ */
|
|
100
|
-
label:
|
|
100
|
+
}), /* @__PURE__ */ P(h, {
|
|
101
|
+
label: z("vibecontrols.pages.ai.agentSelectorLabel", "Agent"),
|
|
101
102
|
compact: !0
|
|
102
103
|
})]
|
|
103
104
|
})]
|
|
104
|
-
}), /* @__PURE__ */
|
|
105
|
+
}), /* @__PURE__ */ P("div", {
|
|
105
106
|
className: "flex-1 overflow-auto p-4 sm:p-6 space-y-6",
|
|
106
|
-
children: /* @__PURE__ */
|
|
107
|
-
agentId:
|
|
108
|
-
children: [/* @__PURE__ */
|
|
107
|
+
children: /* @__PURE__ */ F(m, {
|
|
108
|
+
agentId: K || null,
|
|
109
|
+
children: [/* @__PURE__ */ P(T, { agent: J }), /* @__PURE__ */ P(x, { agent: J })]
|
|
109
110
|
})
|
|
110
111
|
})]
|
|
111
|
-
}) : /* @__PURE__ */
|
|
112
|
+
}) : /* @__PURE__ */ F(I, {
|
|
112
113
|
className: "flex flex-col h-[calc(100vh-6.5rem)] bg-bg-surface overflow-hidden",
|
|
113
|
-
children: [
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
114
|
+
children: [
|
|
115
|
+
/* @__PURE__ */ F("div", {
|
|
116
|
+
className: "flex items-center justify-between gap-3 border-b border-border-default px-4 sm:px-6 py-3",
|
|
117
|
+
children: [/* @__PURE__ */ F("div", { children: [/* @__PURE__ */ P("p", {
|
|
118
|
+
className: "text-xs uppercase tracking-wide text-text-secondary",
|
|
119
|
+
children: z("vibecontrols.pages.ai.contextLabel", "Active context")
|
|
120
|
+
}), /* @__PURE__ */ P("h2", {
|
|
121
|
+
className: "text-sm font-semibold text-text-primary mt-0.5",
|
|
122
|
+
children: z("vibecontrols.pages.ai.contextHeading", "AI workbench")
|
|
123
|
+
})] }), /* @__PURE__ */ F("div", {
|
|
124
|
+
className: "flex items-center gap-3",
|
|
125
|
+
children: [/* @__PURE__ */ P(E, {
|
|
126
|
+
aiHref: "/ai",
|
|
127
|
+
activeMode: "ai"
|
|
128
|
+
}), /* @__PURE__ */ P(h, {
|
|
129
|
+
label: z("vibecontrols.pages.ai.agentSelectorLabel", "Agent"),
|
|
130
|
+
compact: !0
|
|
131
|
+
})]
|
|
129
132
|
})]
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
children: /* @__PURE__ */ N(y, {
|
|
156
|
-
sidebar: /* @__PURE__ */ N(b, {
|
|
157
|
-
agent: q,
|
|
158
|
-
onDeleteSession: Z,
|
|
159
|
-
onDeleteSelected: Q
|
|
133
|
+
}),
|
|
134
|
+
/* @__PURE__ */ P(w, { active: "workbench" }),
|
|
135
|
+
/* @__PURE__ */ P("div", {
|
|
136
|
+
className: "flex-1 min-h-0 overflow-auto p-4 sm:p-6 lg:p-0",
|
|
137
|
+
children: /* @__PURE__ */ P(m, {
|
|
138
|
+
agentId: K || null,
|
|
139
|
+
children: /* @__PURE__ */ P(p, {
|
|
140
|
+
agentId: K || null,
|
|
141
|
+
requirements: [{
|
|
142
|
+
packages: [d],
|
|
143
|
+
mode: "all",
|
|
144
|
+
eyebrow: z("vibecontrols.pages.ai.pluginRequiredEyebrow", "AI plugin required"),
|
|
145
|
+
title: z("vibecontrols.pages.ai.coreTitle", "Install the AI orchestrator"),
|
|
146
|
+
description: z("vibecontrols.pages.ai.coreDescription", "The AI page routes prompts and sessions through the AI orchestrator plugin on your agent. Install it to continue.")
|
|
147
|
+
}, {
|
|
148
|
+
packages: f,
|
|
149
|
+
mode: "any",
|
|
150
|
+
eyebrow: z("vibecontrols.pages.ai.providerEyebrow", "AI provider required"),
|
|
151
|
+
title: z("vibecontrols.pages.ai.providerTitle", "Install at least one AI provider"),
|
|
152
|
+
description: z("vibecontrols.pages.ai.providerDescription", "Pick the AI harness you want to use. You can install more providers later from the Plugins tab on your agent.")
|
|
153
|
+
}],
|
|
154
|
+
children: /* @__PURE__ */ P(O, {
|
|
155
|
+
fallback: /* @__PURE__ */ P("div", {
|
|
156
|
+
className: "flex items-center justify-center h-full",
|
|
157
|
+
children: /* @__PURE__ */ P(i, { message: "Loading..." })
|
|
160
158
|
}),
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
159
|
+
children: /* @__PURE__ */ P(y, {
|
|
160
|
+
sidebar: /* @__PURE__ */ P(b, {
|
|
161
|
+
agent: J,
|
|
162
|
+
onDeleteSession: Q,
|
|
163
|
+
onDeleteSelected: $
|
|
164
|
+
}),
|
|
165
|
+
detailPanel: X ? /* @__PURE__ */ P(C, {
|
|
166
|
+
sessionId: X,
|
|
167
|
+
agent: J
|
|
168
|
+
}) : void 0,
|
|
169
|
+
children: /* @__PURE__ */ P(S, { agent: J })
|
|
170
|
+
})
|
|
166
171
|
})
|
|
167
172
|
})
|
|
168
173
|
})
|
|
169
174
|
})
|
|
170
|
-
|
|
175
|
+
]
|
|
171
176
|
});
|
|
172
177
|
}
|
|
173
178
|
//#endregion
|
|
174
|
-
export {
|
|
179
|
+
export { L as AgentManagerPage };
|
|
175
180
|
|
|
176
181
|
//# sourceMappingURL=AgentManagerPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentManagerPage.js","names":[],"sources":["../../../src/pages/ai/AgentManagerPage.tsx"],"sourcesContent":["/**\n * AgentManagerPage — top-level page for the multi-session Agent Manager.\n *\n * Reads URL params (:tab, :sessionId), resolves the active agent via\n * useCachedInitialData(), auto-opens a session tab if the URL has a\n * sessionId, and renders the three-pane layout.\n */\n\nimport { Suspense, useCallback, useEffect, useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { MessagesSquare } from 'lucide-react';\nimport { isNative, nativeNotify } from '../../utils/nativeBridge';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport {\n LoadingState,\n EmptyState,\n ErrorState,\n PluginGate,\n AgentSelector,\n RequireAgentReachable,\n} from '@/components/shared';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { usePluginAvailability } from '@/hooks/usePluginAvailability';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { useAgentManagerKeyboard } from '@/hooks/useAgentManagerKeyboard';\nimport { terminateSession, bulkDeleteSessions } from '@/services/aiApi';\nimport { AgentManagerLayout } from '@/components/agent-manager/AgentManagerLayout';\nimport { AgentManagerSidebar } from '@/components/agent-manager/AgentManagerSidebar';\nimport { AgentManagerCenter } from '@/components/agent-manager/AgentManagerCenter';\nimport { AgentManagerDetailPanel } from '@/components/agent-manager/AgentManagerDetailPanel';\nimport { BridleConfigPanel } from '@/components/agent-manager/BridleConfigPanel';\nimport { ClaudeCredentialsCard } from '@/components/ai/ClaudeCredentialsCard';\nimport { AIModeToggle } from '@/components/ai/AIModeToggle';\nimport { AI_CORE_PACKAGE, AI_PROVIDER_PACKAGES } from '@/constants/pluginCatalog';\n\ninterface AgentManagerParams {\n tab?: string;\n sessionId?: string;\n}\n\nexport function AgentManagerPage() {\n const { tab: tabParam, sessionId: sessionIdParam } = useParams<\n AgentManagerParams & Record<string, string | undefined>\n >();\n const tr = useTr();\n const handleNavigate = useVibeNavigate();\n\n const { agents, loading: agentsLoading, error: agentsError } = useCachedInitialData();\n const selectedAgentId = useVibeControlsStore((s) => s.activeAgentId);\n\n // Prefer the user-selected agent from the store; fall back to the first\n // healthy one so the page works on first visit before the selector commits.\n const activeAgent = useMemo(() => {\n if (selectedAgentId) {\n const match = agents.find((a) => a.id === selectedAgentId);\n if (match) return match;\n }\n return agents.find((a) => a.isActive && a.tunnelUrl && a.agentApiKey) ?? null;\n }, [agents, selectedAgentId]);\n\n const agentId = activeAgent?.id ?? '';\n // AgentRef for canonical aiApi calls — agent → profile → plugins/data.\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n const openSession = useAgentManagerStore((s) => s.openSession);\n const activeTabSessionId = useAgentManagerStore((s) => s.activeTabSessionId);\n\n // Pre-fetch plugin availability at the page level so the detection phase\n // overlaps with the agents query's resolution. This collapses the two\n // sequential loading states (\"Loading agents...\" → \"Checking plugin\n // availability...\") into one unified wait, eliminating the flicker the\n // user saw between them.\n const detectionPackages = useMemo(() => [AI_CORE_PACKAGE, ...AI_PROVIDER_PACKAGES], []);\n const { initialLoading: pluginsInitialLoading } = usePluginAvailability(\n detectionPackages,\n agentId || null,\n activeProfile\n );\n\n // Auto-open session from URL param\n useEffect(() => {\n if (sessionIdParam && agentId) {\n openSession(sessionIdParam, `Session ${sessionIdParam.slice(0, 8)}`);\n }\n }, [sessionIdParam, agentId, openSession]);\n\n const handleDeleteSession = useCallback(\n async (sessionId: string) => {\n if (!agentId) return;\n try {\n await terminateSession(agentRef, sessionId);\n if (isNative()) void nativeNotify('success');\n } catch {\n if (isNative()) void nativeNotify('error');\n }\n },\n [agentRef]\n );\n\n const handleDeleteSelected = useCallback(\n async (sessionIds: string[]) => {\n if (!agentId) return;\n try {\n await bulkDeleteSessions(agentRef, sessionIds);\n if (isNative()) void nativeNotify('success');\n } catch {\n if (isNative()) void nativeNotify('error');\n }\n },\n [agentRef]\n );\n\n // Activate keyboard shortcuts (Ctrl+Shift+N, Ctrl+K, Ctrl+F, Escape)\n useAgentManagerKeyboard();\n\n // Single unified wait: show one LoadingState while either the agents\n // query or the plugin-availability probe is still in flight. Prevents\n // the two-step \"Loading agents...\" → \"Checking plugin availability...\"\n // cascade. `agentId && pluginsInitialLoading` also catches the case\n // where agents resolved but the plugin probe hasn't fired/settled yet.\n if (agentsLoading || (agentId && pluginsInitialLoading)) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full bg-bg-surface\">\n <LoadingState\n message={tr(\n 'vibecontrols.pages.ai.detectingAgent',\n 'Detecting agent and plugin availability…'\n )}\n />\n </MotionPage>\n );\n }\n\n if (agentsError) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full bg-bg-surface\">\n <ErrorState title=\"Failed to load agents\" message={agentsError.message} />\n </MotionPage>\n );\n }\n\n if (agents.length === 0) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full bg-bg-surface p-8\">\n <EmptyState\n icon={<MessagesSquare className=\"w-8 h-8 text-text-secondary\" />}\n title={tr('vibecontrols.pages.ai.noActiveAgentAvailable', 'No agents available')}\n description={tr(\n 'vibecontrols.pages.ai.agentManagerRequiresAgent',\n 'The Agent Manager needs at least one vibecontrols agent. Add one or start an existing one.'\n )}\n action={\n <button\n onClick={() => handleNavigate('/agents')}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors text-sm\"\n >\n {tr('vibecontrols.pages.ai.goToAgents', 'Go to Agents')}\n </button>\n }\n />\n </MotionPage>\n );\n }\n\n if (tabParam === 'config') {\n return (\n <MotionPage className=\"flex flex-col h-[calc(100vh-6.5rem)] bg-bg-surface overflow-hidden\">\n <div className=\"flex items-center justify-between gap-3 border-b border-border-default px-4 sm:px-6 py-3\">\n <div>\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">\n {tr('vibecontrols.pages.ai.contextLabel', 'Active context')}\n </p>\n <h2 className=\"text-sm font-semibold text-text-primary mt-0.5\">\n {tr('vibecontrols.pages.ai.configHeading', 'AI configuration')}\n </h2>\n </div>\n <div className=\"flex items-center gap-3\">\n <AIModeToggle aiHref=\"/ai\" activeMode=\"ai\" />\n <AgentSelector\n label={tr('vibecontrols.pages.ai.agentSelectorLabel', 'Agent')}\n compact\n />\n </div>\n </div>\n\n <div className=\"flex-1 overflow-auto p-4 sm:p-6 space-y-6\">\n <RequireAgentReachable agentId={agentId || null}>\n <ClaudeCredentialsCard agent={agentRef} />\n <BridleConfigPanel agent={agentRef} />\n </RequireAgentReachable>\n </div>\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"flex flex-col h-[calc(100vh-6.5rem)] bg-bg-surface overflow-hidden\">\n <div className=\"flex items-center justify-between gap-3 border-b border-border-default px-4 sm:px-6 py-3\">\n <div>\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">\n {tr('vibecontrols.pages.ai.contextLabel', 'Active context')}\n </p>\n <h2 className=\"text-sm font-semibold text-text-primary mt-0.5\">\n {tr('vibecontrols.pages.ai.contextHeading', 'AI workbench')}\n </h2>\n </div>\n <div className=\"flex items-center gap-3\">\n {/* Plan toggle navigates to the canonical /ai-tools/plan\n workspace; the AI side stays on this AgentManagerPage. */}\n <AIModeToggle aiHref=\"/ai\" activeMode=\"ai\" />\n <AgentSelector label={tr('vibecontrols.pages.ai.agentSelectorLabel', 'Agent')} compact />\n </div>\n </div>\n\n <div className=\"flex-1 min-h-0 overflow-auto p-4 sm:p-6 lg:p-0\">\n <RequireAgentReachable agentId={agentId || null}>\n <PluginGate\n agentId={agentId || null}\n requirements={[\n {\n packages: [AI_CORE_PACKAGE],\n mode: 'all',\n eyebrow: tr('vibecontrols.pages.ai.pluginRequiredEyebrow', 'AI plugin required'),\n title: tr('vibecontrols.pages.ai.coreTitle', 'Install the AI orchestrator'),\n description: tr(\n 'vibecontrols.pages.ai.coreDescription',\n 'The AI page routes prompts and sessions through the AI orchestrator plugin on your agent. Install it to continue.'\n ),\n },\n {\n packages: AI_PROVIDER_PACKAGES,\n mode: 'any',\n eyebrow: tr('vibecontrols.pages.ai.providerEyebrow', 'AI provider required'),\n title: tr(\n 'vibecontrols.pages.ai.providerTitle',\n 'Install at least one AI provider'\n ),\n description: tr(\n 'vibecontrols.pages.ai.providerDescription',\n 'Pick the AI harness you want to use. You can install more providers later from the Plugins tab on your agent.'\n ),\n },\n ]}\n >\n <Suspense\n fallback={\n <div className=\"flex items-center justify-center h-full\">\n <LoadingState message=\"Loading...\" />\n </div>\n }\n >\n <AgentManagerLayout\n sidebar={\n <AgentManagerSidebar\n agent={agentRef}\n onDeleteSession={handleDeleteSession}\n onDeleteSelected={handleDeleteSelected}\n />\n }\n detailPanel={\n activeTabSessionId ? (\n <AgentManagerDetailPanel sessionId={activeTabSessionId} agent={agentRef} />\n ) : undefined\n }\n >\n <AgentManagerCenter agent={agentRef} />\n </AgentManagerLayout>\n </Suspense>\n </PluginGate>\n </RequireAgentReachable>\n </div>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AgentManagerPage.js","names":[],"sources":["../../../src/pages/ai/AgentManagerPage.tsx"],"sourcesContent":["/**\n * AgentManagerPage — top-level page for the multi-session Agent Manager.\n *\n * Reads URL params (:tab, :sessionId), resolves the active agent via\n * useCachedInitialData(), auto-opens a session tab if the URL has a\n * sessionId, and renders the three-pane layout.\n */\n\nimport { Suspense, useCallback, useEffect, useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { MessagesSquare } from 'lucide-react';\nimport { isNative, nativeNotify } from '../../utils/nativeBridge';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport {\n LoadingState,\n EmptyState,\n ErrorState,\n PluginGate,\n AgentSelector,\n RequireAgentReachable,\n} from '@/components/shared';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { usePluginAvailability } from '@/hooks/usePluginAvailability';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { useAgentManagerKeyboard } from '@/hooks/useAgentManagerKeyboard';\nimport { terminateSession, bulkDeleteSessions } from '@/services/aiApi';\nimport { AgentManagerLayout } from '@/components/agent-manager/AgentManagerLayout';\nimport { AgentManagerSidebar } from '@/components/agent-manager/AgentManagerSidebar';\nimport { AgentManagerCenter } from '@/components/agent-manager/AgentManagerCenter';\nimport { AgentManagerDetailPanel } from '@/components/agent-manager/AgentManagerDetailPanel';\nimport { BridleConfigPanel } from '@/components/agent-manager/BridleConfigPanel';\nimport { ClaudeCredentialsCard } from '@/components/ai/ClaudeCredentialsCard';\nimport { AIModeToggle } from '@/components/ai/AIModeToggle';\nimport { AiSectionTabs } from '@/components/ai/AiSectionTabs';\nimport { AI_CORE_PACKAGE, AI_PROVIDER_PACKAGES } from '@/constants/pluginCatalog';\n\ninterface AgentManagerParams {\n tab?: string;\n sessionId?: string;\n}\n\nexport function AgentManagerPage() {\n const { tab: tabParam, sessionId: sessionIdParam } = useParams<\n AgentManagerParams & Record<string, string | undefined>\n >();\n const tr = useTr();\n const handleNavigate = useVibeNavigate();\n\n const { agents, loading: agentsLoading, error: agentsError } = useCachedInitialData();\n const selectedAgentId = useVibeControlsStore((s) => s.activeAgentId);\n\n // Prefer the user-selected agent from the store; fall back to the first\n // healthy one so the page works on first visit before the selector commits.\n const activeAgent = useMemo(() => {\n if (selectedAgentId) {\n const match = agents.find((a) => a.id === selectedAgentId);\n if (match) return match;\n }\n return agents.find((a) => a.isActive && a.tunnelUrl && a.agentApiKey) ?? null;\n }, [agents, selectedAgentId]);\n\n const agentId = activeAgent?.id ?? '';\n // AgentRef for canonical aiApi calls — agent → profile → plugins/data.\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n const openSession = useAgentManagerStore((s) => s.openSession);\n const activeTabSessionId = useAgentManagerStore((s) => s.activeTabSessionId);\n\n // Pre-fetch plugin availability at the page level so the detection phase\n // overlaps with the agents query's resolution. This collapses the two\n // sequential loading states (\"Loading agents...\" → \"Checking plugin\n // availability...\") into one unified wait, eliminating the flicker the\n // user saw between them.\n const detectionPackages = useMemo(() => [AI_CORE_PACKAGE, ...AI_PROVIDER_PACKAGES], []);\n const { initialLoading: pluginsInitialLoading } = usePluginAvailability(\n detectionPackages,\n agentId || null,\n activeProfile\n );\n\n // Auto-open session from URL param\n useEffect(() => {\n if (sessionIdParam && agentId) {\n openSession(sessionIdParam, `Session ${sessionIdParam.slice(0, 8)}`);\n }\n }, [sessionIdParam, agentId, openSession]);\n\n const handleDeleteSession = useCallback(\n async (sessionId: string) => {\n if (!agentId) return;\n try {\n await terminateSession(agentRef, sessionId);\n if (isNative()) void nativeNotify('success');\n } catch {\n if (isNative()) void nativeNotify('error');\n }\n },\n [agentRef]\n );\n\n const handleDeleteSelected = useCallback(\n async (sessionIds: string[]) => {\n if (!agentId) return;\n try {\n await bulkDeleteSessions(agentRef, sessionIds);\n if (isNative()) void nativeNotify('success');\n } catch {\n if (isNative()) void nativeNotify('error');\n }\n },\n [agentRef]\n );\n\n // Activate keyboard shortcuts (Ctrl+Shift+N, Ctrl+K, Ctrl+F, Escape)\n useAgentManagerKeyboard();\n\n // Single unified wait: show one LoadingState while either the agents\n // query or the plugin-availability probe is still in flight. Prevents\n // the two-step \"Loading agents...\" → \"Checking plugin availability...\"\n // cascade. `agentId && pluginsInitialLoading` also catches the case\n // where agents resolved but the plugin probe hasn't fired/settled yet.\n if (agentsLoading || (agentId && pluginsInitialLoading)) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full bg-bg-surface\">\n <LoadingState\n message={tr(\n 'vibecontrols.pages.ai.detectingAgent',\n 'Detecting agent and plugin availability…'\n )}\n />\n </MotionPage>\n );\n }\n\n if (agentsError) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full bg-bg-surface\">\n <ErrorState title=\"Failed to load agents\" message={agentsError.message} />\n </MotionPage>\n );\n }\n\n if (agents.length === 0) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full bg-bg-surface p-8\">\n <EmptyState\n icon={<MessagesSquare className=\"w-8 h-8 text-text-secondary\" />}\n title={tr('vibecontrols.pages.ai.noActiveAgentAvailable', 'No agents available')}\n description={tr(\n 'vibecontrols.pages.ai.agentManagerRequiresAgent',\n 'The Agent Manager needs at least one vibecontrols agent. Add one or start an existing one.'\n )}\n action={\n <button\n onClick={() => handleNavigate('/agents')}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors text-sm\"\n >\n {tr('vibecontrols.pages.ai.goToAgents', 'Go to Agents')}\n </button>\n }\n />\n </MotionPage>\n );\n }\n\n if (tabParam === 'config') {\n return (\n <MotionPage className=\"flex flex-col h-[calc(100vh-6.5rem)] bg-bg-surface overflow-hidden\">\n <div className=\"flex items-center justify-between gap-3 border-b border-border-default px-4 sm:px-6 py-3\">\n <div>\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">\n {tr('vibecontrols.pages.ai.contextLabel', 'Active context')}\n </p>\n <h2 className=\"text-sm font-semibold text-text-primary mt-0.5\">\n {tr('vibecontrols.pages.ai.configHeading', 'AI configuration')}\n </h2>\n </div>\n <div className=\"flex items-center gap-3\">\n <AIModeToggle aiHref=\"/ai\" activeMode=\"ai\" />\n <AgentSelector\n label={tr('vibecontrols.pages.ai.agentSelectorLabel', 'Agent')}\n compact\n />\n </div>\n </div>\n\n <div className=\"flex-1 overflow-auto p-4 sm:p-6 space-y-6\">\n <RequireAgentReachable agentId={agentId || null}>\n <ClaudeCredentialsCard agent={agentRef} />\n <BridleConfigPanel agent={agentRef} />\n </RequireAgentReachable>\n </div>\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"flex flex-col h-[calc(100vh-6.5rem)] bg-bg-surface overflow-hidden\">\n <div className=\"flex items-center justify-between gap-3 border-b border-border-default px-4 sm:px-6 py-3\">\n <div>\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">\n {tr('vibecontrols.pages.ai.contextLabel', 'Active context')}\n </p>\n <h2 className=\"text-sm font-semibold text-text-primary mt-0.5\">\n {tr('vibecontrols.pages.ai.contextHeading', 'AI workbench')}\n </h2>\n </div>\n <div className=\"flex items-center gap-3\">\n {/* Plan toggle navigates to the canonical /ai-tools/plan\n workspace; the AI side stays on this AgentManagerPage. */}\n <AIModeToggle aiHref=\"/ai\" activeMode=\"ai\" />\n <AgentSelector label={tr('vibecontrols.pages.ai.agentSelectorLabel', 'Agent')} compact />\n </div>\n </div>\n\n <AiSectionTabs active=\"workbench\" />\n\n <div className=\"flex-1 min-h-0 overflow-auto p-4 sm:p-6 lg:p-0\">\n <RequireAgentReachable agentId={agentId || null}>\n <PluginGate\n agentId={agentId || null}\n requirements={[\n {\n packages: [AI_CORE_PACKAGE],\n mode: 'all',\n eyebrow: tr('vibecontrols.pages.ai.pluginRequiredEyebrow', 'AI plugin required'),\n title: tr('vibecontrols.pages.ai.coreTitle', 'Install the AI orchestrator'),\n description: tr(\n 'vibecontrols.pages.ai.coreDescription',\n 'The AI page routes prompts and sessions through the AI orchestrator plugin on your agent. Install it to continue.'\n ),\n },\n {\n packages: AI_PROVIDER_PACKAGES,\n mode: 'any',\n eyebrow: tr('vibecontrols.pages.ai.providerEyebrow', 'AI provider required'),\n title: tr(\n 'vibecontrols.pages.ai.providerTitle',\n 'Install at least one AI provider'\n ),\n description: tr(\n 'vibecontrols.pages.ai.providerDescription',\n 'Pick the AI harness you want to use. You can install more providers later from the Plugins tab on your agent.'\n ),\n },\n ]}\n >\n <Suspense\n fallback={\n <div className=\"flex items-center justify-center h-full\">\n <LoadingState message=\"Loading...\" />\n </div>\n }\n >\n <AgentManagerLayout\n sidebar={\n <AgentManagerSidebar\n agent={agentRef}\n onDeleteSession={handleDeleteSession}\n onDeleteSelected={handleDeleteSelected}\n />\n }\n detailPanel={\n activeTabSessionId ? (\n <AgentManagerDetailPanel sessionId={activeTabSessionId} agent={agentRef} />\n ) : undefined\n }\n >\n <AgentManagerCenter agent={agentRef} />\n </AgentManagerLayout>\n </Suspense>\n </PluginGate>\n </RequireAgentReachable>\n </div>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|