claude-smart 0.2.23 → 0.2.24
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/.agents/plugins/marketplace.json +20 -0
- package/README.md +69 -27
- package/bin/claude-smart.js +296 -11
- package/package.json +11 -1
- package/plugin/.claude-plugin/plugin.json +17 -0
- package/plugin/.codex-plugin/plugin.json +35 -0
- package/plugin/LICENSE +202 -0
- package/plugin/README.md +37 -0
- package/plugin/bin/cs-cite +77 -0
- package/plugin/commands/clear-all.md +8 -0
- package/plugin/commands/dashboard.md +8 -0
- package/plugin/commands/learn.md +12 -0
- package/plugin/commands/restart.md +8 -0
- package/plugin/commands/show.md +8 -0
- package/plugin/dashboard/AGENTS.md +6 -0
- package/plugin/dashboard/app/api/claude-settings/route.ts +19 -0
- package/plugin/dashboard/app/api/config/route.ts +16 -0
- package/plugin/dashboard/app/api/health/route.ts +10 -0
- package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +63 -0
- package/plugin/dashboard/app/api/sessions/[id]/route.ts +28 -0
- package/plugin/dashboard/app/api/sessions/route.ts +14 -0
- package/plugin/dashboard/app/configure/env/page.tsx +318 -0
- package/plugin/dashboard/app/configure/layout.tsx +47 -0
- package/plugin/dashboard/app/configure/page.tsx +5 -0
- package/plugin/dashboard/app/configure/server/page.tsx +258 -0
- package/plugin/dashboard/app/dashboard/page.tsx +227 -0
- package/plugin/dashboard/app/globals.css +129 -0
- package/plugin/dashboard/app/icon.png +0 -0
- package/plugin/dashboard/app/layout.tsx +40 -0
- package/plugin/dashboard/app/page.tsx +5 -0
- package/plugin/dashboard/app/preferences/[id]/page.tsx +531 -0
- package/plugin/dashboard/app/preferences/page.tsx +126 -0
- package/plugin/dashboard/app/providers.tsx +12 -0
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +321 -0
- package/plugin/dashboard/app/sessions/page.tsx +186 -0
- package/plugin/dashboard/app/skills/page.tsx +362 -0
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +597 -0
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +830 -0
- package/plugin/dashboard/components/common/delete-all-button.tsx +45 -0
- package/plugin/dashboard/components/common/empty-state.tsx +34 -0
- package/plugin/dashboard/components/common/learnings-badge.tsx +34 -0
- package/plugin/dashboard/components/common/page-header.tsx +34 -0
- package/plugin/dashboard/components/common/page-tabs.tsx +115 -0
- package/plugin/dashboard/components/common/stat-card.tsx +38 -0
- package/plugin/dashboard/components/layout/nav-items.ts +22 -0
- package/plugin/dashboard/components/layout/sidebar.tsx +45 -0
- package/plugin/dashboard/components/layout/top-bar.tsx +64 -0
- package/plugin/dashboard/components/stall-banner.tsx +53 -0
- package/plugin/dashboard/components/ui/badge.tsx +52 -0
- package/plugin/dashboard/components/ui/button.tsx +60 -0
- package/plugin/dashboard/components/ui/collapsible.tsx +21 -0
- package/plugin/dashboard/components/ui/input.tsx +20 -0
- package/plugin/dashboard/components/ui/label.tsx +20 -0
- package/plugin/dashboard/components/ui/scroll-area.tsx +55 -0
- package/plugin/dashboard/components/ui/select.tsx +201 -0
- package/plugin/dashboard/components/ui/separator.tsx +25 -0
- package/plugin/dashboard/components/ui/sheet.tsx +135 -0
- package/plugin/dashboard/components/ui/switch.tsx +32 -0
- package/plugin/dashboard/components.json +25 -0
- package/plugin/dashboard/eslint.config.mjs +16 -0
- package/plugin/dashboard/hooks/use-settings.tsx +88 -0
- package/plugin/dashboard/hooks/use-stall-state.ts +59 -0
- package/plugin/dashboard/lib/claude-settings-file.ts +114 -0
- package/plugin/dashboard/lib/config-file.ts +131 -0
- package/plugin/dashboard/lib/format.ts +58 -0
- package/plugin/dashboard/lib/reflexio-client.ts +238 -0
- package/plugin/dashboard/lib/reflexio-url.ts +17 -0
- package/plugin/dashboard/lib/session-reader.ts +245 -0
- package/plugin/dashboard/lib/status.ts +24 -0
- package/plugin/dashboard/lib/types.ts +145 -0
- package/plugin/dashboard/lib/utils.ts +6 -0
- package/plugin/dashboard/next.config.ts +7 -0
- package/plugin/dashboard/package-lock.json +10275 -0
- package/plugin/dashboard/package.json +37 -0
- package/plugin/dashboard/postcss.config.mjs +7 -0
- package/plugin/dashboard/public/claude-smart-icon.png +0 -0
- package/plugin/dashboard/tsconfig.json +34 -0
- package/plugin/hooks/codex-hooks.json +67 -0
- package/plugin/hooks/hooks.json +111 -0
- package/plugin/pyproject.toml +49 -0
- package/plugin/scripts/_codex_env.sh +27 -0
- package/plugin/scripts/_lib.sh +325 -0
- package/plugin/scripts/backend-service.sh +208 -0
- package/plugin/scripts/cli.sh +40 -0
- package/plugin/scripts/dashboard-build.sh +139 -0
- package/plugin/scripts/dashboard-open.sh +107 -0
- package/plugin/scripts/dashboard-service.sh +195 -0
- package/plugin/scripts/ensure-plugin-root.sh +84 -0
- package/plugin/scripts/hook_entry.sh +70 -0
- package/plugin/scripts/smart-install.sh +411 -0
- package/plugin/src/claude_smart/__init__.py +3 -0
- package/plugin/src/claude_smart/cli.py +1273 -0
- package/plugin/src/claude_smart/context_format.py +277 -0
- package/plugin/src/claude_smart/context_inject.py +92 -0
- package/plugin/src/claude_smart/cs_cite.py +236 -0
- package/plugin/src/claude_smart/events/__init__.py +1 -0
- package/plugin/src/claude_smart/events/post_tool.py +148 -0
- package/plugin/src/claude_smart/events/pre_tool.py +52 -0
- package/plugin/src/claude_smart/events/session_end.py +20 -0
- package/plugin/src/claude_smart/events/session_start.py +119 -0
- package/plugin/src/claude_smart/events/stop.py +393 -0
- package/plugin/src/claude_smart/events/user_prompt.py +73 -0
- package/plugin/src/claude_smart/hook.py +114 -0
- package/plugin/src/claude_smart/ids.py +56 -0
- package/plugin/src/claude_smart/internal_call.py +89 -0
- package/plugin/src/claude_smart/optimizer_assistant.py +203 -0
- package/plugin/src/claude_smart/publish.py +71 -0
- package/plugin/src/claude_smart/query_compose.py +51 -0
- package/plugin/src/claude_smart/reflexio_adapter.py +403 -0
- package/plugin/src/claude_smart/runtime.py +52 -0
- package/plugin/src/claude_smart/stall_banner.py +61 -0
- package/plugin/src/claude_smart/state.py +276 -0
- package/plugin/uv.lock +3720 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Save, CheckCircle2 } from "lucide-react";
|
|
5
|
+
import { PageHeader } from "@/components/common/page-header";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Input } from "@/components/ui/input";
|
|
8
|
+
import { Label } from "@/components/ui/label";
|
|
9
|
+
import { Switch } from "@/components/ui/switch";
|
|
10
|
+
import { Separator } from "@/components/ui/separator";
|
|
11
|
+
import { useSettings } from "@/hooks/use-settings";
|
|
12
|
+
import type { ClaudeCodeHookConfig, ClaudeSmartConfig } from "@/lib/types";
|
|
13
|
+
|
|
14
|
+
export default function ConfigureEnvPage() {
|
|
15
|
+
const { reflexioUrl, setReflexioUrl } = useSettings();
|
|
16
|
+
const [config, setConfig] = useState<ClaudeSmartConfig | null>(null);
|
|
17
|
+
const [hookConfig, setHookConfig] = useState<ClaudeCodeHookConfig | null>(null);
|
|
18
|
+
const [hookDirty, setHookDirty] = useState(false);
|
|
19
|
+
const [error, setError] = useState<string | null>(null);
|
|
20
|
+
const [saved, setSaved] = useState(false);
|
|
21
|
+
const [saving, setSaving] = useState(false);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
Promise.all([
|
|
25
|
+
fetch("/api/config", { cache: "no-store" }).then((r) => r.json()),
|
|
26
|
+
fetch("/api/claude-settings", { cache: "no-store" }).then((r) =>
|
|
27
|
+
r.json(),
|
|
28
|
+
),
|
|
29
|
+
])
|
|
30
|
+
.then(([envConfig, claudeConfig]) => {
|
|
31
|
+
setConfig(envConfig);
|
|
32
|
+
setHookConfig(claudeConfig);
|
|
33
|
+
setHookDirty(false);
|
|
34
|
+
})
|
|
35
|
+
.catch((e) => setError(e instanceof Error ? e.message : String(e)));
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
const update = <K extends keyof ClaudeSmartConfig>(
|
|
39
|
+
key: K,
|
|
40
|
+
value: ClaudeSmartConfig[K],
|
|
41
|
+
) => {
|
|
42
|
+
setConfig((prev) => (prev ? { ...prev, [key]: value } : prev));
|
|
43
|
+
setSaved(false);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const updateOptimizer = (enabled: boolean) => {
|
|
47
|
+
setHookConfig((prev) =>
|
|
48
|
+
prev
|
|
49
|
+
? {
|
|
50
|
+
...prev,
|
|
51
|
+
CLAUDE_SMART_ENABLE_OPTIMIZER: enabled,
|
|
52
|
+
effectiveValue: enabled,
|
|
53
|
+
localValue: enabled,
|
|
54
|
+
}
|
|
55
|
+
: prev,
|
|
56
|
+
);
|
|
57
|
+
setHookDirty(true);
|
|
58
|
+
setSaved(false);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const save = async () => {
|
|
62
|
+
if (!config || !hookConfig) return;
|
|
63
|
+
setSaving(true);
|
|
64
|
+
setError(null);
|
|
65
|
+
try {
|
|
66
|
+
const [envRes, hookRes] = await Promise.all([
|
|
67
|
+
fetch("/api/config", {
|
|
68
|
+
method: "PUT",
|
|
69
|
+
headers: { "content-type": "application/json" },
|
|
70
|
+
body: JSON.stringify(config),
|
|
71
|
+
}),
|
|
72
|
+
fetch("/api/claude-settings", {
|
|
73
|
+
method: "PUT",
|
|
74
|
+
headers: { "content-type": "application/json" },
|
|
75
|
+
body: JSON.stringify(
|
|
76
|
+
hookDirty
|
|
77
|
+
? {
|
|
78
|
+
CLAUDE_SMART_ENABLE_OPTIMIZER:
|
|
79
|
+
hookConfig.CLAUDE_SMART_ENABLE_OPTIMIZER,
|
|
80
|
+
}
|
|
81
|
+
: {},
|
|
82
|
+
),
|
|
83
|
+
}),
|
|
84
|
+
]);
|
|
85
|
+
if (!envRes.ok) throw new Error(`environment save failed: ${envRes.status}`);
|
|
86
|
+
if (!hookRes.ok)
|
|
87
|
+
throw new Error(`Claude Code settings save failed: ${hookRes.status}`);
|
|
88
|
+
const updated: ClaudeSmartConfig = await envRes.json();
|
|
89
|
+
const updatedHook: ClaudeCodeHookConfig = await hookRes.json();
|
|
90
|
+
setConfig(updated);
|
|
91
|
+
setHookConfig(updatedHook);
|
|
92
|
+
setHookDirty(false);
|
|
93
|
+
setSaved(true);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
96
|
+
} finally {
|
|
97
|
+
setSaving(false);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<>
|
|
103
|
+
<PageHeader
|
|
104
|
+
title="Environment"
|
|
105
|
+
description="Dashboard settings, Reflexio backend environment, and Claude Code hook environment."
|
|
106
|
+
actions={
|
|
107
|
+
<Button onClick={save} disabled={!config || !hookConfig || saving} size="sm">
|
|
108
|
+
<Save className="h-3.5 w-3.5" />
|
|
109
|
+
{saving ? "Saving…" : "Save"}
|
|
110
|
+
</Button>
|
|
111
|
+
}
|
|
112
|
+
/>
|
|
113
|
+
|
|
114
|
+
<div className="p-6 max-w-2xl mx-auto space-y-6">
|
|
115
|
+
{error && (
|
|
116
|
+
<div className="rounded-lg border border-destructive/30 bg-destructive/5 text-destructive px-4 py-3 text-sm">
|
|
117
|
+
{error}
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
{saved && (
|
|
121
|
+
<div className="rounded-lg border border-border bg-accent/40 px-4 py-2.5 text-sm flex items-center gap-2">
|
|
122
|
+
<CheckCircle2 className="h-4 w-4 text-muted-foreground" />
|
|
123
|
+
Saved to ~/.reflexio/.env and Claude Code settings
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
|
|
127
|
+
<section className="space-y-4">
|
|
128
|
+
<div>
|
|
129
|
+
<h2 className="text-sm font-semibold">Dashboard</h2>
|
|
130
|
+
<p className="text-xs text-muted-foreground">
|
|
131
|
+
Stored in browser localStorage — only affects this UI.
|
|
132
|
+
</p>
|
|
133
|
+
</div>
|
|
134
|
+
<div className="space-y-2">
|
|
135
|
+
<Label>Reflexio endpoint (dashboard)</Label>
|
|
136
|
+
<Input
|
|
137
|
+
value={reflexioUrl}
|
|
138
|
+
onChange={(e) => setReflexioUrl(e.target.value)}
|
|
139
|
+
className="font-mono text-xs"
|
|
140
|
+
placeholder="http://localhost:8071"
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
</section>
|
|
144
|
+
|
|
145
|
+
<Separator />
|
|
146
|
+
|
|
147
|
+
<section className="space-y-4">
|
|
148
|
+
<div>
|
|
149
|
+
<h2 className="text-sm font-semibold">claude-smart environment</h2>
|
|
150
|
+
<p className="text-xs text-muted-foreground">
|
|
151
|
+
Writes to <code className="font-mono">~/.reflexio/.env</code>. Unknown
|
|
152
|
+
keys are preserved. These values are read by the Reflexio backend.
|
|
153
|
+
</p>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{config === null && !error ? (
|
|
157
|
+
<div className="text-sm text-muted-foreground">Loading…</div>
|
|
158
|
+
) : config ? (
|
|
159
|
+
<div className="space-y-5">
|
|
160
|
+
<div className="space-y-2">
|
|
161
|
+
<Label>REFLEXIO_URL</Label>
|
|
162
|
+
<Input
|
|
163
|
+
value={config.REFLEXIO_URL}
|
|
164
|
+
onChange={(e) => update("REFLEXIO_URL", e.target.value)}
|
|
165
|
+
className="font-mono text-xs"
|
|
166
|
+
placeholder="http://localhost:8071/"
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<div className="flex items-start justify-between gap-4">
|
|
171
|
+
<div className="min-w-0">
|
|
172
|
+
<Label htmlFor="use-local-cli">CLAUDE_SMART_USE_LOCAL_CLI</Label>
|
|
173
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
174
|
+
Route generation through the local <code>claude</code> CLI.
|
|
175
|
+
</p>
|
|
176
|
+
</div>
|
|
177
|
+
<Switch
|
|
178
|
+
id="use-local-cli"
|
|
179
|
+
checked={!!config.CLAUDE_SMART_USE_LOCAL_CLI}
|
|
180
|
+
onCheckedChange={(v) =>
|
|
181
|
+
update("CLAUDE_SMART_USE_LOCAL_CLI", v)
|
|
182
|
+
}
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div className="flex items-start justify-between gap-4">
|
|
187
|
+
<div className="min-w-0">
|
|
188
|
+
<Label htmlFor="use-local-embed">
|
|
189
|
+
CLAUDE_SMART_USE_LOCAL_EMBEDDING
|
|
190
|
+
</Label>
|
|
191
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
192
|
+
Use in-process ONNX embedder (offline-friendly).
|
|
193
|
+
</p>
|
|
194
|
+
</div>
|
|
195
|
+
<Switch
|
|
196
|
+
id="use-local-embed"
|
|
197
|
+
checked={!!config.CLAUDE_SMART_USE_LOCAL_EMBEDDING}
|
|
198
|
+
onCheckedChange={(v) =>
|
|
199
|
+
update("CLAUDE_SMART_USE_LOCAL_EMBEDDING", v)
|
|
200
|
+
}
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<div className="space-y-2">
|
|
205
|
+
<Label>CLAUDE_SMART_CLI_PATH</Label>
|
|
206
|
+
<Input
|
|
207
|
+
value={String(config.CLAUDE_SMART_CLI_PATH ?? "")}
|
|
208
|
+
onChange={(e) => update("CLAUDE_SMART_CLI_PATH", e.target.value)}
|
|
209
|
+
className="font-mono text-xs"
|
|
210
|
+
placeholder="(empty — auto-detect via $PATH)"
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<div className="space-y-2">
|
|
215
|
+
<Label>CLAUDE_SMART_CLI_TIMEOUT</Label>
|
|
216
|
+
<Input
|
|
217
|
+
value={String(config.CLAUDE_SMART_CLI_TIMEOUT ?? "")}
|
|
218
|
+
onChange={(e) =>
|
|
219
|
+
update("CLAUDE_SMART_CLI_TIMEOUT", e.target.value)
|
|
220
|
+
}
|
|
221
|
+
className="font-mono text-xs"
|
|
222
|
+
placeholder="120"
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<div className="space-y-2">
|
|
227
|
+
<Label>CLAUDE_SMART_STATE_DIR</Label>
|
|
228
|
+
<Input
|
|
229
|
+
value={String(config.CLAUDE_SMART_STATE_DIR ?? "")}
|
|
230
|
+
onChange={(e) =>
|
|
231
|
+
update("CLAUDE_SMART_STATE_DIR", e.target.value)
|
|
232
|
+
}
|
|
233
|
+
className="font-mono text-xs"
|
|
234
|
+
placeholder="(empty — default ~/.claude-smart/sessions)"
|
|
235
|
+
/>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
) : null}
|
|
239
|
+
</section>
|
|
240
|
+
|
|
241
|
+
<Separator />
|
|
242
|
+
|
|
243
|
+
<section className="space-y-4">
|
|
244
|
+
<div>
|
|
245
|
+
<h2 className="text-sm font-semibold">Claude Code hook environment</h2>
|
|
246
|
+
<p className="text-xs text-muted-foreground">
|
|
247
|
+
Writes to{" "}
|
|
248
|
+
<code className="font-mono">.claude/settings.local.json</code>.
|
|
249
|
+
Hook-side env changes apply to new Claude Code sessions.
|
|
250
|
+
</p>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
{hookConfig === null && !error ? (
|
|
254
|
+
<div className="text-sm text-muted-foreground">Loading…</div>
|
|
255
|
+
) : hookConfig ? (
|
|
256
|
+
<div className="space-y-5">
|
|
257
|
+
<div className="flex items-start justify-between gap-4">
|
|
258
|
+
<div className="min-w-0">
|
|
259
|
+
<Label htmlFor="enable-optimizer">
|
|
260
|
+
CLAUDE_SMART_ENABLE_OPTIMIZER
|
|
261
|
+
</Label>
|
|
262
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
263
|
+
Enable shared skill optimization and rollups during
|
|
264
|
+
SessionStart.
|
|
265
|
+
</p>
|
|
266
|
+
</div>
|
|
267
|
+
<Switch
|
|
268
|
+
id="enable-optimizer"
|
|
269
|
+
checked={!!hookConfig.CLAUDE_SMART_ENABLE_OPTIMIZER}
|
|
270
|
+
onCheckedChange={updateOptimizer}
|
|
271
|
+
/>
|
|
272
|
+
</div>
|
|
273
|
+
<div className="rounded-md border border-border bg-muted/20 p-3 text-xs text-muted-foreground space-y-2">
|
|
274
|
+
<div className="flex items-center justify-between gap-4">
|
|
275
|
+
<span>Effective value</span>
|
|
276
|
+
<span className="font-medium text-foreground">
|
|
277
|
+
{formatSettingValue(hookConfig.effectiveValue)}
|
|
278
|
+
</span>
|
|
279
|
+
</div>
|
|
280
|
+
<div className="flex items-center justify-between gap-4">
|
|
281
|
+
<span>Project override</span>
|
|
282
|
+
<span className="font-medium text-foreground">
|
|
283
|
+
{formatSettingValue(hookConfig.localValue)}
|
|
284
|
+
</span>
|
|
285
|
+
</div>
|
|
286
|
+
<div className="flex items-center justify-between gap-4">
|
|
287
|
+
<span>User setting</span>
|
|
288
|
+
<span className="font-medium text-foreground">
|
|
289
|
+
{formatSettingValue(hookConfig.userValue)}
|
|
290
|
+
</span>
|
|
291
|
+
</div>
|
|
292
|
+
<div className="pt-1 space-y-1">
|
|
293
|
+
<p>
|
|
294
|
+
Project file:{" "}
|
|
295
|
+
<code className="font-mono break-all">
|
|
296
|
+
{hookConfig.settingsPath}
|
|
297
|
+
</code>
|
|
298
|
+
</p>
|
|
299
|
+
<p>
|
|
300
|
+
User file:{" "}
|
|
301
|
+
<code className="font-mono break-all">
|
|
302
|
+
{hookConfig.userSettingsPath}
|
|
303
|
+
</code>
|
|
304
|
+
</p>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
) : null}
|
|
309
|
+
</section>
|
|
310
|
+
</div>
|
|
311
|
+
</>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function formatSettingValue(value: boolean | null): string {
|
|
316
|
+
if (value === null) return "Not set";
|
|
317
|
+
return value ? "Enabled" : "Disabled";
|
|
318
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { usePathname } from "next/navigation";
|
|
4
|
+
import { Server, SlidersHorizontal } from "lucide-react";
|
|
5
|
+
import { PageTabs } from "@/components/common/page-tabs";
|
|
6
|
+
|
|
7
|
+
const tabs = [
|
|
8
|
+
{
|
|
9
|
+
id: "env",
|
|
10
|
+
href: "/configure/env",
|
|
11
|
+
label: "Environment",
|
|
12
|
+
description: "Local plugin paths and runtime flags",
|
|
13
|
+
icon: SlidersHorizontal,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "server",
|
|
17
|
+
href: "/configure/server",
|
|
18
|
+
label: "Reflexio server",
|
|
19
|
+
description: "Extraction settings from the backend",
|
|
20
|
+
icon: Server,
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export default function ConfigureLayout({
|
|
25
|
+
children,
|
|
26
|
+
}: {
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
}) {
|
|
29
|
+
const pathname = usePathname();
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
33
|
+
<div className="border-b border-border bg-muted/20 px-6 py-4">
|
|
34
|
+
<PageTabs
|
|
35
|
+
items={tabs}
|
|
36
|
+
activeId={
|
|
37
|
+
tabs.find(
|
|
38
|
+
(tab) =>
|
|
39
|
+
pathname === tab.href || pathname.startsWith(`${tab.href}/`),
|
|
40
|
+
)?.id ?? "env"
|
|
41
|
+
}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
<div className="flex-1 overflow-auto">{children}</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
import { Save, CheckCircle2 } from "lucide-react";
|
|
5
|
+
import { PageHeader } from "@/components/common/page-header";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Input } from "@/components/ui/input";
|
|
8
|
+
import { Label } from "@/components/ui/label";
|
|
9
|
+
import { useSettings } from "@/hooks/use-settings";
|
|
10
|
+
import type { ReflexioConfig } from "@/lib/types";
|
|
11
|
+
|
|
12
|
+
type ExtractorField =
|
|
13
|
+
| "profile_extractor_configs"
|
|
14
|
+
| "user_playbook_extractor_configs";
|
|
15
|
+
|
|
16
|
+
export default function ConfigureServerPage() {
|
|
17
|
+
const { reflexioUrl } = useSettings();
|
|
18
|
+
const [srvConfig, setSrvConfig] = useState<ReflexioConfig | null>(null);
|
|
19
|
+
const [srvError, setSrvError] = useState<string | null>(null);
|
|
20
|
+
const [srvSaved, setSrvSaved] = useState(false);
|
|
21
|
+
const [srvSaving, setSrvSaving] = useState(false);
|
|
22
|
+
const [srvLoading, setSrvLoading] = useState(true);
|
|
23
|
+
|
|
24
|
+
const fetchSrvConfig = useCallback(async (): Promise<ReflexioConfig> => {
|
|
25
|
+
const headers: HeadersInit = {};
|
|
26
|
+
if (reflexioUrl) headers["x-reflexio-url"] = reflexioUrl;
|
|
27
|
+
const res = await fetch("/api/reflexio/api/get_config", {
|
|
28
|
+
cache: "no-store",
|
|
29
|
+
headers,
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const body = await res.json().catch(() => null);
|
|
33
|
+
const detail = body?.error
|
|
34
|
+
? `${body.error}${body.detail ? `: ${body.detail}` : ""}`
|
|
35
|
+
: `HTTP ${res.status}`;
|
|
36
|
+
throw new Error(detail);
|
|
37
|
+
}
|
|
38
|
+
return (await res.json()) as ReflexioConfig;
|
|
39
|
+
}, [reflexioUrl]);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
let alive = true;
|
|
43
|
+
async function load() {
|
|
44
|
+
setSrvLoading(true);
|
|
45
|
+
setSrvError(null);
|
|
46
|
+
try {
|
|
47
|
+
const data = await fetchSrvConfig();
|
|
48
|
+
if (alive) setSrvConfig(data);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
if (alive) setSrvError(e instanceof Error ? e.message : String(e));
|
|
51
|
+
} finally {
|
|
52
|
+
if (alive) setSrvLoading(false);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
void load();
|
|
56
|
+
return () => {
|
|
57
|
+
alive = false;
|
|
58
|
+
};
|
|
59
|
+
}, [fetchSrvConfig]);
|
|
60
|
+
|
|
61
|
+
const updateSrv = <K extends keyof ReflexioConfig>(
|
|
62
|
+
key: K,
|
|
63
|
+
value: ReflexioConfig[K],
|
|
64
|
+
) => {
|
|
65
|
+
setSrvConfig((prev) => (prev ? { ...prev, [key]: value } : prev));
|
|
66
|
+
setSrvSaved(false);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const updateExtractorPrompt = (field: ExtractorField, v: string) => {
|
|
70
|
+
setSrvConfig((prev) => {
|
|
71
|
+
if (!prev) return prev;
|
|
72
|
+
const list = prev[field];
|
|
73
|
+
if (!list || list.length === 0) return prev;
|
|
74
|
+
const next = [...list];
|
|
75
|
+
next[0] = { ...next[0], extraction_definition_prompt: v };
|
|
76
|
+
return { ...prev, [field]: next };
|
|
77
|
+
});
|
|
78
|
+
setSrvSaved(false);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const saveSrv = async () => {
|
|
82
|
+
if (!srvConfig) return;
|
|
83
|
+
setSrvSaving(true);
|
|
84
|
+
setSrvError(null);
|
|
85
|
+
try {
|
|
86
|
+
const headers: HeadersInit = { "content-type": "application/json" };
|
|
87
|
+
if (reflexioUrl) headers["x-reflexio-url"] = reflexioUrl;
|
|
88
|
+
const res = await fetch("/api/reflexio/api/set_config", {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers,
|
|
91
|
+
body: JSON.stringify(srvConfig),
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
const body = await res.json().catch(() => null);
|
|
95
|
+
const detail = body?.error
|
|
96
|
+
? `${body.error}${body.detail ? `: ${body.detail}` : ""}`
|
|
97
|
+
: `HTTP ${res.status}`;
|
|
98
|
+
throw new Error(detail);
|
|
99
|
+
}
|
|
100
|
+
const fresh = await fetchSrvConfig();
|
|
101
|
+
setSrvConfig(fresh);
|
|
102
|
+
setSrvSaved(true);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
setSrvError(e instanceof Error ? e.message : String(e));
|
|
105
|
+
} finally {
|
|
106
|
+
setSrvSaving(false);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<>
|
|
112
|
+
<PageHeader
|
|
113
|
+
title="Reflexio server"
|
|
114
|
+
description="Fetched live from the reflexio backend via /api/get_config. Requires reflexio to be running."
|
|
115
|
+
actions={
|
|
116
|
+
<Button onClick={saveSrv} disabled={!srvConfig || srvSaving} size="sm">
|
|
117
|
+
<Save className="h-3.5 w-3.5" />
|
|
118
|
+
{srvSaving ? "Saving…" : "Save"}
|
|
119
|
+
</Button>
|
|
120
|
+
}
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
<div className="p-6 max-w-2xl mx-auto space-y-6">
|
|
124
|
+
{srvError && (
|
|
125
|
+
<div className="rounded-lg border border-destructive/30 bg-destructive/5 text-destructive px-4 py-3 text-sm">
|
|
126
|
+
{srvError}
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
{srvSaved && (
|
|
130
|
+
<div className="rounded-lg border border-border bg-accent/40 px-4 py-2.5 text-sm flex items-center gap-2">
|
|
131
|
+
<CheckCircle2 className="h-4 w-4 text-muted-foreground" />
|
|
132
|
+
Saved to reflexio
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
135
|
+
|
|
136
|
+
{srvLoading ? (
|
|
137
|
+
<div className="text-sm text-muted-foreground">Loading…</div>
|
|
138
|
+
) : srvConfig ? (
|
|
139
|
+
<section className="space-y-5">
|
|
140
|
+
<div className="space-y-2">
|
|
141
|
+
<Label>Additional agent description</Label>
|
|
142
|
+
<p className="text-xs text-muted-foreground">
|
|
143
|
+
Free-form context about the agent's working environment.
|
|
144
|
+
Prepended to extractor prompts.
|
|
145
|
+
</p>
|
|
146
|
+
<textarea
|
|
147
|
+
value={srvConfig.agent_context_prompt ?? ""}
|
|
148
|
+
onChange={(e) =>
|
|
149
|
+
updateSrv("agent_context_prompt", e.target.value || null)
|
|
150
|
+
}
|
|
151
|
+
className="w-full font-mono text-xs min-h-[80px] rounded-md border border-input bg-transparent px-3 py-2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
152
|
+
placeholder="(empty)"
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div className="space-y-2">
|
|
157
|
+
<Label>Extraction window size</Label>
|
|
158
|
+
<p className="text-xs text-muted-foreground">
|
|
159
|
+
How many recent interactions are fed into each extraction run
|
|
160
|
+
(past K interactions).
|
|
161
|
+
</p>
|
|
162
|
+
<Input
|
|
163
|
+
type="number"
|
|
164
|
+
min={1}
|
|
165
|
+
value={srvConfig.window_size ?? ""}
|
|
166
|
+
onChange={(e) => {
|
|
167
|
+
const n = Number(e.target.value);
|
|
168
|
+
updateSrv(
|
|
169
|
+
"window_size",
|
|
170
|
+
Number.isFinite(n) && n > 0 ? n : undefined,
|
|
171
|
+
);
|
|
172
|
+
}}
|
|
173
|
+
className="font-mono text-xs"
|
|
174
|
+
placeholder="10"
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<div className="space-y-2">
|
|
179
|
+
<Label>Extraction stride</Label>
|
|
180
|
+
<p className="text-xs text-muted-foreground">
|
|
181
|
+
How many new interactions must accumulate before the next
|
|
182
|
+
extraction run.
|
|
183
|
+
</p>
|
|
184
|
+
<Input
|
|
185
|
+
type="number"
|
|
186
|
+
min={1}
|
|
187
|
+
value={srvConfig.stride_size ?? ""}
|
|
188
|
+
onChange={(e) => {
|
|
189
|
+
const n = Number(e.target.value);
|
|
190
|
+
updateSrv(
|
|
191
|
+
"stride_size",
|
|
192
|
+
Number.isFinite(n) && n > 0 ? n : undefined,
|
|
193
|
+
);
|
|
194
|
+
}}
|
|
195
|
+
className="font-mono text-xs"
|
|
196
|
+
placeholder="5"
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div className="space-y-2">
|
|
201
|
+
<Label>Preference focus area</Label>
|
|
202
|
+
<p className="text-xs text-muted-foreground">
|
|
203
|
+
What the preference extractor should focus on.
|
|
204
|
+
</p>
|
|
205
|
+
{srvConfig.profile_extractor_configs &&
|
|
206
|
+
srvConfig.profile_extractor_configs.length > 0 ? (
|
|
207
|
+
<textarea
|
|
208
|
+
value={
|
|
209
|
+
srvConfig.profile_extractor_configs[0]
|
|
210
|
+
.extraction_definition_prompt ?? ""
|
|
211
|
+
}
|
|
212
|
+
onChange={(e) =>
|
|
213
|
+
updateExtractorPrompt(
|
|
214
|
+
"profile_extractor_configs",
|
|
215
|
+
e.target.value,
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
className="w-full font-mono text-xs min-h-[120px] rounded-md border border-input bg-transparent px-3 py-2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
219
|
+
/>
|
|
220
|
+
) : (
|
|
221
|
+
<div className="text-xs text-muted-foreground italic">
|
|
222
|
+
No preference extractor configured on the server.
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<div className="space-y-2">
|
|
228
|
+
<Label>Project-specific skill focus area</Label>
|
|
229
|
+
<p className="text-xs text-muted-foreground">
|
|
230
|
+
What the project-specific skill extractor should focus on.
|
|
231
|
+
</p>
|
|
232
|
+
{srvConfig.user_playbook_extractor_configs &&
|
|
233
|
+
srvConfig.user_playbook_extractor_configs.length > 0 ? (
|
|
234
|
+
<textarea
|
|
235
|
+
value={
|
|
236
|
+
srvConfig.user_playbook_extractor_configs[0]
|
|
237
|
+
.extraction_definition_prompt ?? ""
|
|
238
|
+
}
|
|
239
|
+
onChange={(e) =>
|
|
240
|
+
updateExtractorPrompt(
|
|
241
|
+
"user_playbook_extractor_configs",
|
|
242
|
+
e.target.value,
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
className="w-full font-mono text-xs min-h-[120px] rounded-md border border-input bg-transparent px-3 py-2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
246
|
+
/>
|
|
247
|
+
) : (
|
|
248
|
+
<div className="text-xs text-muted-foreground italic">
|
|
249
|
+
No project-specific skill extractor configured on the server.
|
|
250
|
+
</div>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
</section>
|
|
254
|
+
) : null}
|
|
255
|
+
</div>
|
|
256
|
+
</>
|
|
257
|
+
);
|
|
258
|
+
}
|