Spaces:
Runtime error
Runtime error
Upload 33 files
Browse files- server.py +24 -1
- ui_kits/chan-compass/api.js +1 -0
- ui_kits/chan-compass/app.jsx +21 -2
- ui_kits/chan-compass/views.jsx +46 -13
server.py
CHANGED
|
@@ -201,6 +201,7 @@ async def automation_state():
|
|
| 201 |
"log": automation.STATE.get("log", [])[-40:],
|
| 202 |
"schedule": automation.schedule_info(),
|
| 203 |
"traces": research_agent.list_traces(),
|
|
|
|
| 204 |
})
|
| 205 |
|
| 206 |
|
|
@@ -222,9 +223,31 @@ async def model_status():
|
|
| 222 |
})
|
| 223 |
|
| 224 |
|
|
|
|
|
|
|
|
|
|
| 225 |
@app.post("/api/model/test")
|
| 226 |
async def model_test():
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
|
| 230 |
@app.get("/api/model/finetune-status")
|
|
|
|
| 201 |
"log": automation.STATE.get("log", [])[-40:],
|
| 202 |
"schedule": automation.schedule_info(),
|
| 203 |
"traces": research_agent.list_traces(),
|
| 204 |
+
"running": bool(automation.STATE.get("running")),
|
| 205 |
})
|
| 206 |
|
| 207 |
|
|
|
|
| 223 |
})
|
| 224 |
|
| 225 |
|
| 226 |
+
_SELFTEST = {"running": False, "result": ""}
|
| 227 |
+
|
| 228 |
+
|
| 229 |
@app.post("/api/model/test")
|
| 230 |
async def model_test():
|
| 231 |
+
import threading
|
| 232 |
+
if _SELFTEST["running"]:
|
| 233 |
+
return JSONResponse({"started": True, "running": True})
|
| 234 |
+
|
| 235 |
+
def _run():
|
| 236 |
+
_SELFTEST["running"] = True
|
| 237 |
+
_SELFTEST["result"] = ""
|
| 238 |
+
try:
|
| 239 |
+
_SELFTEST["result"] = llm_local.quick_test()
|
| 240 |
+
except Exception as e:
|
| 241 |
+
_SELFTEST["result"] = f"❌ {e}"
|
| 242 |
+
_SELFTEST["running"] = False
|
| 243 |
+
|
| 244 |
+
threading.Thread(target=_run, daemon=True).start()
|
| 245 |
+
return JSONResponse({"started": True, "running": True})
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
@app.get("/api/model/test-status")
|
| 249 |
+
async def model_test_status():
|
| 250 |
+
return JSONResponse({"running": _SELFTEST["running"], "result": _SELFTEST["result"]})
|
| 251 |
|
| 252 |
|
| 253 |
@app.get("/api/model/finetune-status")
|
ui_kits/chan-compass/api.js
CHANGED
|
@@ -56,6 +56,7 @@ window.CCApi = (function () {
|
|
| 56 |
publishTraces: (repo) => jpost("/api/automation/publish-traces", { repo }),
|
| 57 |
modelStatus: () => jget("/api/model/status"),
|
| 58 |
modelTest: () => jpost("/api/model/test", {}),
|
|
|
|
| 59 |
finetuneStatus: () => jget("/api/model/finetune-status"),
|
| 60 |
exportDataset: () => jpost("/api/model/export-dataset", {}),
|
| 61 |
email: (content, to, tag) => jpost("/api/email", { content, to, tag }),
|
|
|
|
| 56 |
publishTraces: (repo) => jpost("/api/automation/publish-traces", { repo }),
|
| 57 |
modelStatus: () => jget("/api/model/status"),
|
| 58 |
modelTest: () => jpost("/api/model/test", {}),
|
| 59 |
+
modelTestStatus: () => jget("/api/model/test-status"),
|
| 60 |
finetuneStatus: () => jget("/api/model/finetune-status"),
|
| 61 |
exportDataset: () => jpost("/api/model/export-dataset", {}),
|
| 62 |
email: (content, to, tag) => jpost("/api/email", { content, to, tag }),
|
ui_kits/chan-compass/app.jsx
CHANGED
|
@@ -3,9 +3,28 @@ const _DS = window.ChanCompassSpectrum2DesignSystem_b52b37;
|
|
| 3 |
const { Tabs: CCTabs, StatusLight: CCStatus, Badge: CCBadge } = _DS;
|
| 4 |
const { useState: useS, useEffect: useE } = React;
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
function Glyph({ name, size = 18, color, style }) {
|
| 7 |
-
|
| 8 |
-
return <
|
|
|
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
/* Error boundary — a render error in any view shows a message instead of
|
|
|
|
| 3 |
const { Tabs: CCTabs, StatusLight: CCStatus, Badge: CCBadge } = _DS;
|
| 4 |
const { useState: useS, useEffect: useE } = React;
|
| 5 |
|
| 6 |
+
function _pascalG(name) {
|
| 7 |
+
return String(name).split(/[-_]/).map(s=>s.charAt(0).toUpperCase()+s.slice(1)).join("");
|
| 8 |
+
}
|
| 9 |
+
function _glyphSvg(name, size, color) {
|
| 10 |
+
const L = window.lucide;
|
| 11 |
+
if (!L || !L.icons) return "";
|
| 12 |
+
const node = L.icons[_pascalG(name)] || L.icons[name];
|
| 13 |
+
if (!node) return "";
|
| 14 |
+
const children = Array.isArray(node) ? node : (node.tags || node[2] || []);
|
| 15 |
+
const inner = (children || []).map(([tag, attrs]) => {
|
| 16 |
+
const a = Object.entries(attrs||{}).map(([k,v])=>`${k}="${v}"`).join(" ");
|
| 17 |
+
return `<${tag} ${a}/>`;
|
| 18 |
+
}).join("");
|
| 19 |
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" `
|
| 20 |
+
+ `viewBox="0 0 24 24" fill="none" stroke="${color||'currentColor'}" `
|
| 21 |
+
+ `stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${inner}</svg>`;
|
| 22 |
+
}
|
| 23 |
function Glyph({ name, size = 18, color, style }) {
|
| 24 |
+
const html = _glyphSvg(name, size, color);
|
| 25 |
+
return <span style={{ width:size, height:size, display:'inline-flex',
|
| 26 |
+
alignItems:'center', justifyContent:'center', ...style }}
|
| 27 |
+
dangerouslySetInnerHTML={{__html: html}}/>;
|
| 28 |
}
|
| 29 |
|
| 30 |
/* Error boundary — a render error in any view shows a message instead of
|
ui_kits/chan-compass/views.jsx
CHANGED
|
@@ -5,9 +5,30 @@ const { Button, Field, Checkbox, Switch, Card, InlineAlert, Badge, StatusLight,
|
|
| 5 |
const { useState, useEffect, useRef } = React;
|
| 6 |
const API = window.CCApi;
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
function Icon({ name, size = 18, color, style }) {
|
| 9 |
-
|
| 10 |
-
return <
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
|
| 13 |
const num = (v) => (typeof v === "number" ? v : parseFloat(v));
|
|
@@ -281,24 +302,27 @@ function ResearchView() {
|
|
| 281 |
|
| 282 |
/* ─────────── Automation ─────────── */
|
| 283 |
function AutomationView() {
|
| 284 |
-
const [state, setState] = useState({log:[], schedule:"", traces:""});
|
| 285 |
const [repo, setRepo] = useState("ranranrunforit/chan-compass-agent-traces");
|
| 286 |
const [pubStatus, setPubStatus] = useState("");
|
| 287 |
-
const [
|
| 288 |
useEffect(()=>{
|
| 289 |
-
const tick = ()=> API.autoState().then(setState).catch(()=>{});
|
| 290 |
-
tick(); const id=setInterval(tick,
|
| 291 |
},[]);
|
| 292 |
-
const
|
|
|
|
| 293 |
const publish = async () => { setPubStatus("Publishing...");
|
| 294 |
try { const r = await API.publishTraces(repo); setPubStatus(r.status); }
|
| 295 |
catch(e){ setPubStatus("X "+e.message); } };
|
| 296 |
return (
|
| 297 |
<div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)"}}>
|
| 298 |
-
<
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
|
|
|
|
|
|
| 302 |
<div className="cc-log" style={{maxHeight:300, overflow:"auto"}}>
|
| 303 |
{(state.log||[]).map((l,i)=><div key={i} className="mono" style={{fontSize:12}}>{l}</div>)}
|
| 304 |
{(!state.log || !state.log.length) && <span className="dim">(no log yet)</span>}
|
|
@@ -327,7 +351,16 @@ function ModelView() {
|
|
| 327 |
tick(); const id=setInterval(tick, 3000); return ()=>clearInterval(id);
|
| 328 |
},[]);
|
| 329 |
useEffect(()=>{ API.finetuneStatus().then(r=>setFt(r.status)).catch(()=>{}); },[]);
|
| 330 |
-
const runTest = async () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
const [dlUrl, setDlUrl] = useState("");
|
| 332 |
const exportDs = async () => {
|
| 333 |
const r = await API.exportDataset();
|
|
@@ -335,7 +368,7 @@ function ModelView() {
|
|
| 335 |
else { setDl("No pairs captured yet — run a few Signals AI summaries first."); setDlUrl(""); }
|
| 336 |
};
|
| 337 |
return (
|
| 338 |
-
<div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)"
|
| 339 |
<InlineAlert variant="informative" title="Everything runs locally through llama.cpp">
|
| 340 |
Qwen3 GGUF weights, all below the 32B cap. The Summary sub-agent uses a published fine-tune (Chan-Tuned Qwen3-1.7B).
|
| 341 |
</InlineAlert>
|
|
|
|
| 5 |
const { useState, useEffect, useRef } = React;
|
| 6 |
const API = window.CCApi;
|
| 7 |
|
| 8 |
+
// kebab-case (data-lucide name) → PascalCase (lucide.icons key)
|
| 9 |
+
function _pascal(name) {
|
| 10 |
+
return String(name).split(/[-_]/).map(s=>s.charAt(0).toUpperCase()+s.slice(1)).join("");
|
| 11 |
+
}
|
| 12 |
+
function _lucideSvg(name, size, color) {
|
| 13 |
+
const L = window.lucide;
|
| 14 |
+
if (!L || !L.icons) return "";
|
| 15 |
+
const node = L.icons[_pascal(name)] || L.icons[name];
|
| 16 |
+
if (!node) return "";
|
| 17 |
+
// lucide icon data is an array of [tag, attrs] children
|
| 18 |
+
const children = Array.isArray(node) ? node : (node.tags || node[2] || []);
|
| 19 |
+
const inner = (children || []).map(([tag, attrs]) => {
|
| 20 |
+
const a = Object.entries(attrs||{}).map(([k,v])=>`${k}="${v}"`).join(" ");
|
| 21 |
+
return `<${tag} ${a}/>`;
|
| 22 |
+
}).join("");
|
| 23 |
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" `
|
| 24 |
+
+ `viewBox="0 0 24 24" fill="none" stroke="${color||'currentColor'}" `
|
| 25 |
+
+ `stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${inner}</svg>`;
|
| 26 |
+
}
|
| 27 |
function Icon({ name, size = 18, color, style }) {
|
| 28 |
+
const html = _lucideSvg(name, size, color);
|
| 29 |
+
return <span style={{ width:size, height:size, display:"inline-flex",
|
| 30 |
+
alignItems:"center", justifyContent:"center", ...style }}
|
| 31 |
+
dangerouslySetInnerHTML={{__html: html}}/>;
|
| 32 |
}
|
| 33 |
|
| 34 |
const num = (v) => (typeof v === "number" ? v : parseFloat(v));
|
|
|
|
| 302 |
|
| 303 |
/* ─────────── Automation ─────────── */
|
| 304 |
function AutomationView() {
|
| 305 |
+
const [state, setState] = useState({log:[], schedule:"", traces:"", running:false});
|
| 306 |
const [repo, setRepo] = useState("ranranrunforit/chan-compass-agent-traces");
|
| 307 |
const [pubStatus, setPubStatus] = useState("");
|
| 308 |
+
const [starting, setStarting] = useState(false);
|
| 309 |
useEffect(()=>{
|
| 310 |
+
const tick = ()=> API.autoState().then(s=>{ setState(s); if (s.running) setStarting(false); }).catch(()=>{});
|
| 311 |
+
tick(); const id=setInterval(tick, 2000); return ()=>clearInterval(id);
|
| 312 |
},[]);
|
| 313 |
+
const busy = starting || state.running;
|
| 314 |
+
const runNow = async () => { setStarting(true); await API.autoRun().catch(()=>setStarting(false)); };
|
| 315 |
const publish = async () => { setPubStatus("Publishing...");
|
| 316 |
try { const r = await API.publishTraces(repo); setPubStatus(r.status); }
|
| 317 |
catch(e){ setPubStatus("X "+e.message); } };
|
| 318 |
return (
|
| 319 |
<div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)"}}>
|
| 320 |
+
<Card title="Daily pipeline" subtitle="schedule + status">
|
| 321 |
+
<Markdown text={state.schedule||""}/>
|
| 322 |
+
</Card>
|
| 323 |
+
<Card title="Pipeline" subtitle="data → signals → rotation → news → auto-research"
|
| 324 |
+
action={<Button variant="accent" size="sm" onClick={runNow} disabled={busy}>
|
| 325 |
+
<Icon name="zap" size={14}/> {busy?"Running…":"Run now"}</Button>}>
|
| 326 |
<div className="cc-log" style={{maxHeight:300, overflow:"auto"}}>
|
| 327 |
{(state.log||[]).map((l,i)=><div key={i} className="mono" style={{fontSize:12}}>{l}</div>)}
|
| 328 |
{(!state.log || !state.log.length) && <span className="dim">(no log yet)</span>}
|
|
|
|
| 351 |
tick(); const id=setInterval(tick, 3000); return ()=>clearInterval(id);
|
| 352 |
},[]);
|
| 353 |
useEffect(()=>{ API.finetuneStatus().then(r=>setFt(r.status)).catch(()=>{}); },[]);
|
| 354 |
+
const runTest = async () => {
|
| 355 |
+
setTest("⏳ Testing sub-agents… (this can take a moment on CPU)");
|
| 356 |
+
await API.modelTest();
|
| 357 |
+
const poll = setInterval(async () => {
|
| 358 |
+
try {
|
| 359 |
+
const s = await API.modelTestStatus();
|
| 360 |
+
if (!s.running && s.result) { setTest(s.result); clearInterval(poll); }
|
| 361 |
+
} catch (_) { clearInterval(poll); }
|
| 362 |
+
}, 1500);
|
| 363 |
+
};
|
| 364 |
const [dlUrl, setDlUrl] = useState("");
|
| 365 |
const exportDs = async () => {
|
| 366 |
const r = await API.exportDataset();
|
|
|
|
| 368 |
else { setDl("No pairs captured yet — run a few Signals AI summaries first."); setDlUrl(""); }
|
| 369 |
};
|
| 370 |
return (
|
| 371 |
+
<div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)"}}>
|
| 372 |
<InlineAlert variant="informative" title="Everything runs locally through llama.cpp">
|
| 373 |
Qwen3 GGUF weights, all below the 32B cap. The Summary sub-agent uses a published fine-tune (Chan-Tuned Qwen3-1.7B).
|
| 374 |
</InlineAlert>
|