total-recall-brain 3.1.2 → 3.2.1
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/extension/background.js +99 -0
- package/extension/content-script.css +16 -0
- package/extension/content-script.js +134 -0
- package/extension/icons/README.md +27 -0
- package/extension/icons/icon-128.png +0 -0
- package/extension/icons/icon-16.png +0 -0
- package/extension/icons/icon-48.png +0 -0
- package/extension/lib/brain-client.js +47 -0
- package/extension/manifest.json +39 -0
- package/extension/options/options.css +218 -0
- package/extension/options/options.html +72 -0
- package/extension/options/options.js +99 -0
- package/extension/popup/popup.css +221 -0
- package/extension/popup/popup.html +44 -0
- package/extension/popup/popup.js +107 -0
- package/extension/sidepanel/sidepanel.css +340 -0
- package/extension/sidepanel/sidepanel.html +38 -0
- package/extension/sidepanel/sidepanel.js +229 -0
- package/frontend/README.md +73 -0
- package/frontend/index.html +17 -0
- package/frontend/package.json +31 -0
- package/frontend/src/App.css +2 -0
- package/frontend/src/App.tsx +390 -0
- package/frontend/src/api.ts +688 -0
- package/frontend/src/assets/hero.png +0 -0
- package/frontend/src/assets/react.svg +1 -0
- package/frontend/src/assets/vite.svg +1 -0
- package/frontend/src/components/BrainSelector.tsx +272 -0
- package/frontend/src/components/DaemonLogsTab.tsx +54 -0
- package/frontend/src/components/Graph3D.tsx +850 -0
- package/frontend/src/components/MarkdownUtils.tsx +99 -0
- package/frontend/src/components/ResearchAgendaTab.tsx +707 -0
- package/frontend/src/components/TaskQueueTab.tsx +185 -0
- package/frontend/src/index.css +1589 -0
- package/frontend/src/main.tsx +10 -0
- package/frontend/src/pages/ApiKeysPage.tsx +288 -0
- package/frontend/src/pages/AutomationsPage.tsx +354 -0
- package/frontend/src/pages/ChatPage.tsx +806 -0
- package/frontend/src/pages/DeploymentsPage.tsx +275 -0
- package/frontend/src/pages/FilesPage.tsx +384 -0
- package/frontend/src/pages/HealthPage.tsx +91 -0
- package/frontend/src/pages/IntegrationsPage.tsx +382 -0
- package/frontend/src/pages/LoginPage.tsx +237 -0
- package/frontend/src/pages/MemoryPage.tsx +1192 -0
- package/frontend/src/pages/SandboxPage.tsx +113 -0
- package/frontend/src/pages/SettingsPage.tsx +662 -0
- package/frontend/src/pages/SkillsPage.tsx +618 -0
- package/frontend/src/pages/TasksPage.tsx +392 -0
- package/frontend/src/pages/UsagePage.tsx +260 -0
- package/frontend/src/types.ts +163 -0
- package/frontend/tsconfig.app.json +25 -0
- package/frontend/tsconfig.json +7 -0
- package/frontend/tsconfig.node.json +24 -0
- package/frontend/vite.config.ts +24 -0
- package/package.json +8 -1
- package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +35 -8
- package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +13 -8
- package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +28 -1
- package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +28 -1
- package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +830 -81
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
import React, { Fragment, useState, useRef } from 'react'
|
|
2
|
+
import { renderMarkdown, extractSources } from './MarkdownUtils'
|
|
3
|
+
import type { MemoryNode } from '../types'
|
|
4
|
+
import { patchResearch, deleteResearch, shareToApi } from '../api'
|
|
5
|
+
|
|
6
|
+
export interface ResearchItem {
|
|
7
|
+
id: string
|
|
8
|
+
topic: string
|
|
9
|
+
status: 'pending' | 'in_progress' | 'done' | 'failed' | 'paused'
|
|
10
|
+
priority: 'low' | 'medium' | 'high' | 'critical'
|
|
11
|
+
notes: string | null
|
|
12
|
+
node_slug?: string | null
|
|
13
|
+
research_phase?: string
|
|
14
|
+
created_at: string
|
|
15
|
+
updated_at: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ResearchAgendaTabProps {
|
|
19
|
+
researchItems: ResearchItem[]
|
|
20
|
+
loading: boolean
|
|
21
|
+
showResearchForm: boolean
|
|
22
|
+
setShowResearchForm: (v: boolean) => void
|
|
23
|
+
researchTopic: string
|
|
24
|
+
setResearchTopic: (v: string) => void
|
|
25
|
+
researchPriority: 'low' | 'medium' | 'high' | 'critical'
|
|
26
|
+
setResearchPriority: (v: 'low' | 'medium' | 'high' | 'critical') => void
|
|
27
|
+
researchNotes: string
|
|
28
|
+
setResearchNotes: (v: string) => void
|
|
29
|
+
researchSubmitting: boolean
|
|
30
|
+
handleCreateResearch: (e: React.FormEvent) => void
|
|
31
|
+
expandedResearchId: string | null
|
|
32
|
+
handleToggleExpand: (item: ResearchItem) => void
|
|
33
|
+
loadedDiscoveries: Record<string, MemoryNode | null>
|
|
34
|
+
loadingNodeSlugs: Record<string, boolean>
|
|
35
|
+
refreshResearch?: () => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default function ResearchAgendaTab(props: ResearchAgendaTabProps) {
|
|
39
|
+
const {
|
|
40
|
+
researchItems,
|
|
41
|
+
loading,
|
|
42
|
+
showResearchForm,
|
|
43
|
+
setShowResearchForm,
|
|
44
|
+
researchTopic,
|
|
45
|
+
setResearchTopic,
|
|
46
|
+
researchPriority,
|
|
47
|
+
setResearchPriority,
|
|
48
|
+
researchNotes,
|
|
49
|
+
setResearchNotes,
|
|
50
|
+
researchSubmitting,
|
|
51
|
+
handleCreateResearch,
|
|
52
|
+
expandedResearchId,
|
|
53
|
+
handleToggleExpand,
|
|
54
|
+
loadedDiscoveries,
|
|
55
|
+
loadingNodeSlugs,
|
|
56
|
+
refreshResearch,
|
|
57
|
+
} = props
|
|
58
|
+
|
|
59
|
+
const [steerTarget, setSteerTarget] = useState<string | null>(null)
|
|
60
|
+
const steerRef = useRef<HTMLTextAreaElement>(null)
|
|
61
|
+
const [expandedReports, setExpandedReports] = useState<Record<string, boolean>>({})
|
|
62
|
+
|
|
63
|
+
const handlePatch = async (id: string, updates: Record<string, unknown>) => {
|
|
64
|
+
try {
|
|
65
|
+
await patchResearch(id, updates)
|
|
66
|
+
refreshResearch?.()
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error('Failed to update research:', err)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const handleDelete = async (id: string) => {
|
|
73
|
+
if (!confirm('Cancel this research project?')) return
|
|
74
|
+
try {
|
|
75
|
+
await deleteResearch(id)
|
|
76
|
+
refreshResearch?.()
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error('Failed to delete research:', err)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const handleSteerSubmit = async () => {
|
|
83
|
+
if (!steerTarget || !steerRef.current) return
|
|
84
|
+
const direction = steerRef.current.value.trim()
|
|
85
|
+
if (!direction) return
|
|
86
|
+
try {
|
|
87
|
+
await patchResearch(steerTarget, { notes: direction })
|
|
88
|
+
setSteerTarget(null)
|
|
89
|
+
refreshResearch?.()
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error('Failed to steer research:', err)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div>
|
|
98
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
|
99
|
+
<h3 style={{ margin: 0 }}>Vector Research agenda</h3>
|
|
100
|
+
<button
|
|
101
|
+
className="btn btn-sm btn-ghost"
|
|
102
|
+
onClick={() => setShowResearchForm(!showResearchForm)}
|
|
103
|
+
style={{ borderColor: 'var(--border)' }}
|
|
104
|
+
>
|
|
105
|
+
{showResearchForm ? 'Cancel' : '+ Queue Research Topic'}
|
|
106
|
+
</button>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{/* Collapsible Research Form */}
|
|
110
|
+
{showResearchForm && (
|
|
111
|
+
<form onSubmit={handleCreateResearch} style={{
|
|
112
|
+
background: 'var(--bg-secondary)',
|
|
113
|
+
border: '1px solid var(--border)',
|
|
114
|
+
borderRadius: 12,
|
|
115
|
+
padding: 20,
|
|
116
|
+
marginBottom: 20,
|
|
117
|
+
display: 'flex',
|
|
118
|
+
flexDirection: 'column',
|
|
119
|
+
gap: 12
|
|
120
|
+
}}>
|
|
121
|
+
<div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: 16 }}>
|
|
122
|
+
<label style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12, color: 'var(--text-secondary)' }}>
|
|
123
|
+
Research Query / Topic
|
|
124
|
+
<input
|
|
125
|
+
type="text"
|
|
126
|
+
required
|
|
127
|
+
placeholder="e.g. Bun vs Node.js HTTP clustering performance benchmarks"
|
|
128
|
+
value={researchTopic}
|
|
129
|
+
onChange={e => setResearchTopic(e.target.value)}
|
|
130
|
+
style={{
|
|
131
|
+
padding: '8px 10px',
|
|
132
|
+
borderRadius: 8,
|
|
133
|
+
background: 'var(--bg-primary)',
|
|
134
|
+
color: 'var(--text-primary)',
|
|
135
|
+
border: '1px solid var(--border)',
|
|
136
|
+
outline: 'none'
|
|
137
|
+
}}
|
|
138
|
+
/>
|
|
139
|
+
</label>
|
|
140
|
+
<label style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12, color: 'var(--text-secondary)' }}>
|
|
141
|
+
Agenda Priority
|
|
142
|
+
<select
|
|
143
|
+
value={researchPriority}
|
|
144
|
+
onChange={e => setResearchPriority(e.target.value as 'low' | 'medium' | 'high' | 'critical')}
|
|
145
|
+
style={{
|
|
146
|
+
padding: '8px 10px',
|
|
147
|
+
borderRadius: 8,
|
|
148
|
+
background: 'var(--bg-primary)',
|
|
149
|
+
color: 'var(--text-primary)',
|
|
150
|
+
border: '1px solid var(--border)',
|
|
151
|
+
outline: 'none'
|
|
152
|
+
}}
|
|
153
|
+
>
|
|
154
|
+
<option value="low">Low</option>
|
|
155
|
+
<option value="medium">Medium</option>
|
|
156
|
+
<option value="high">High</option>
|
|
157
|
+
<option value="critical">Critical</option>
|
|
158
|
+
</select>
|
|
159
|
+
</label>
|
|
160
|
+
</div>
|
|
161
|
+
<label style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12, color: 'var(--text-secondary)' }}>
|
|
162
|
+
Target Gaps / Reference Notes
|
|
163
|
+
<textarea
|
|
164
|
+
placeholder="Identify specific APIs, libraries or cutoff dates..."
|
|
165
|
+
value={researchNotes}
|
|
166
|
+
onChange={e => setResearchNotes(e.target.value)}
|
|
167
|
+
style={{
|
|
168
|
+
padding: '10px',
|
|
169
|
+
borderRadius: 8,
|
|
170
|
+
background: 'var(--bg-primary)',
|
|
171
|
+
color: 'var(--text-primary)',
|
|
172
|
+
border: '1px solid var(--border)',
|
|
173
|
+
outline: 'none',
|
|
174
|
+
minHeight: 80,
|
|
175
|
+
resize: 'vertical'
|
|
176
|
+
}}
|
|
177
|
+
/>
|
|
178
|
+
</label>
|
|
179
|
+
<button
|
|
180
|
+
type="submit"
|
|
181
|
+
className="btn btn-primary"
|
|
182
|
+
disabled={researchSubmitting || !researchTopic.trim()}
|
|
183
|
+
style={{ alignSelf: 'flex-end', minWidth: 120 }}
|
|
184
|
+
>
|
|
185
|
+
{researchSubmitting ? 'Queueing...' : 'Add to Agenda'}
|
|
186
|
+
</button>
|
|
187
|
+
</form>
|
|
188
|
+
)}
|
|
189
|
+
|
|
190
|
+
{/* Research Agenda Table */}
|
|
191
|
+
<div style={{ overflowX: 'auto', border: '1px solid var(--border)', borderRadius: 12, background: 'var(--bg-secondary)' }}>
|
|
192
|
+
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
|
|
193
|
+
<thead>
|
|
194
|
+
<tr style={{ borderBottom: '1px solid var(--border)', color: 'var(--text-secondary)', textAlign: 'left', background: 'rgba(255, 255, 255, 0.02)' }}>
|
|
195
|
+
<th style={{ padding: '12px 16px', width: 40, textAlign: 'center' }}></th>
|
|
196
|
+
<th style={{ padding: '12px 16px' }}>Research Topic</th>
|
|
197
|
+
<th style={{ padding: '12px 16px', width: 100 }}>Priority</th>
|
|
198
|
+
<th style={{ padding: '12px 16px', width: 120 }}>Status</th>
|
|
199
|
+
<th style={{ padding: '12px 16px', width: 160 }}>Updated At</th>
|
|
200
|
+
</tr>
|
|
201
|
+
</thead>
|
|
202
|
+
<tbody>
|
|
203
|
+
{loading && researchItems.length === 0 ? (
|
|
204
|
+
<tr>
|
|
205
|
+
<td colSpan={5} style={{ textAlign: 'center', padding: 24, color: 'var(--text-tertiary)' }}>Loading agenda...</td>
|
|
206
|
+
</tr>
|
|
207
|
+
) : researchItems.length === 0 ? (
|
|
208
|
+
<tr>
|
|
209
|
+
<td colSpan={5} style={{ textAlign: 'center', padding: 40, color: 'var(--text-tertiary)' }}>
|
|
210
|
+
No active research topics queued.
|
|
211
|
+
</td>
|
|
212
|
+
</tr>
|
|
213
|
+
) : (
|
|
214
|
+
researchItems.map(item => {
|
|
215
|
+
const isExpanded = expandedResearchId === item.id;
|
|
216
|
+
const node = item.node_slug ? loadedDiscoveries[item.node_slug] : null;
|
|
217
|
+
const isNodeLoading = item.node_slug ? loadingNodeSlugs[item.node_slug] : false;
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<Fragment key={item.id}>
|
|
221
|
+
<tr
|
|
222
|
+
onClick={() => handleToggleExpand(item)}
|
|
223
|
+
style={{
|
|
224
|
+
borderBottom: isExpanded ? 'none' : '1px solid var(--border)',
|
|
225
|
+
cursor: 'pointer',
|
|
226
|
+
background: isExpanded ? 'rgba(255, 255, 255, 0.015)' : 'transparent',
|
|
227
|
+
transition: 'background 0.2s ease',
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
<td style={{ padding: '14px 16px', textAlign: 'center', color: 'var(--text-tertiary)' }}>
|
|
231
|
+
<span style={{
|
|
232
|
+
display: 'inline-block',
|
|
233
|
+
transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
|
|
234
|
+
transition: 'transform 0.2s',
|
|
235
|
+
fontSize: 10
|
|
236
|
+
}}>
|
|
237
|
+
▶
|
|
238
|
+
</span>
|
|
239
|
+
</td>
|
|
240
|
+
<td style={{ padding: '14px 16px' }}>
|
|
241
|
+
<div style={{ fontWeight: 600, color: 'var(--text-primary)' }}>{item.topic}</div>
|
|
242
|
+
{item.notes && (
|
|
243
|
+
<div style={{ fontSize: 12, color: 'var(--text-secondary)', marginTop: 4 }}>
|
|
244
|
+
{item.notes}
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
</td>
|
|
248
|
+
<td style={{ padding: '14px 16px' }}>
|
|
249
|
+
<span className={`badge ${
|
|
250
|
+
item.priority === 'critical' ? 'badge-error' :
|
|
251
|
+
item.priority === 'high' ? 'badge-accent' :
|
|
252
|
+
item.priority === 'medium' ? 'badge-warning' :
|
|
253
|
+
'badge-ghost'
|
|
254
|
+
}`} style={{ fontSize: 11, textTransform: 'capitalize' }}>
|
|
255
|
+
{item.priority}
|
|
256
|
+
</span>
|
|
257
|
+
</td>
|
|
258
|
+
<td style={{ padding: '14px 16px' }}>
|
|
259
|
+
<span className={`badge ${
|
|
260
|
+
(item.node_slug && (item.status === 'pending' || item.status === 'in_progress')) || item.status === 'done' ? 'badge-success' :
|
|
261
|
+
item.status === 'in_progress' ? 'badge-accent' :
|
|
262
|
+
item.status === 'failed' ? 'badge-error' :
|
|
263
|
+
'badge-warning'
|
|
264
|
+
}`} style={{ textTransform: 'capitalize', fontSize: 11 }}>
|
|
265
|
+
{item.node_slug && (item.status === 'pending' || item.status === 'in_progress') ? 'Researched' : item.status.replace('_', ' ')}
|
|
266
|
+
</span>
|
|
267
|
+
</td>
|
|
268
|
+
<td style={{ padding: '14px 16px', color: 'var(--text-tertiary)', fontSize: 12 }}>
|
|
269
|
+
{new Date(item.updated_at).toLocaleString([], { dateStyle: 'short', timeStyle: 'short' })}
|
|
270
|
+
</td>
|
|
271
|
+
</tr>
|
|
272
|
+
{isExpanded && (
|
|
273
|
+
<tr style={{ borderBottom: '1px solid var(--border)', background: 'rgba(255, 255, 255, 0.015)' }}>
|
|
274
|
+
<td colSpan={5} style={{ padding: '0 24px 20px 24px' }}>
|
|
275
|
+
<div style={{
|
|
276
|
+
border: '1px solid rgba(255, 255, 255, 0.06)',
|
|
277
|
+
borderRadius: 12,
|
|
278
|
+
background: 'rgba(10, 10, 15, 0.4)',
|
|
279
|
+
padding: 20,
|
|
280
|
+
boxShadow: 'inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 4px 20px rgba(0, 0, 0, 0.3)',
|
|
281
|
+
}}>
|
|
282
|
+
<style dangerouslySetInnerHTML={{__html: `
|
|
283
|
+
@keyframes pulse {
|
|
284
|
+
0% { transform: scale(0.95); opacity: 0.5; }
|
|
285
|
+
50% { transform: scale(1.05); opacity: 1; }
|
|
286
|
+
100% { transform: scale(0.95); opacity: 0.5; }
|
|
287
|
+
}
|
|
288
|
+
`}} />
|
|
289
|
+
|
|
290
|
+
{/* Cognitive Stepper */}
|
|
291
|
+
<div style={{ marginBottom: 20 }}>
|
|
292
|
+
<div style={{ fontSize: 11, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600, marginBottom: 12 }}>
|
|
293
|
+
Cognitive Research Engine Progress
|
|
294
|
+
</div>
|
|
295
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 12 }}>
|
|
296
|
+
{[
|
|
297
|
+
{
|
|
298
|
+
title: 'Data Acquisition',
|
|
299
|
+
phase: 'acquisition',
|
|
300
|
+
icon: '🔍',
|
|
301
|
+
desc: 'Deep multi-source crawling & LLM synthesis'
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
title: 'Cognitive Deliberation',
|
|
305
|
+
phase: 'deliberation',
|
|
306
|
+
icon: '🧠',
|
|
307
|
+
desc: 'System 2 context evaluation & insights'
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
title: 'Clarity Auditing',
|
|
311
|
+
phase: 'improvement',
|
|
312
|
+
icon: '✨',
|
|
313
|
+
desc: 'Markdown polish & premium layout restructuring'
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
title: 'Source Monitoring',
|
|
317
|
+
phase: 'monitoring',
|
|
318
|
+
icon: '📡',
|
|
319
|
+
desc: 'Ongoing newsletter, RSS & release tracking'
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
title: 'Tangent Spawning',
|
|
323
|
+
phase: 'expansion',
|
|
324
|
+
icon: '🚀',
|
|
325
|
+
desc: 'Autonomously brainstorming & enqueuing tangents'
|
|
326
|
+
}
|
|
327
|
+
].map((step, idx) => {
|
|
328
|
+
const phases = ['acquisition', 'deliberation', 'improvement', 'monitoring', 'expansion']
|
|
329
|
+
const activePhase = item.research_phase || 'acquisition'
|
|
330
|
+
const activeIndex = phases.indexOf(activePhase)
|
|
331
|
+
|
|
332
|
+
let state: 'completed' | 'active' | 'waiting' | 'failed' | 'upcoming' = 'upcoming'
|
|
333
|
+
if (idx < activeIndex) {
|
|
334
|
+
state = 'completed'
|
|
335
|
+
} else if (idx === activeIndex) {
|
|
336
|
+
if (item.status === 'in_progress') state = 'active'
|
|
337
|
+
else if (item.status === 'pending') state = 'waiting'
|
|
338
|
+
else if (item.status === 'failed') state = 'failed'
|
|
339
|
+
else state = 'waiting'
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
let borderColor = 'rgba(255, 255, 255, 0.04)'
|
|
343
|
+
let bgColor = 'rgba(255, 255, 255, 0.01)'
|
|
344
|
+
let titleColor = 'var(--text-secondary)'
|
|
345
|
+
let badgeBg = 'rgba(255, 255, 255, 0.02)'
|
|
346
|
+
let statusText = 'Locked'
|
|
347
|
+
let statusColor = 'var(--text-tertiary)'
|
|
348
|
+
let isPulse = false
|
|
349
|
+
|
|
350
|
+
if (state === 'completed') {
|
|
351
|
+
borderColor = 'rgba(46, 204, 113, 0.25)'
|
|
352
|
+
bgColor = 'rgba(46, 204, 113, 0.02)'
|
|
353
|
+
titleColor = '#fff'
|
|
354
|
+
badgeBg = 'rgba(46, 204, 113, 0.12)'
|
|
355
|
+
statusText = '✓ Completed'
|
|
356
|
+
statusColor = '#2ecc71'
|
|
357
|
+
} else if (state === 'active') {
|
|
358
|
+
borderColor = 'rgba(108, 92, 231, 0.45)'
|
|
359
|
+
bgColor = 'rgba(108, 92, 231, 0.05)'
|
|
360
|
+
titleColor = '#fff'
|
|
361
|
+
badgeBg = 'rgba(108, 92, 231, 0.18)'
|
|
362
|
+
statusText = '⚡ Running'
|
|
363
|
+
statusColor = 'var(--accent)'
|
|
364
|
+
isPulse = true
|
|
365
|
+
} else if (state === 'waiting') {
|
|
366
|
+
borderColor = 'rgba(241, 196, 15, 0.25)'
|
|
367
|
+
bgColor = 'rgba(241, 196, 15, 0.02)'
|
|
368
|
+
titleColor = 'var(--text-primary)'
|
|
369
|
+
badgeBg = 'rgba(241, 196, 15, 0.1)'
|
|
370
|
+
statusText = '⏳ Queued'
|
|
371
|
+
statusColor = '#f1c40f'
|
|
372
|
+
} else if (state === 'failed') {
|
|
373
|
+
borderColor = 'rgba(231, 76, 60, 0.25)'
|
|
374
|
+
bgColor = 'rgba(231, 76, 60, 0.02)'
|
|
375
|
+
titleColor = 'var(--text-primary)'
|
|
376
|
+
badgeBg = 'rgba(231, 76, 60, 0.1)'
|
|
377
|
+
statusText = '⚠️ Failed'
|
|
378
|
+
statusColor = '#e74c3c'
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
<div key={idx} style={{
|
|
383
|
+
border: `1px solid ${borderColor}`,
|
|
384
|
+
background: bgColor,
|
|
385
|
+
borderRadius: 10,
|
|
386
|
+
padding: 12,
|
|
387
|
+
display: 'flex',
|
|
388
|
+
flexDirection: 'column',
|
|
389
|
+
gap: 6,
|
|
390
|
+
transition: 'all 0.3s ease',
|
|
391
|
+
position: 'relative'
|
|
392
|
+
}}>
|
|
393
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
394
|
+
<span style={{
|
|
395
|
+
display: 'inline-flex',
|
|
396
|
+
alignItems: 'center',
|
|
397
|
+
justifyContent: 'center',
|
|
398
|
+
width: 24,
|
|
399
|
+
height: 24,
|
|
400
|
+
borderRadius: '50%',
|
|
401
|
+
background: badgeBg,
|
|
402
|
+
fontSize: 12
|
|
403
|
+
}}>
|
|
404
|
+
{step.icon}
|
|
405
|
+
</span>
|
|
406
|
+
<span style={{
|
|
407
|
+
fontSize: 10,
|
|
408
|
+
fontWeight: 700,
|
|
409
|
+
color: statusColor,
|
|
410
|
+
display: 'inline-flex',
|
|
411
|
+
alignItems: 'center',
|
|
412
|
+
gap: 4
|
|
413
|
+
}}>
|
|
414
|
+
{isPulse && (
|
|
415
|
+
<span style={{
|
|
416
|
+
width: 6,
|
|
417
|
+
height: 6,
|
|
418
|
+
borderRadius: '50%',
|
|
419
|
+
background: 'var(--accent)',
|
|
420
|
+
animation: 'pulse 1.5s infinite ease-in-out'
|
|
421
|
+
}} />
|
|
422
|
+
)}
|
|
423
|
+
{statusText}
|
|
424
|
+
</span>
|
|
425
|
+
</div>
|
|
426
|
+
<div style={{ fontSize: 12, fontWeight: 600, color: titleColor }}>
|
|
427
|
+
{idx + 1}. {step.title}
|
|
428
|
+
</div>
|
|
429
|
+
<div style={{ fontSize: 10, color: 'var(--text-tertiary)', lineHeight: 1.3 }}>
|
|
430
|
+
{step.desc}
|
|
431
|
+
</div>
|
|
432
|
+
</div>
|
|
433
|
+
)
|
|
434
|
+
})}
|
|
435
|
+
</div>
|
|
436
|
+
</div>
|
|
437
|
+
|
|
438
|
+
<hr style={{ border: 'none', borderTop: '1px solid rgba(255, 255, 255, 0.06)', margin: '20px 0' }} />
|
|
439
|
+
|
|
440
|
+
{/* Lifecycle Control Buttons */}
|
|
441
|
+
<div style={{ display: 'flex', gap: '8px', padding: '0 0 16px', flexWrap: 'wrap' }}>
|
|
442
|
+
{item.status === 'in_progress' && (
|
|
443
|
+
<button className="btn btn-sm" onClick={(e) => { e.stopPropagation(); handlePatch(item.id, { status: 'paused' }) }}>⏸ Pause</button>
|
|
444
|
+
)}
|
|
445
|
+
{(item.status === 'paused' || item.status === 'failed') && (
|
|
446
|
+
<button className="btn btn-sm" onClick={(e) => { e.stopPropagation(); handlePatch(item.id, { status: 'pending' }) }}>▶️ Resume</button>
|
|
447
|
+
)}
|
|
448
|
+
{item.status === 'done' && (
|
|
449
|
+
<button className="btn btn-sm" onClick={(e) => { e.stopPropagation(); handlePatch(item.id, { status: 'pending', research_phase: 'acquisition' }) }}>🔄 Re-run</button>
|
|
450
|
+
)}
|
|
451
|
+
<button className="btn btn-sm" onClick={(e) => { e.stopPropagation(); setSteerTarget(item.id) }}>🎯 Steer</button>
|
|
452
|
+
{item.status !== 'done' && (
|
|
453
|
+
<button className="btn btn-sm" onClick={(e) => { e.stopPropagation(); handlePatch(item.id, { status: 'done' }) }}>✅ Conclude</button>
|
|
454
|
+
)}
|
|
455
|
+
<button className="btn btn-sm" style={{ borderColor: 'var(--error)', color: 'var(--error)' }} onClick={(e) => { e.stopPropagation(); handleDelete(item.id) }}>❌ Cancel</button>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
{item.node_slug ? (
|
|
459
|
+
<>
|
|
460
|
+
{isNodeLoading ? (
|
|
461
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 10, color: 'var(--text-secondary)' }}>
|
|
462
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" style={{ animation: 'spin 1s linear infinite' }}>
|
|
463
|
+
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
|
|
464
|
+
</svg>
|
|
465
|
+
<span>Reading memory node and cataloging discoveries...</span>
|
|
466
|
+
</div>
|
|
467
|
+
) : node ? (
|
|
468
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
469
|
+
{item.status === 'in_progress' && (
|
|
470
|
+
<div style={{
|
|
471
|
+
display: 'flex',
|
|
472
|
+
alignItems: 'center',
|
|
473
|
+
gap: 8,
|
|
474
|
+
background: 'rgba(108, 92, 231, 0.1)',
|
|
475
|
+
border: '1px solid rgba(108, 92, 231, 0.2)',
|
|
476
|
+
borderRadius: 8,
|
|
477
|
+
padding: '8px 12px',
|
|
478
|
+
fontSize: 12,
|
|
479
|
+
color: 'var(--accent)',
|
|
480
|
+
marginBottom: 4
|
|
481
|
+
}}>
|
|
482
|
+
<span className="pulse" style={{ background: 'var(--accent)', width: 8, height: 8, borderRadius: '50%', display: 'inline-block' }} />
|
|
483
|
+
<span>Fact-seeker agents are currently updating this topic in the background. Previous discoveries are displayed below.</span>
|
|
484
|
+
</div>
|
|
485
|
+
)}
|
|
486
|
+
{item.status === 'pending' && (
|
|
487
|
+
<div style={{
|
|
488
|
+
display: 'flex',
|
|
489
|
+
alignItems: 'center',
|
|
490
|
+
gap: 8,
|
|
491
|
+
background: 'rgba(241, 196, 15, 0.1)',
|
|
492
|
+
border: '1px solid rgba(241, 196, 15, 0.2)',
|
|
493
|
+
borderRadius: 8,
|
|
494
|
+
padding: '8px 12px',
|
|
495
|
+
fontSize: 12,
|
|
496
|
+
color: 'var(--warning)',
|
|
497
|
+
marginBottom: 4
|
|
498
|
+
}}>
|
|
499
|
+
<span>⏳</span>
|
|
500
|
+
<span>Scheduled for automatic refresh. Previous discoveries are displayed below.</span>
|
|
501
|
+
</div>
|
|
502
|
+
)}
|
|
503
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', flexWrap: 'wrap', gap: 12, borderBottom: '1px solid rgba(255, 255, 255, 0.06)', paddingBottom: 12 }}>
|
|
504
|
+
<div>
|
|
505
|
+
<h4 style={{ margin: 0, fontSize: 15, fontWeight: 700, color: '#fff' }}>{node.title}</h4>
|
|
506
|
+
<div style={{ display: 'flex', gap: 8, marginTop: 6, alignItems: 'center' }}>
|
|
507
|
+
<span className="badge badge-accent" style={{ fontSize: 10, padding: '2px 8px' }}>{node.category}</span>
|
|
508
|
+
{node.modality && <span className="badge" style={{ fontSize: 10, padding: '2px 8px', background: 'rgba(255,255,255,0.06)', color: 'var(--text-secondary)' }}>{node.modality}</span>}
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
|
|
512
|
+
<div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
|
|
513
|
+
{node.importance !== undefined && (
|
|
514
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 4, background: 'rgba(255, 255, 255, 0.03)', padding: '4px 8px', borderRadius: 6, fontSize: 11, color: 'var(--text-secondary)', border: '1px solid rgba(255,255,255,0.04)' }}>
|
|
515
|
+
<span>⭐ Importance:</span>
|
|
516
|
+
<span style={{ fontWeight: 600, color: '#fff' }}>{node.importance}/5</span>
|
|
517
|
+
</div>
|
|
518
|
+
)}
|
|
519
|
+
{node.confidence !== undefined && (
|
|
520
|
+
<div style={{
|
|
521
|
+
display: 'flex',
|
|
522
|
+
alignItems: 'center',
|
|
523
|
+
gap: 4,
|
|
524
|
+
background: node.confidence >= 0.9 ? 'rgba(0, 206, 201, 0.08)' : 'rgba(108, 92, 231, 0.08)',
|
|
525
|
+
border: `1px solid ${node.confidence >= 0.9 ? 'rgba(0, 206, 201, 0.3)' : 'rgba(108, 92, 231, 0.3)'}`,
|
|
526
|
+
borderRadius: 6,
|
|
527
|
+
padding: '4px 8px',
|
|
528
|
+
fontSize: 11,
|
|
529
|
+
color: node.confidence >= 0.9 ? 'var(--success)' : 'var(--accent)'
|
|
530
|
+
}}>
|
|
531
|
+
<span>✓ Confidence:</span>
|
|
532
|
+
<span style={{ fontWeight: 600 }}>{(node.confidence * 100).toFixed(0)}%</span>
|
|
533
|
+
</div>
|
|
534
|
+
)}
|
|
535
|
+
</div>
|
|
536
|
+
</div>
|
|
537
|
+
|
|
538
|
+
<div style={{ position: 'relative' }}>
|
|
539
|
+
<div style={{
|
|
540
|
+
maxHeight: expandedReports[item.id] ? 'none' : 500,
|
|
541
|
+
overflow: 'hidden',
|
|
542
|
+
paddingRight: 8,
|
|
543
|
+
transition: 'max-height 0.3s ease',
|
|
544
|
+
}}>
|
|
545
|
+
<div style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
|
|
546
|
+
{renderMarkdown(node.content || node.body || '')}
|
|
547
|
+
</div>
|
|
548
|
+
</div>
|
|
549
|
+
{!expandedReports[item.id] && (node.content || node.body || '').length > 800 && (
|
|
550
|
+
<div style={{
|
|
551
|
+
position: 'absolute',
|
|
552
|
+
bottom: 0,
|
|
553
|
+
left: 0,
|
|
554
|
+
right: 0,
|
|
555
|
+
height: 80,
|
|
556
|
+
background: 'linear-gradient(transparent, rgba(10, 10, 15, 0.95))',
|
|
557
|
+
pointerEvents: 'none',
|
|
558
|
+
}} />
|
|
559
|
+
)}
|
|
560
|
+
</div>
|
|
561
|
+
{(node.content || node.body || '').length > 800 && (
|
|
562
|
+
<button
|
|
563
|
+
className="btn btn-sm btn-ghost"
|
|
564
|
+
onClick={(e) => {
|
|
565
|
+
e.stopPropagation()
|
|
566
|
+
setExpandedReports(prev => ({ ...prev, [item.id]: !prev[item.id] }))
|
|
567
|
+
}}
|
|
568
|
+
style={{ alignSelf: 'flex-start', marginTop: 8, fontSize: 12, color: 'var(--accent)' }}
|
|
569
|
+
>
|
|
570
|
+
{expandedReports[item.id] ? '▲ Show less' : '▼ Show more'}
|
|
571
|
+
</button>
|
|
572
|
+
)}
|
|
573
|
+
|
|
574
|
+
{extractSources(node.content || node.body).length > 0 && (
|
|
575
|
+
<div style={{ borderTop: '1px solid rgba(255, 255, 255, 0.06)', paddingTop: 12 }}>
|
|
576
|
+
<div style={{ fontSize: 11, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: '0.04em', fontWeight: 600, marginBottom: 8 }}>Sources & Citations</div>
|
|
577
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
578
|
+
{extractSources(node.content || node.body).map((src, idx) => {
|
|
579
|
+
const domain = (() => { try { return new URL(src.url).hostname.replace(/^www\./, '') } catch { return src.url } })()
|
|
580
|
+
return (
|
|
581
|
+
<div
|
|
582
|
+
key={idx}
|
|
583
|
+
style={{
|
|
584
|
+
display: 'flex',
|
|
585
|
+
alignItems: 'center',
|
|
586
|
+
gap: 10,
|
|
587
|
+
background: 'rgba(108, 92, 231, 0.06)',
|
|
588
|
+
border: '1px solid rgba(108, 92, 231, 0.2)',
|
|
589
|
+
borderRadius: 10,
|
|
590
|
+
padding: '8px 14px',
|
|
591
|
+
fontSize: 12,
|
|
592
|
+
transition: 'all 0.2s',
|
|
593
|
+
}}
|
|
594
|
+
>
|
|
595
|
+
<img
|
|
596
|
+
src={`https://www.google.com/s2/favicons?domain=${domain}&sz=16`}
|
|
597
|
+
width={16}
|
|
598
|
+
height={16}
|
|
599
|
+
alt=""
|
|
600
|
+
style={{ borderRadius: 2, flexShrink: 0 }}
|
|
601
|
+
/>
|
|
602
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 2, flex: 1, minWidth: 0 }}>
|
|
603
|
+
<span style={{ fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{src.text}</span>
|
|
604
|
+
<span style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>{domain}</span>
|
|
605
|
+
</div>
|
|
606
|
+
<a
|
|
607
|
+
href={src.url}
|
|
608
|
+
target="_blank"
|
|
609
|
+
rel="noopener noreferrer"
|
|
610
|
+
className="btn btn-sm"
|
|
611
|
+
style={{ fontSize: 11, padding: '3px 10px', textDecoration: 'none', flexShrink: 0 }}
|
|
612
|
+
onClick={(e) => e.stopPropagation()}
|
|
613
|
+
>
|
|
614
|
+
Open
|
|
615
|
+
</a>
|
|
616
|
+
<button
|
|
617
|
+
className="btn btn-sm"
|
|
618
|
+
style={{ fontSize: 11, padding: '3px 10px', flexShrink: 0 }}
|
|
619
|
+
onClick={(e) => {
|
|
620
|
+
e.stopPropagation()
|
|
621
|
+
shareToApi({ url: src.url, action: 'research', title: src.text }).catch(console.error)
|
|
622
|
+
}}
|
|
623
|
+
>
|
|
624
|
+
🔬 Research deeper
|
|
625
|
+
</button>
|
|
626
|
+
</div>
|
|
627
|
+
)
|
|
628
|
+
})}
|
|
629
|
+
</div>
|
|
630
|
+
</div>
|
|
631
|
+
)}
|
|
632
|
+
</div>
|
|
633
|
+
) : (
|
|
634
|
+
<div style={{ color: 'var(--text-tertiary)', fontSize: 13 }}>
|
|
635
|
+
No memory synthesis node found for slug: <code>{item.node_slug}</code>
|
|
636
|
+
</div>
|
|
637
|
+
)}
|
|
638
|
+
</>
|
|
639
|
+
) : (
|
|
640
|
+
<>
|
|
641
|
+
{item.status === 'pending' && (
|
|
642
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
643
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--warning)', fontWeight: 600, fontSize: 14 }}>
|
|
644
|
+
<span>⏳</span> Queued for Ingestion
|
|
645
|
+
</div>
|
|
646
|
+
<p style={{ color: 'var(--text-secondary)', fontSize: 13, margin: 0 }}>
|
|
647
|
+
This research topic is enqueued in the Cognitive Scheduler. The background fact-seeker daemon will systematically synthesize findings and catalog discoveries when processing background gaps.
|
|
648
|
+
</p>
|
|
649
|
+
</div>
|
|
650
|
+
)}
|
|
651
|
+
|
|
652
|
+
{item.status === 'in_progress' && (
|
|
653
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
654
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--accent)', fontWeight: 600, fontSize: 14 }}>
|
|
655
|
+
<span className="pulse" style={{ background: 'var(--accent)' }} />
|
|
656
|
+
Synthesizing Deep Discoveries...
|
|
657
|
+
</div>
|
|
658
|
+
<p style={{ color: 'var(--text-secondary)', fontSize: 13, margin: 0 }}>
|
|
659
|
+
Fact-seeker agents are actively searching vector indexes, crawling target APIs, and structuring new domain schemas. Results will be verified by the System 2 consensus writer.
|
|
660
|
+
</p>
|
|
661
|
+
</div>
|
|
662
|
+
)}
|
|
663
|
+
|
|
664
|
+
{item.status === 'failed' && (
|
|
665
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
666
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--error)', fontWeight: 600, fontSize: 14 }}>
|
|
667
|
+
<span>⚠️</span> Ingestion Boundary Encountered
|
|
668
|
+
</div>
|
|
669
|
+
<p style={{ color: 'var(--text-secondary)', fontSize: 13, margin: 0 }}>
|
|
670
|
+
The research pipeline failed to produce high-confidence consensus conclusions or hit an instruction boundary. Check live cognitive console logs for execution specifics.
|
|
671
|
+
</p>
|
|
672
|
+
</div>
|
|
673
|
+
)}
|
|
674
|
+
</>
|
|
675
|
+
)}
|
|
676
|
+
</div>
|
|
677
|
+
</td>
|
|
678
|
+
</tr>
|
|
679
|
+
)}
|
|
680
|
+
</Fragment>
|
|
681
|
+
)
|
|
682
|
+
})
|
|
683
|
+
)}
|
|
684
|
+
</tbody>
|
|
685
|
+
</table>
|
|
686
|
+
</div>
|
|
687
|
+
|
|
688
|
+
{/* Steer Modal Overlay */}
|
|
689
|
+
{steerTarget && (
|
|
690
|
+
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}>
|
|
691
|
+
<div style={{ background: 'var(--bg-secondary, #1e1e2e)', borderRadius: '12px', padding: '24px', maxWidth: '500px', width: '90%', border: '1px solid var(--border)' }}>
|
|
692
|
+
<h3 style={{ margin: '0 0 12px' }}>🎯 Steer Research</h3>
|
|
693
|
+
<textarea
|
|
694
|
+
ref={steerRef}
|
|
695
|
+
placeholder="Add direction... e.g. 'Focus more on pricing comparisons'"
|
|
696
|
+
style={{ width: '100%', minHeight: '100px', borderRadius: '8px', padding: '12px', background: 'var(--bg-primary, #11111b)', color: 'inherit', border: '1px solid var(--border, #333)', outline: 'none', resize: 'vertical', boxSizing: 'border-box' }}
|
|
697
|
+
/>
|
|
698
|
+
<div style={{ display: 'flex', gap: '8px', marginTop: '12px', justifyContent: 'flex-end' }}>
|
|
699
|
+
<button className="btn btn-sm btn-ghost" onClick={() => setSteerTarget(null)}>Cancel</button>
|
|
700
|
+
<button className="btn btn-sm btn-primary" onClick={handleSteerSubmit}>Submit</button>
|
|
701
|
+
</div>
|
|
702
|
+
</div>
|
|
703
|
+
</div>
|
|
704
|
+
)}
|
|
705
|
+
</div>
|
|
706
|
+
)
|
|
707
|
+
}
|