memhouse 0.0.1 → 0.3.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/LICENSE +22 -0
- package/README.md +72 -3
- package/bin/memhouse.js +559 -0
- package/cache.js +850 -0
- package/ch-ingest.js +329 -0
- package/ch-schema.js +152 -0
- package/clickhouse.js +87 -0
- package/editors/adapter-errors.js +52 -0
- package/editors/antigravity.js +1048 -0
- package/editors/base.js +321 -0
- package/editors/claude.js +386 -0
- package/editors/codebuff.js +338 -0
- package/editors/codex.js +537 -0
- package/editors/commandcode.js +166 -0
- package/editors/copilot-jetbrains.js +207 -0
- package/editors/copilot.js +260 -0
- package/editors/cursor-agent.js +202 -0
- package/editors/cursor.js +498 -0
- package/editors/gemini.js +231 -0
- package/editors/goose.js +386 -0
- package/editors/gsd.js +366 -0
- package/editors/index.js +274 -0
- package/editors/kiro.js +315 -0
- package/editors/opencode.js +432 -0
- package/editors/vscode.js +413 -0
- package/editors/windsurf.js +634 -0
- package/editors/zed.js +229 -0
- package/index.js +424 -2
- package/mcp-server.js +294 -0
- package/mem-house/DESIGN.md +119 -0
- package/mem-house/delivery/AGENT-INSTALL.md +87 -0
- package/mem-house/delivery/PROMPT.md +42 -0
- package/mem-house/delivery/install.sh +181 -0
- package/mem-house/delivery/kernel-install.md +83 -0
- package/mem-house/delivery/plugin/.claude-plugin/plugin.json +8 -0
- package/mem-house/delivery/skills/memhouse-search/SKILL.md +104 -0
- package/mem-house/delivery/skills/memhouse-sessions/SKILL.md +94 -0
- package/mem-house/delivery/skills/memhouse-sql/SKILL.md +115 -0
- package/mem-house/rls.sql +17 -0
- package/mem-house/schema.sql +145 -0
- package/mem-house/server/queries.js +878 -0
- package/mem-house/server/server.js +228 -0
- package/mem-house/shipper/ship.js +461 -0
- package/misc/syntax-check.js +38 -0
- package/package.json +59 -7
- package/pricing.js +88 -0
- package/pricing.json +992 -0
- package/public/assets/index-CCPh7a2Z.js +75 -0
- package/public/assets/index-DV6ONi_F.css +2 -0
- package/public/index.html +16 -0
- package/relay-client.js +306 -0
- package/relay-server.js +577 -0
- package/server.js +860 -0
- package/share-image.js +367 -0
- package/ui/eslint.config.js +29 -0
- package/ui/index.html +15 -0
- package/ui/package-lock.json +4229 -0
- package/ui/package.json +38 -0
- package/ui/src/App.jsx +364 -0
- package/ui/src/components/ActivityHeatmap.jsx +206 -0
- package/ui/src/components/AiAuditCard.jsx +254 -0
- package/ui/src/components/AnimatedLoader.jsx +14 -0
- package/ui/src/components/AnimatedLogo.jsx +96 -0
- package/ui/src/components/ChatSidebar.jsx +226 -0
- package/ui/src/components/DateRangePicker.jsx +104 -0
- package/ui/src/components/EditorDot.jsx +11 -0
- package/ui/src/components/EditorIcon.jsx +117 -0
- package/ui/src/components/KpiCard.jsx +9 -0
- package/ui/src/components/LiveFeed.jsx +128 -0
- package/ui/src/components/LoginScreen.jsx +81 -0
- package/ui/src/components/MessageRenderer.jsx +148 -0
- package/ui/src/components/PageHeader.jsx +11 -0
- package/ui/src/components/SectionTitle.jsx +3 -0
- package/ui/src/components/ShareModal.jsx +305 -0
- package/ui/src/components/TokenTimeline.jsx +258 -0
- package/ui/src/hooks/useLive.jsx +15 -0
- package/ui/src/index.css +83 -0
- package/ui/src/lib/api.js +343 -0
- package/ui/src/lib/constants.js +88 -0
- package/ui/src/lib/theme.jsx +23 -0
- package/ui/src/main.jsx +16 -0
- package/ui/src/pages/Artifacts.jsx +575 -0
- package/ui/src/pages/Compare.jsx +232 -0
- package/ui/src/pages/CostAnalysis.jsx +354 -0
- package/ui/src/pages/Dashboard.jsx +529 -0
- package/ui/src/pages/DeepAnalysis.jsx +495 -0
- package/ui/src/pages/GSD.jsx +726 -0
- package/ui/src/pages/MCPs.jsx +744 -0
- package/ui/src/pages/ProjectDetail.jsx +325 -0
- package/ui/src/pages/Projects.jsx +249 -0
- package/ui/src/pages/RelayDashboard.jsx +590 -0
- package/ui/src/pages/RelayUserDetail.jsx +466 -0
- package/ui/src/pages/Sessions.jsx +535 -0
- package/ui/src/pages/Settings.jsx +241 -0
- package/ui/src/pages/SqlViewer.jsx +370 -0
- package/ui/src/pages/Subscriptions.jsx +411 -0
- package/ui/vite.config.js +18 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Calendar, X } from 'lucide-react'
|
|
2
|
+
import { useTheme } from '../lib/theme'
|
|
3
|
+
|
|
4
|
+
const PRESETS = [
|
|
5
|
+
{ label: '7d', days: 7 },
|
|
6
|
+
{ label: '30d', days: 30 },
|
|
7
|
+
{ label: '90d', days: 90 },
|
|
8
|
+
{ label: '1y', days: 365 },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
// value: { from: 'YYYY-MM-DD', to: 'YYYY-MM-DD' } | null
|
|
12
|
+
// onChange: called with same shape, or null to clear
|
|
13
|
+
export default function DateRangePicker({ value, onChange }) {
|
|
14
|
+
const { dark } = useTheme()
|
|
15
|
+
const today = new Date().toISOString().split('T')[0]
|
|
16
|
+
const active = !!value
|
|
17
|
+
|
|
18
|
+
function applyPreset(days) {
|
|
19
|
+
const to = new Date()
|
|
20
|
+
const from = new Date(to.getTime() - days * 86400000)
|
|
21
|
+
onChange({
|
|
22
|
+
from: from.toISOString().split('T')[0],
|
|
23
|
+
to: to.toISOString().split('T')[0],
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function setFrom(from) {
|
|
28
|
+
onChange({ from, to: value?.to || today })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function setTo(to) {
|
|
32
|
+
onChange({ from: value?.from || today, to })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="flex items-center gap-1.5 flex-wrap">
|
|
37
|
+
<Calendar size={12} style={{ color: active ? 'var(--c-accent)' : 'var(--c-text3)', flexShrink: 0 }} />
|
|
38
|
+
|
|
39
|
+
{/* Preset buttons */}
|
|
40
|
+
{PRESETS.map(p => {
|
|
41
|
+
const isActive = active && (() => {
|
|
42
|
+
const diff = Math.round((Date.now() - new Date(value.from).getTime()) / 86400000)
|
|
43
|
+
return diff >= p.days - 1 && diff <= p.days + 1
|
|
44
|
+
})()
|
|
45
|
+
return (
|
|
46
|
+
<button
|
|
47
|
+
key={p.label}
|
|
48
|
+
onClick={() => applyPreset(p.days)}
|
|
49
|
+
className="px-2 py-0.5 text-[11px] transition"
|
|
50
|
+
style={{
|
|
51
|
+
border: isActive ? '1px solid var(--c-accent)' : '1px solid var(--c-border)',
|
|
52
|
+
color: isActive ? 'var(--c-accent)' : 'var(--c-text2)',
|
|
53
|
+
background: isActive ? 'rgba(99,102,241,0.1)' : 'transparent',
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
{p.label}
|
|
57
|
+
</button>
|
|
58
|
+
)
|
|
59
|
+
})}
|
|
60
|
+
|
|
61
|
+
{/* Custom date inputs */}
|
|
62
|
+
<input
|
|
63
|
+
type="date"
|
|
64
|
+
value={value?.from || ''}
|
|
65
|
+
max={value?.to || today}
|
|
66
|
+
onChange={e => setFrom(e.target.value)}
|
|
67
|
+
className="px-1.5 py-0.5 text-[11px] outline-none cursor-pointer"
|
|
68
|
+
style={{
|
|
69
|
+
background: 'var(--c-bg3)',
|
|
70
|
+
color: 'var(--c-text)',
|
|
71
|
+
border: '1px solid var(--c-border)',
|
|
72
|
+
colorScheme: dark ? 'dark' : 'light',
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
<span className="text-[11px]" style={{ color: 'var(--c-text3)' }}>—</span>
|
|
76
|
+
<input
|
|
77
|
+
type="date"
|
|
78
|
+
value={value?.to || ''}
|
|
79
|
+
min={value?.from || ''}
|
|
80
|
+
max={today}
|
|
81
|
+
onChange={e => setTo(e.target.value)}
|
|
82
|
+
className="px-1.5 py-0.5 text-[11px] outline-none cursor-pointer"
|
|
83
|
+
style={{
|
|
84
|
+
background: 'var(--c-bg3)',
|
|
85
|
+
color: 'var(--c-text)',
|
|
86
|
+
border: '1px solid var(--c-border)',
|
|
87
|
+
colorScheme: dark ? 'dark' : 'light',
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
{/* Clear button */}
|
|
92
|
+
{active && (
|
|
93
|
+
<button
|
|
94
|
+
onClick={() => onChange(null)}
|
|
95
|
+
className="flex items-center gap-0.5 px-2 py-0.5 text-[11px] transition"
|
|
96
|
+
style={{ border: '1px solid var(--c-accent)', color: 'var(--c-accent)' }}
|
|
97
|
+
>
|
|
98
|
+
<X size={9} /> clear
|
|
99
|
+
</button>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { editorLabel } from '../lib/constants'
|
|
2
|
+
import EditorIcon from './EditorIcon'
|
|
3
|
+
|
|
4
|
+
export default function EditorDot({ source, showLabel = false, size = 8 }) {
|
|
5
|
+
return (
|
|
6
|
+
<span className="inline-flex items-center gap-1.5">
|
|
7
|
+
<EditorIcon source={source} size={size} />
|
|
8
|
+
{showLabel && <span className="text-[11px]" style={{ color: 'var(--c-text)' }}>{editorLabel(source)}</span>}
|
|
9
|
+
</span>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useId } from 'react'
|
|
2
|
+
import { editorColor } from '../lib/constants'
|
|
3
|
+
|
|
4
|
+
const PATHS = {
|
|
5
|
+
cursor: 'M11.503.131 1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23',
|
|
6
|
+
devin: {
|
|
7
|
+
viewBox: '0 0 35.25 40.38',
|
|
8
|
+
transform: 'translate(-14.777 -137.73)',
|
|
9
|
+
d: 'm39.167 155.54c0.84846-0.48774 1.8974-0.48774 2.7459 0l2.1929 1.2661c0.34838 0.19148 0.68547 0.18106 1.0225-5e-4l4.3834-2.5323c0.31439-0.18029 0.50671-0.515 0.50671-0.8781v-5.0634c0-0.36328-0.1924-0.69894-0.50671-0.87811l-4.3834-2.5323c-0.31439-0.18031-0.70131-0.18031-1.0157 0l-4.3834 2.5323c-0.26369 0.1778-0.51306 0.45907-0.51306 0.88708v2.5323c0 0.97659-0.52447 1.8879-1.3729 2.3757-0.84846 0.48768-1.8974 0.48768-2.7459 0l-2.1929-1.2657c-0.46985-0.27058-0.95152-0.0497-1.0194-7e-3l-4.3834 2.5323c-0.31444 0.1803-0.5067 0.51501-0.5067 0.87811v5.0634c0 0.36327 0.19222 0.69892 0.5067 0.87809 1.4694 0.84487 2.9332 1.7016 4.4049 2.5413 0.29351 0.15057 0.6327 0.18995 1.0015-8e-3l2.1929-1.2661c0.84841-0.48768 1.8974-0.48768 2.7447 0 0.84487 0.48769 1.3729 1.4002 1.3729 2.3768v2.5323c0.0019 0.39738 0.22253 0.64724 0.51351 0.88532l4.3834 2.5323c0.31175 0.1833 0.7022 0.18306 1.0134 0l4.3834-2.5323c0.31439-0.18029 0.50666-0.51495 0.50666-0.87809v-5.0633c0-0.36328-0.1924-0.69894-0.50666-0.87815l-4.3834-2.5323c-0.34578-0.17938-0.66742-0.14455-1.0114 7e-3l-2.1929 1.2661c-0.84491 0.48773-1.8974 0.48773-2.7447 0-0.84491-0.48769-1.373-1.4002-1.373-2.3768 0-0.97659 0.52451-1.8879 1.373-2.3768l-0.0036-0.0134z M15.285 154.27l4.3834 2.5323c0.3113 0.18324 0.7023 0.18311 1.0134 0 1.4659-0.85041 2.9382-1.691 4.4013-2.5457 0.35014-0.20864 0.49266-0.58437 0.49266-0.87232v-2.5323c0-0.97664 0.52448-1.888 1.3729-2.3757 0.84844-0.48769 1.8974-0.48769 2.7459 0l2.1929 1.2661c0.13685 0.079 0.52674 0.25225 1.0015 8e-3 1.4704-0.84233 2.9372-1.6934 4.4049-2.5413 0.31439-0.18029 0.50671-0.515 0.50671-0.8781v-5.0634c0-0.36329-0.1924-0.69894-0.50671-0.87811l-4.3834-2.5323c-0.31448-0.1803-0.7013-0.1803-1.0158 0-1.4659 0.85041-2.9382 1.691-4.4013 2.5457-0.38564 0.23443-0.49275 0.62763-0.49275 0.87367v2.5323c0 0.97659-0.52449 1.8879-1.3729 2.3768-0.84489 0.48773-1.8974 0.48773-2.7459 0l-2.1929-1.2661c-0.34408-0.18796-0.6721-0.14674-1.0194-7e-3l-4.3834 2.5323c-0.31448 0.1803-0.50669 0.51501-0.50669 0.8781v5.0634c0 0.36328 0.19222 0.69893 0.50669 0.8781v4e-3z M37.287 168.61l-4.3834-2.5323s-0.01522-5e-3 -0.02152-9e-3c-0.3311-0.16938-0.66408-0.16515-0.99911 0.0125l-2.1929 1.2661c-0.84488 0.48769-1.8974 0.48769-2.7423 0-0.84844-0.48774-1.3729-1.4002-1.3729-2.3768v-2.5323c0-0.15429-0.04232-0.59032-0.49139-0.87327-0.0054-4e-3 -0.01171-9e-3 -0.01792-0.0134l-4.3834-2.5323c-0.31448-0.18029-0.7013-0.18029-1.0158 0l-4.3834 2.5323c-0.31448 0.18029-0.50669 0.515-0.50669 0.8781v5.0634c0 0.36328 0.19222 0.69889 0.50669 0.87811l4.3834 2.5323c0.34369 0.18308 0.67257 0.16105 1.0158-5e-4l2.1929-1.2661c0.84844-0.48774 1.8974-0.48774 2.7459 0 0.84487 0.48769 1.3729 1.4002 1.3729 2.3768v2.5323c0 0.4525 0.25018 0.73699 0.51012 0.88707l4.3834 2.5323c0.31128 0.18323 0.70228 0.18312 1.0134 0l4.3834-2.5323c0.31439-0.18029 0.50671-0.515 0.50671-0.8781v-5.0634c0-0.36328-0.1924-0.69893-0.50671-0.87811z',
|
|
10
|
+
},
|
|
11
|
+
claude: 'm4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z',
|
|
12
|
+
zed: 'M2.25 1.5a.75.75 0 0 0-.75.75v16.5H0V2.25A2.25 2.25 0 0 1 2.25 0h20.095c1.002 0 1.504 1.212.795 1.92L10.764 14.298h3.486V12.75h1.5v1.922a1.125 1.125 0 0 1-1.125 1.125H9.264l-2.578 2.578h11.689V9h1.5v9.375a1.5 1.5 0 0 1-1.5 1.5H5.185L2.562 22.5H21.75a.75.75 0 0 0 .75-.75V5.25H24v16.5A2.25 2.25 0 0 1 21.75 24H1.655C.653 24 .151 22.788.86 22.08L13.19 9.75H9.75v1.5h-1.5V9.375A1.125 1.125 0 0 1 9.375 8.25h5.314l2.625-2.625H5.625V15h-1.5V5.625a1.5 1.5 0 0 1 1.5-1.5h13.19L21.438 1.5z',
|
|
13
|
+
opencode: {
|
|
14
|
+
viewBox: '0 0 24 30',
|
|
15
|
+
paths: [
|
|
16
|
+
{ d: 'M18 24H6V12H18V24Z', fill: '#CFCECD' },
|
|
17
|
+
{ d: 'M18 6H6V24H18V6ZM24 30H0V0H24V30Z', fill: '#656363' },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
gemini: 'M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81',
|
|
21
|
+
codex: {
|
|
22
|
+
d: 'M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z',
|
|
23
|
+
gradient: [
|
|
24
|
+
['0%', '#B1A7FF'],
|
|
25
|
+
['5%', '#7A9DFF'],
|
|
26
|
+
['100%', '#3941FF'],
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
copilot: 'M23.922 16.997C23.061 18.492 18.063 22.02 12 22.02 5.937 22.02.939 18.492.078 16.997A.641.641 0 0 1 0 16.741v-2.869a.883.883 0 0 1 .053-.22c.372-.935 1.347-2.292 2.605-2.656.167-.429.414-1.055.644-1.517a10.098 10.098 0 0 1-.052-1.086c0-1.331.282-2.499 1.132-3.368.397-.406.89-.717 1.474-.952C7.255 2.937 9.248 1.98 11.978 1.98c2.731 0 4.767.957 6.166 2.093.584.235 1.077.546 1.474.952.85.869 1.132 2.037 1.132 3.368 0 .368-.014.733-.052 1.086.23.462.477 1.088.644 1.517 1.258.364 2.233 1.721 2.605 2.656a.841.841 0 0 1 .053.22v2.869a.641.641 0 0 1-.078.256Zm-11.75-5.992h-.344a4.359 4.359 0 0 1-.355.508c-.77.947-1.918 1.492-3.508 1.492-1.725 0-2.989-.359-3.782-1.259a2.137 2.137 0 0 1-.085-.104L4 11.746v6.585c1.435.779 4.514 2.179 8 2.179 3.486 0 6.565-1.4 8-2.179v-6.585l-.098-.104s-.033.045-.085.104c-.793.9-2.057 1.259-3.782 1.259-1.59 0-2.738-.545-3.508-1.492a4.359 4.359 0 0 1-.355-.508Zm2.328 3.25c.549 0 1 .451 1 1v2c0 .549-.451 1-1 1-.549 0-1-.451-1-1v-2c0-.549.451-1 1-1Zm-5 0c.549 0 1 .451 1 1v2c0 .549-.451 1-1 1-.549 0-1-.451-1-1v-2c0-.549.451-1 1-1Zm3.313-6.185c.136 1.057.403 1.913.878 2.497.442.544 1.134.938 2.344.938 1.573 0 2.292-.337 2.657-.751.384-.435.558-1.15.558-2.361 0-1.14-.243-1.847-.705-2.319-.477-.488-1.319-.862-2.824-1.025-1.487-.161-2.192.138-2.533.529-.269.307-.437.808-.438 1.578v.021c0 .265.021.562.063.893Zm-1.626 0c.042-.331.063-.628.063-.894v-.02c-.001-.77-.169-1.271-.438-1.578-.341-.391-1.046-.69-2.533-.529-1.505.163-2.347.537-2.824 1.025-.462.472-.705 1.179-.705 2.319 0 1.211.175 1.926.558 2.361.365.414 1.084.751 2.657.751 1.21 0 1.902-.394 2.344-.938.475-.584.742-1.44.878-2.497Z',
|
|
30
|
+
// VS Code - official bracket icon
|
|
31
|
+
vscode: 'M17.583.063a1.5 1.5 0 0 0-1.032.392 1.5 1.5 0 0 0-.001 0L7.04 9.708 2.81 6.442a1 1 0 0 0-1.32.098L.178 7.853a1 1 0 0 0 0 1.414l3.612 3.29-3.612 3.29a1 1 0 0 0 0 1.414L1.49 18.574a1 1 0 0 0 1.32.098L7.04 15.408l9.51 9.253a1.5 1.5 0 0 0 1.033.392A1.5 1.5 0 0 0 19.08 23.5V1.5A1.5 1.5 0 0 0 17.583.063M17.08 5.442l-5.5 7.115 5.5 7.115Z',
|
|
32
|
+
antigravity: 'M21.751 22.607c1.34 1.005 3.35.335 1.508-1.508C17.73 15.74 18.904 1 12.037 1 5.17 1 6.342 15.74.815 21.1c-2.01 2.009.167 2.511 1.507 1.506 5.192-3.517 4.857-9.714 9.715-9.714 4.857 0 4.522 6.197 9.714 9.715z',
|
|
33
|
+
command: 'M6,2A4,4 0 0,1 10,6V8H14V6A4,4 0 0,1 18,2A4,4 0 0,1 22,6A4,4 0 0,1 18,10H16V14H18A4,4 0 0,1 22,18A4,4 0 0,1 18,22A4,4 0 0,1 14,18V16H10V18A4,4 0 0,1 6,22A4,4 0 0,1 2,18A4,4 0 0,1 6,14H8V10H6A4,4 0 0,1 2,6A4,4 0 0,1 6,2M16,18A2,2 0 0,0 18,20A2,2 0 0,0 20,18A2,2 0 0,0 18,16H16V18M14,10H10V14H14V10M6,16A2,2 0 0,0 4,18A2,2 0 0,0 6,20A2,2 0 0,0 8,18V16H6M8,6A2,2 0 0,0 6,4A2,2 0 0,0 4,6A2,2 0 0,0 6,8H8V6M18,8A2,2 0 0,0 20,6A2,2 0 0,0 18,4A2,2 0 0,0 16,6V8H18Z',
|
|
34
|
+
goose: 'M22.112 23.596C23.018 23.399 23.979 22.864 23.979 22.864L22.297 21.479C21.466 20.795 20.759 19.973 20.206 19.05C19.441 17.774 18.385 16.697 17.125 15.908L16.509 15.55C16.298 15.403 16.151 15.175 16.13 14.917C16.117 14.751 16.157 14.602 16.25 14.471C16.57 14.019 18.227 12.053 18.531 11.802C18.922 11.479 19.357 11.21 19.762 10.902L19.934 10.77C19.936 10.768 19.938 10.767 19.94 10.765C20.07 10.663 20.192 10.554 20.29 10.425C20.641 10.018 20.726 9.658 20.747 9.499C20.7 9.346 20.558 9.003 20.163 8.608C20.411 8.623 20.71 8.819 20.982 9.05C21.165 8.758 21.356 8.45 21.547 8.141C21.674 7.934 21.485 7.78 21.48 7.775L21.479 7.775C21.479 7.775 21.479 7.774 21.479 7.774C21.474 7.769 21.319 7.579 21.114 7.707C20.673 7.979 20.234 8.252 19.842 8.5C19.842 8.5 19.379 8.49 18.829 8.964C18.7 9.062 18.591 9.184 18.489 9.314L18.484 9.32C18.439 9.377 18.396 9.434 18.352 9.492C18.044 9.897 17.775 10.332 17.452 10.723C17.201 11.027 15.235 12.684 14.783 13.004C14.652 13.097 14.504 13.137 14.337 13.124C14.08 13.103 13.851 12.956 13.704 12.745L13.346 12.129C12.557 10.868 11.48 9.813 10.204 9.048C9.281 8.495 8.459 7.787 7.775 6.957L6.39 5.275C6.39 5.275 5.854 6.236 5.658 7.141C5.931 7.474 6.644 8.298 7.473 8.928C6.581 8.509 5.922 8.184 5.402 7.913C5.322 8.506 5.353 9.403 5.436 10.098C5.999 10.344 6.957 10.724 7.933 10.926C7.152 11.108 6.296 11.141 5.635 11.128C5.751 11.558 5.914 11.997 6.132 12.438C6.224 12.642 6.326 12.842 6.435 13.037C6.785 13.133 8.159 13.333 8.89 13.169C8.163 13.428 6.942 13.865 6.942 13.865C7.88 15.031 8.916 15.98 8.916 15.98C10.492 15.133 10.852 15.017 12.034 14.244C10.119 15.802 9.622 16.438 9.085 17.091L8.71 17.617C8.516 17.89 8.347 18.18 8.206 18.484C7.734 19.5 7.065 21.666 7.065 21.666C6.946 22.043 7.222 22.32 7.589 22.189C7.589 22.189 9.754 21.521 10.77 21.048C11.074 20.907 11.364 20.738 11.637 20.544L12.163 20.169C12.339 20.024 12.514 19.882 12.707 19.714C12.707 19.714 14.03 21.282 15.39 22.313C15.39 22.313 15.826 21.092 16.086 20.364C15.921 21.096 16.121 22.469 16.218 22.819C16.412 22.929 16.612 23.03 16.816 23.123C17.258 23.341 17.696 23.503 18.126 23.619C18.113 22.958 18.146 22.102 18.329 21.321C18.531 22.297 18.91 23.256 19.157 23.819C19.851 23.902 20.748 23.933 21.341 23.853C21.07 23.333 20.746 22.673 20.326 21.781C20.956 22.611 21.781 23.324 22.113 23.597L22.112 23.596Z',
|
|
35
|
+
kiro: {
|
|
36
|
+
paths: [
|
|
37
|
+
{ d: 'M18.8 0H5.2A5.2 5.2 0 000 5.2v13.6A5.2 5.2 0 005.2 24h13.6a5.2 5.2 0 005.2-5.2V5.2A5.2 5.2 0 0018.8 0z', fill: '#9046FF' },
|
|
38
|
+
{ d: 'M7.97 16.376c-1.644 3.642 1.86 4.556 4.443 2.424.76 2.39 3.608.607 4.631-1.247 2.251-4.084 1.342-8.249 1.108-9.108-1.6-5.859-9.6-5.869-10.976.03-.323 1.033-.328 2.206-.507 3.423-.09.617-.16 1.009-.393 1.655-.139.373-.323.7-.62 1.257-.458.865-.264 2.53 2.101 1.665l.224-.1h-.01l-.001.001z', fill: '#fff' },
|
|
39
|
+
{ d: 'M12.722 10.985c-.656 0-.755-.785-.755-1.252 0-.423.074-.756.218-.97a.61.61 0 01.537-.283c.229 0 .428.095.567.289.159.218.243.55.243.964 0 .785-.303 1.252-.805 1.252h-.005zm2.703 0c-.656 0-.755-.785-.755-1.252 0-.423.074-.756.219-.97a.61.61 0 01.536-.283c.229 0 .428.095.567.289.159.218.243.55.243.964 0 .785-.303 1.252-.805 1.252h-.005z', fill: '#000' },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
// Terminal icon for generic editors
|
|
43
|
+
terminal: 'M4 17.27V19h16v-1.73ZM4 5v1.73l7.07 4.55L4 15.82v1.73l10-6.46Z',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Map editor IDs to icon paths
|
|
47
|
+
const EDITOR_ICONS = {
|
|
48
|
+
'cursor': 'cursor',
|
|
49
|
+
'cursor-agent': 'cursor',
|
|
50
|
+
'devin': 'devin',
|
|
51
|
+
'devin-next': 'devin',
|
|
52
|
+
'antigravity': 'antigravity',
|
|
53
|
+
'claude-code': 'claude',
|
|
54
|
+
'claude': 'claude',
|
|
55
|
+
'vscode': 'vscode',
|
|
56
|
+
'vscode-insiders': 'vscode',
|
|
57
|
+
'zed': 'zed',
|
|
58
|
+
'gemini-cli': 'gemini',
|
|
59
|
+
'copilot-cli': 'copilot',
|
|
60
|
+
'copilot-jetbrains': 'copilot',
|
|
61
|
+
'opencode': 'opencode',
|
|
62
|
+
'codex': 'codex',
|
|
63
|
+
'commandcode': 'command',
|
|
64
|
+
'goose': 'goose',
|
|
65
|
+
'kiro': 'kiro',
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default function EditorIcon({ source, size = 16, className = '' }) {
|
|
69
|
+
const pathKey = EDITOR_ICONS[source] || 'terminal'
|
|
70
|
+
const icon = PATHS[pathKey]
|
|
71
|
+
const color = editorColor(source)
|
|
72
|
+
const pathData = typeof icon === 'string' ? { d: icon, viewBox: '0 0 24 24' } : icon
|
|
73
|
+
const rawId = useId()
|
|
74
|
+
const gradientId = pathData?.gradient ? `icon-${rawId.replace(/:/g, '')}-${pathKey}` : null
|
|
75
|
+
const fill = gradientId ? `url(#${gradientId})` : color
|
|
76
|
+
|
|
77
|
+
if (!pathData?.d && !pathData?.paths?.length) {
|
|
78
|
+
return (
|
|
79
|
+
<span
|
|
80
|
+
className={`inline-block rounded-full flex-shrink-0 ${className}`}
|
|
81
|
+
style={{ width: size, height: size, background: color }}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<svg
|
|
88
|
+
viewBox={pathData.viewBox || '0 0 24 24'}
|
|
89
|
+
width={size}
|
|
90
|
+
height={size}
|
|
91
|
+
className={`inline-block flex-shrink-0 ${className}`}
|
|
92
|
+
fill={fill}
|
|
93
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
94
|
+
>
|
|
95
|
+
{pathData.gradient ? (
|
|
96
|
+
<defs>
|
|
97
|
+
<linearGradient id={gradientId} x1="12" x2="12" y1="0" y2="24" gradientUnits="userSpaceOnUse">
|
|
98
|
+
{pathData.gradient.map(([offset, stopColor]) => (
|
|
99
|
+
<stop key={offset} offset={offset} stopColor={stopColor} />
|
|
100
|
+
))}
|
|
101
|
+
</linearGradient>
|
|
102
|
+
</defs>
|
|
103
|
+
) : null}
|
|
104
|
+
{pathData.transform ? (
|
|
105
|
+
<g transform={pathData.transform}>
|
|
106
|
+
<path d={pathData.d} />
|
|
107
|
+
</g>
|
|
108
|
+
) : pathData.paths ? (
|
|
109
|
+
pathData.paths.map((path) => (
|
|
110
|
+
<path key={path.d} d={path.d} fill={path.fill || fill} fillOpacity={path.fillOpacity} />
|
|
111
|
+
))
|
|
112
|
+
) : (
|
|
113
|
+
<path d={pathData.d} />
|
|
114
|
+
)}
|
|
115
|
+
</svg>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default function KpiCard({ label, value, sub, onClick }) {
|
|
2
|
+
return (
|
|
3
|
+
<div className={`card px-3 py-2${onClick ? ' cursor-pointer hover:opacity-80 transition' : ''}`} onClick={onClick}>
|
|
4
|
+
<div className="text-base font-bold" style={{ color: onClick ? 'var(--c-accent)' : 'var(--c-white)' }}>{value}</div>
|
|
5
|
+
<div className="text-[11px]" style={{ color: 'var(--c-text2)' }}>{label}</div>
|
|
6
|
+
{sub && <div className="text-[10px] mt-0.5" style={{ color: 'var(--c-text3)' }}>{sub}</div>}
|
|
7
|
+
</div>
|
|
8
|
+
)
|
|
9
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react'
|
|
2
|
+
import { Radio, MessageSquare, FolderOpen, Cpu } from 'lucide-react'
|
|
3
|
+
import EditorDot from './EditorDot'
|
|
4
|
+
import { editorLabel, formatNumber } from '../lib/constants'
|
|
5
|
+
import { fetchRelayFeed } from '../lib/api'
|
|
6
|
+
|
|
7
|
+
function timeLabel(ts) {
|
|
8
|
+
if (!ts) return ''
|
|
9
|
+
const d = new Date(ts)
|
|
10
|
+
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function LiveFeed({ onSessionClick }) {
|
|
14
|
+
const [items, setItems] = useState([])
|
|
15
|
+
const scrollRef = useRef(null)
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const load = () => {
|
|
19
|
+
fetchRelayFeed({ limit: 80 })
|
|
20
|
+
.then(data => {
|
|
21
|
+
if (Array.isArray(data)) setItems(data)
|
|
22
|
+
})
|
|
23
|
+
.catch(() => {})
|
|
24
|
+
}
|
|
25
|
+
load()
|
|
26
|
+
const iv = setInterval(load, 10000)
|
|
27
|
+
return () => clearInterval(iv)
|
|
28
|
+
}, [])
|
|
29
|
+
|
|
30
|
+
// Group items by relative time buckets
|
|
31
|
+
const now = Date.now()
|
|
32
|
+
const buckets = []
|
|
33
|
+
let currentBucket = null
|
|
34
|
+
|
|
35
|
+
for (const item of items) {
|
|
36
|
+
const diff = now - item.lastUpdatedAt
|
|
37
|
+
let label
|
|
38
|
+
if (diff < 300000) label = 'Just now'
|
|
39
|
+
else if (diff < 3600000) label = `${Math.floor(diff / 60000)} min ago`
|
|
40
|
+
else if (diff < 86400000) label = `${Math.floor(diff / 3600000)}h ago`
|
|
41
|
+
else label = new Date(item.lastUpdatedAt).toLocaleDateString()
|
|
42
|
+
|
|
43
|
+
if (!currentBucket || currentBucket.label !== label) {
|
|
44
|
+
currentBucket = { label, items: [] }
|
|
45
|
+
buckets.push(currentBucket)
|
|
46
|
+
}
|
|
47
|
+
currentBucket.items.push(item)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="flex flex-col h-full">
|
|
52
|
+
{/* Header */}
|
|
53
|
+
<div className="flex items-center gap-2 px-3 py-2.5 shrink-0" style={{ borderBottom: '1px solid var(--c-border)' }}>
|
|
54
|
+
<Radio size={12} style={{ color: '#22c55e' }} />
|
|
55
|
+
<span className="text-[12px] font-medium uppercase tracking-wider" style={{ color: 'var(--c-text2)' }}>Live Feed</span>
|
|
56
|
+
<span className="inline-block w-1.5 h-1.5 rounded-full pulse-dot ml-auto" style={{ background: '#22c55e' }} />
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{/* Feed */}
|
|
60
|
+
<div ref={scrollRef} className="flex-1 overflow-y-auto scrollbar-thin">
|
|
61
|
+
{items.length === 0 && (
|
|
62
|
+
<div className="text-[12px] py-8 text-center" style={{ color: 'var(--c-text3)' }}>
|
|
63
|
+
No recent activity
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
{buckets.map((bucket, bi) => (
|
|
68
|
+
<div key={bi}>
|
|
69
|
+
{/* Time separator */}
|
|
70
|
+
<div className="sticky top-0 px-3 py-1.5 text-[10px] font-medium uppercase tracking-wider" style={{ background: 'var(--c-bg)', color: 'var(--c-text3)', borderBottom: '1px solid var(--c-border)' }}>
|
|
71
|
+
{bucket.label}
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{bucket.items.map((item) => (
|
|
75
|
+
<div
|
|
76
|
+
key={`${item.id}-${item.username}`}
|
|
77
|
+
className="px-3 py-2.5 cursor-pointer transition hover:bg-[var(--c-card)]"
|
|
78
|
+
style={{ borderBottom: '1px solid var(--c-border)' }}
|
|
79
|
+
onClick={() => onSessionClick && onSessionClick(item.id, item.username)}
|
|
80
|
+
>
|
|
81
|
+
{/* Session name */}
|
|
82
|
+
<div className="text-[12px] font-medium truncate mb-1" style={{ color: 'var(--c-white)' }}>
|
|
83
|
+
{item.name || 'Untitled'}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{/* User + editor row */}
|
|
87
|
+
<div className="flex items-center gap-1.5 mb-1">
|
|
88
|
+
<span
|
|
89
|
+
className="text-[10px] font-medium px-1 py-0.5 shrink-0 truncate max-w-[120px]"
|
|
90
|
+
style={{ background: 'rgba(99,102,241,0.12)', color: '#818cf8' }}
|
|
91
|
+
title={item.username}
|
|
92
|
+
>
|
|
93
|
+
{item.username}
|
|
94
|
+
</span>
|
|
95
|
+
<EditorDot source={item.source} size={6} />
|
|
96
|
+
<span className="text-[10px] truncate" style={{ color: 'var(--c-text2)' }}>{editorLabel(item.source)}</span>
|
|
97
|
+
<span className="text-[10px] ml-auto shrink-0" style={{ color: 'var(--c-text3)' }}>{timeLabel(item.lastUpdatedAt)}</span>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{/* Meta row */}
|
|
101
|
+
<div className="flex items-center gap-2 text-[10px]" style={{ color: 'var(--c-text3)' }}>
|
|
102
|
+
{item.totalMessages > 0 && (
|
|
103
|
+
<span className="flex items-center gap-0.5">
|
|
104
|
+
<MessageSquare size={8} /> {item.totalMessages}
|
|
105
|
+
</span>
|
|
106
|
+
)}
|
|
107
|
+
{item.folder && (
|
|
108
|
+
<span className="flex items-center gap-0.5 truncate">
|
|
109
|
+
<FolderOpen size={8} /> {item.folder.split('/').pop()}
|
|
110
|
+
</span>
|
|
111
|
+
)}
|
|
112
|
+
{item.mode && (
|
|
113
|
+
<span className="px-1 py-0" style={{ background: 'rgba(168,85,247,0.1)', color: '#a855f7' }}>{item.mode}</span>
|
|
114
|
+
)}
|
|
115
|
+
{item.models?.[0] && (
|
|
116
|
+
<span className="flex items-center gap-0.5 ml-auto truncate" style={{ color: '#818cf8' }}>
|
|
117
|
+
<Cpu size={8} /> {item.models[0]}
|
|
118
|
+
</span>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
))}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { Lock } from 'lucide-react'
|
|
3
|
+
import { login } from '../lib/api'
|
|
4
|
+
import AnimatedLogo from './AnimatedLogo'
|
|
5
|
+
|
|
6
|
+
export default function LoginScreen({ onSuccess }) {
|
|
7
|
+
const [password, setPassword] = useState('')
|
|
8
|
+
const [error, setError] = useState(null)
|
|
9
|
+
const [loading, setLoading] = useState(false)
|
|
10
|
+
|
|
11
|
+
const handleSubmit = async (e) => {
|
|
12
|
+
e.preventDefault()
|
|
13
|
+
setError(null)
|
|
14
|
+
setLoading(true)
|
|
15
|
+
try {
|
|
16
|
+
await login(password)
|
|
17
|
+
onSuccess()
|
|
18
|
+
} catch (err) {
|
|
19
|
+
setError(err.message || 'Invalid password')
|
|
20
|
+
}
|
|
21
|
+
setLoading(false)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="min-h-screen flex items-center justify-center p-4" style={{ background: 'var(--c-bg)' }}>
|
|
26
|
+
<div className="w-full max-w-xs">
|
|
27
|
+
<div className="card p-6">
|
|
28
|
+
<div className="flex flex-col items-center mb-5">
|
|
29
|
+
<div
|
|
30
|
+
className="w-10 h-10 flex items-center justify-center rounded-full mb-3"
|
|
31
|
+
style={{ background: 'rgba(99,102,241,0.12)' }}
|
|
32
|
+
>
|
|
33
|
+
<Lock size={18} style={{ color: '#818cf8' }} />
|
|
34
|
+
</div>
|
|
35
|
+
<div className="flex items-center gap-1.5 text-sm font-bold" style={{ color: 'var(--c-white)' }}>
|
|
36
|
+
<AnimatedLogo size={16} />
|
|
37
|
+
Agentlytics
|
|
38
|
+
<span className="text-[10px] font-medium px-1.5 py-0.5" style={{ background: 'rgba(99,102,241,0.15)', color: '#818cf8' }}>
|
|
39
|
+
relay
|
|
40
|
+
</span>
|
|
41
|
+
</div>
|
|
42
|
+
<div className="text-[11px] mt-1" style={{ color: 'var(--c-text3)' }}>
|
|
43
|
+
This relay is password-protected
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<form onSubmit={handleSubmit} className="space-y-3">
|
|
48
|
+
<input
|
|
49
|
+
type="password"
|
|
50
|
+
value={password}
|
|
51
|
+
onChange={e => setPassword(e.target.value)}
|
|
52
|
+
placeholder="Password"
|
|
53
|
+
autoFocus
|
|
54
|
+
className="w-full px-3 py-2 text-[12px] outline-none rounded"
|
|
55
|
+
style={{
|
|
56
|
+
background: 'var(--c-bg3)',
|
|
57
|
+
color: 'var(--c-white)',
|
|
58
|
+
border: error ? '1px solid #f87171' : '1px solid var(--c-border)',
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
{error && (
|
|
62
|
+
<div className="text-[11px]" style={{ color: '#f87171' }}>{error}</div>
|
|
63
|
+
)}
|
|
64
|
+
<button
|
|
65
|
+
type="submit"
|
|
66
|
+
disabled={loading || !password}
|
|
67
|
+
className="w-full py-2 text-[12px] font-medium rounded transition"
|
|
68
|
+
style={{
|
|
69
|
+
background: '#6366f1',
|
|
70
|
+
color: '#fff',
|
|
71
|
+
opacity: loading || !password ? 0.5 : 1,
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
{loading ? 'Signing in...' : 'Sign in'}
|
|
75
|
+
</button>
|
|
76
|
+
</form>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { User, Bot, Wrench, Settings, Play, CheckCircle, ChevronRight, ChevronDown } from 'lucide-react'
|
|
3
|
+
import ReactMarkdown from 'react-markdown'
|
|
4
|
+
import remarkGfm from 'remark-gfm'
|
|
5
|
+
|
|
6
|
+
export const ROLE_CONFIG = {
|
|
7
|
+
user: { icon: User, label: 'User', borderColor: 'rgba(34,197,94,0.2)', bg: 'rgba(34,197,94,0.05)' },
|
|
8
|
+
assistant: { icon: Bot, label: 'Assistant', borderColor: 'rgba(99,102,241,0.2)', bg: 'rgba(99,102,241,0.05)' },
|
|
9
|
+
system: { icon: Settings, label: 'System', borderColor: 'rgba(107,114,128,0.2)', bg: 'rgba(107,114,128,0.05)' },
|
|
10
|
+
tool: { icon: Wrench, label: 'Tool', borderColor: 'rgba(234,179,8,0.2)', bg: 'rgba(234,179,8,0.05)' },
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function parseContent(content) {
|
|
14
|
+
const segments = []
|
|
15
|
+
const regex = /\[tool-call: ([^\]]+)\]|\[tool-result: ([^\]]+)\]\s*(.*?)(?=\n\[tool-|$)/gs
|
|
16
|
+
let lastIdx = 0
|
|
17
|
+
let match
|
|
18
|
+
while ((match = regex.exec(content)) !== null) {
|
|
19
|
+
if (match.index > lastIdx) {
|
|
20
|
+
const text = content.slice(lastIdx, match.index).trim()
|
|
21
|
+
if (text) segments.push({ type: 'text', value: text })
|
|
22
|
+
}
|
|
23
|
+
if (match[1]) {
|
|
24
|
+
segments.push({ type: 'tool-call', name: match[1].replace(/\(.*\)$/, '').trim(), args: match[1] })
|
|
25
|
+
} else if (match[2]) {
|
|
26
|
+
segments.push({ type: 'tool-result', name: match[2].trim(), preview: (match[3] || '').trim() })
|
|
27
|
+
}
|
|
28
|
+
lastIdx = match.index + match[0].length
|
|
29
|
+
}
|
|
30
|
+
if (lastIdx < content.length) {
|
|
31
|
+
const text = content.slice(lastIdx).trim()
|
|
32
|
+
if (text) segments.push({ type: 'text', value: text })
|
|
33
|
+
}
|
|
34
|
+
return segments.length > 0 ? segments : [{ type: 'text', value: content }]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function summarizeToolArgs(name, args) {
|
|
38
|
+
if (!args || typeof args !== 'object') return ''
|
|
39
|
+
if (args.file_path || args.TargetFile) return args.file_path || args.TargetFile
|
|
40
|
+
if (args.CommandLine || args.command) return args.CommandLine || args.command
|
|
41
|
+
if (args.Query || args.query) return `${args.Query || args.query}${args.SearchPath ? ` in ${args.SearchPath}` : ''}`
|
|
42
|
+
if (args.Url || args.url) return args.Url || args.url
|
|
43
|
+
const vals = Object.values(args).filter(v => typeof v === 'string' && v.length > 0 && v.length < 120)
|
|
44
|
+
return vals.length > 0 ? vals[0] : ''
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ToolArgsDiff({ args }) {
|
|
48
|
+
const old = args.old_string || args.old_text || args.oldText || args.search || null
|
|
49
|
+
const nw = args.new_string || args.new_text || args.newText || args.replace || null
|
|
50
|
+
if (old == null && nw == null) return null
|
|
51
|
+
const maxLines = 12
|
|
52
|
+
const oldLines = (old || '').split('\n').slice(0, maxLines)
|
|
53
|
+
const newLines = (nw || '').split('\n').slice(0, maxLines)
|
|
54
|
+
return (
|
|
55
|
+
<div className="mt-1.5 text-[10px] font-mono overflow-x-auto" style={{ border: '1px solid var(--c-border)' }}>
|
|
56
|
+
{(args.file_path || args.TargetFile) && (
|
|
57
|
+
<div className="px-2 py-0.5" style={{ background: 'var(--c-code-bg)', color: 'var(--c-text)' }}>{args.file_path || args.TargetFile}</div>
|
|
58
|
+
)}
|
|
59
|
+
{old && oldLines.map((line, i) => (
|
|
60
|
+
<div key={'o' + i} className="px-2" style={{ background: 'rgba(248,113,113,0.1)', color: '#f87171' }}>
|
|
61
|
+
<span style={{ color: 'var(--c-text3)', userSelect: 'none' }}>- </span>{line}
|
|
62
|
+
</div>
|
|
63
|
+
))}
|
|
64
|
+
{old && oldLines.length < (old || '').split('\n').length && (
|
|
65
|
+
<div className="px-2" style={{ color: 'var(--c-text3)' }}> ... {(old || '').split('\n').length - maxLines} more lines</div>
|
|
66
|
+
)}
|
|
67
|
+
{nw && newLines.map((line, i) => (
|
|
68
|
+
<div key={'n' + i} className="px-2" style={{ background: 'rgba(52,211,153,0.1)', color: '#34d399' }}>
|
|
69
|
+
<span style={{ color: 'var(--c-text3)', userSelect: 'none' }}>+ </span>{line}
|
|
70
|
+
</div>
|
|
71
|
+
))}
|
|
72
|
+
{nw && newLines.length < (nw || '').split('\n').length && (
|
|
73
|
+
<div className="px-2" style={{ color: 'var(--c-text3)' }}> ... {(nw || '').split('\n').length - maxLines} more lines</div>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function ToolArgsDetail({ args }) {
|
|
80
|
+
if (!args || Object.keys(args).length === 0) return null
|
|
81
|
+
const hasDiff = args.old_string || args.new_string || args.old_text || args.new_text || args.search || args.replace
|
|
82
|
+
if (hasDiff) return <ToolArgsDiff args={args} />
|
|
83
|
+
const file = args.file_path || args.TargetFile || args.filePath || args.path || null
|
|
84
|
+
const cmd = args.CommandLine || args.command || null
|
|
85
|
+
const query = args.Query || args.query || args.search_term || null
|
|
86
|
+
const url = args.Url || args.url || null
|
|
87
|
+
return (
|
|
88
|
+
<div className="mt-1.5 text-[10px] font-mono overflow-x-auto" style={{ background: 'var(--c-code-bg)', border: '1px solid var(--c-border)' }}>
|
|
89
|
+
{file && <div className="px-2 py-0.5" style={{ color: 'var(--c-text)' }}>file: {file}</div>}
|
|
90
|
+
{cmd && <div className="px-2 py-0.5" style={{ color: 'var(--c-text)' }}>cmd: {cmd}</div>}
|
|
91
|
+
{query && <div className="px-2 py-0.5" style={{ color: 'var(--c-text)' }}>query: {query}</div>}
|
|
92
|
+
{url && <div className="px-2 py-0.5" style={{ color: 'var(--c-text)' }}>url: {url}</div>}
|
|
93
|
+
{!file && !cmd && !query && !url && (
|
|
94
|
+
<pre className="px-2 py-1 whitespace-pre-wrap break-all" style={{ color: 'var(--c-text2)' }}>{JSON.stringify(args, null, 2)}</pre>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function ToolCallBlock({ name, args, detail }) {
|
|
101
|
+
const [open, setOpen] = useState(false)
|
|
102
|
+
const hasDetail = detail && Object.keys(detail).length > 0
|
|
103
|
+
return (
|
|
104
|
+
<div className="my-1 px-2.5 py-1.5 text-[11px]" style={{ background: 'var(--c-code-bg)', border: '1px solid var(--c-border)' }}>
|
|
105
|
+
<div className="flex items-center gap-2 cursor-pointer" onClick={() => hasDetail && setOpen(!open)}>
|
|
106
|
+
{hasDetail
|
|
107
|
+
? (open ? <ChevronDown size={10} style={{ color: '#a78bfa' }} /> : <ChevronRight size={10} style={{ color: '#a78bfa' }} />)
|
|
108
|
+
: <Play size={10} style={{ color: '#a78bfa' }} />
|
|
109
|
+
}
|
|
110
|
+
<span className="font-bold" style={{ color: 'var(--c-white)' }}>{name}</span>
|
|
111
|
+
{args !== name && !hasDetail && <span className="truncate" style={{ color: 'var(--c-text2)' }}>{args}</span>}
|
|
112
|
+
{hasDetail && <span className="truncate" style={{ color: 'var(--c-text2)' }}>{summarizeToolArgs(name, detail)}</span>}
|
|
113
|
+
</div>
|
|
114
|
+
{open && hasDetail && <ToolArgsDetail args={detail} />}
|
|
115
|
+
</div>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function ToolResultBlock({ name, preview }) {
|
|
120
|
+
const [open, setOpen] = useState(false)
|
|
121
|
+
const isNoisy = preview.length > 120 || preview.startsWith('{') || preview.includes('contentId')
|
|
122
|
+
const short = isNoisy ? `${name} completed` : preview.substring(0, 120)
|
|
123
|
+
return (
|
|
124
|
+
<div className="my-1 px-2.5 py-1.5 text-[11px]" style={{ background: 'var(--c-code-bg)', border: '1px solid var(--c-border)' }}>
|
|
125
|
+
<div className="flex items-center gap-2 cursor-pointer" onClick={() => preview && setOpen(!open)}>
|
|
126
|
+
<CheckCircle size={10} style={{ color: '#34d399' }} />
|
|
127
|
+
<span className="truncate" style={{ color: 'var(--c-text)' }}>{short}</span>
|
|
128
|
+
{isNoisy && preview && <span style={{ color: 'var(--c-text3)' }}>{open ? '[-]' : '[+]'}</span>}
|
|
129
|
+
</div>
|
|
130
|
+
{open && <pre className="mt-1 text-[10px] overflow-x-auto whitespace-pre-wrap break-all" style={{ color: 'var(--c-text2)' }}>{preview}</pre>}
|
|
131
|
+
</div>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export default function MessageContent({ content, toolCallDetails }) {
|
|
136
|
+
const segments = parseContent(content)
|
|
137
|
+
let toolIdx = 0
|
|
138
|
+
return segments.map((seg, i) => {
|
|
139
|
+
if (seg.type === 'tool-call') {
|
|
140
|
+
const detail = toolCallDetails ? toolCallDetails.find(tc => tc.name === seg.name && toolCallDetails.indexOf(tc) >= toolIdx) : null
|
|
141
|
+
if (detail) toolIdx = toolCallDetails.indexOf(detail) + 1
|
|
142
|
+
return <ToolCallBlock key={i} name={seg.name} args={seg.args} detail={detail?.args} />
|
|
143
|
+
}
|
|
144
|
+
if (seg.type === 'tool-result') return <ToolResultBlock key={i} name={seg.name} preview={seg.preview} />
|
|
145
|
+
return <div key={i} className="md-body"><ReactMarkdown remarkPlugins={[remarkGfm]}>{seg.value}</ReactMarkdown></div>
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default function PageHeader({ icon: Icon, title, children }) {
|
|
2
|
+
return (
|
|
3
|
+
<div className="flex items-center gap-3">
|
|
4
|
+
<div className="flex items-center gap-1.5 text-[13px] font-bold" style={{ color: 'var(--c-white)' }}>
|
|
5
|
+
{Icon && <Icon size={14} style={{ color: '#6366f1' }} />}
|
|
6
|
+
{title}
|
|
7
|
+
</div>
|
|
8
|
+
{children}
|
|
9
|
+
</div>
|
|
10
|
+
)
|
|
11
|
+
}
|