ranranrunforit commited on
Commit
c4d05bb
·
verified ·
1 Parent(s): 3af5cbb

Upload 32 files

Browse files
requirements.txt CHANGED
@@ -1,5 +1,4 @@
1
- gradio>=6.18.0
2
- fastapi>=0.115.2,<1.0
3
  pandas>=2.0
4
  numpy>=1.24
5
  pyarrow>=14
 
1
+ gradio==6.17.3
 
2
  pandas>=2.0
3
  numpy>=1.24
4
  pyarrow>=14
server.py CHANGED
@@ -22,7 +22,7 @@ import json
22
  import os
23
 
24
  from fastapi import Request
25
- from fastapi.responses import StreamingResponse, JSONResponse
26
  from fastapi.staticfiles import StaticFiles
27
  from gradio import Server
28
 
@@ -188,8 +188,11 @@ async def research_report(name: str):
188
  # ───────────────────────── Automation ─────────────────────────
189
  @app.post("/api/automation/run")
190
  async def automation_run():
191
- msg = automation.run_pipeline(force=True)
192
- return JSONResponse({"message": msg})
 
 
 
193
 
194
 
195
  @app.get("/api/automation/state")
@@ -233,7 +236,28 @@ async def finetune_status():
233
  @app.post("/api/model/export-dataset")
234
  async def export_dataset():
235
  path = finetune_data.export()
236
- return JSONResponse({"path": path or "", "count": finetune_data.count()})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
 
239
  # ───────────────────────── Email (all tabs) ─────────────────────────
@@ -260,14 +284,11 @@ def _startup():
260
 
261
 
262
  def launch():
263
- import uvicorn
264
- uvicorn.run(
265
- app,
266
- host=os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0"),
267
- port=int(os.environ.get("GRADIO_SERVER_PORT", os.environ.get("PORT", "7860"))),
268
  )
269
-
270
-
271
  if __name__ == "__main__":
272
  launch()
273
-
 
22
  import os
23
 
24
  from fastapi import Request
25
+ from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
26
  from fastapi.staticfiles import StaticFiles
27
  from gradio import Server
28
 
 
188
  # ───────────────────────── Automation ─────────────────────────
189
  @app.post("/api/automation/run")
190
  async def automation_run():
191
+ import threading
192
+ if automation.STATE.get("running"):
193
+ return JSONResponse({"message": "Pipeline already running — watch the log."})
194
+ threading.Thread(target=lambda: automation.run_pipeline(force=True), daemon=True).start()
195
+ return JSONResponse({"message": "Pipeline started — the log updates live below."})
196
 
197
 
198
  @app.get("/api/automation/state")
 
236
  @app.post("/api/model/export-dataset")
237
  async def export_dataset():
238
  path = finetune_data.export()
239
+ if not path:
240
+ return JSONResponse({"path": "", "count": 0, "download": ""})
241
+ # copy into a served dir so the browser can download it
242
+ import shutil
243
+ served_dir = os.path.join(HERE, "exports")
244
+ os.makedirs(served_dir, exist_ok=True)
245
+ fname = os.path.basename(path)
246
+ try:
247
+ shutil.copy(path, os.path.join(served_dir, fname))
248
+ except OSError:
249
+ pass
250
+ return JSONResponse({"path": path, "count": finetune_data.count(),
251
+ "download": "/download/" + fname})
252
+
253
+
254
+ @app.get("/download/{fname}")
255
+ async def download_file(fname: str):
256
+ served = os.path.join(HERE, "exports", os.path.basename(fname))
257
+ if os.path.exists(served):
258
+ return FileResponse(served, filename=os.path.basename(fname),
259
+ media_type="application/jsonl")
260
+ return JSONResponse({"error": "not found"}, status_code=404)
261
 
262
 
263
  # ───────────────────────── Email (all tabs) ─────────────────────────
 
284
 
285
 
286
  def launch():
287
+ app.launch(
288
+ server_name=os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0"),
289
+ server_port=int(os.environ.get("GRADIO_SERVER_PORT", os.environ.get("PORT", "7860"))),
 
 
290
  )
291
+
292
+
293
  if __name__ == "__main__":
294
  launch()
 
ui_kits/chan-compass/app.jsx CHANGED
@@ -8,15 +8,43 @@ function Glyph({ name, size = 18, color, style }) {
8
  return <i data-lucide={name} style={{ width:size, height:size, color, display:'inline-flex', ...style }}></i>;
9
  }
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  function App() {
12
  const [tab, setTab] = useS('signals');
13
  const items = [
14
- { id:'signals', label:'Signals', icon:<Glyph name="trending-up" size={16}/> },
15
- { id:'rotation', label:'Sector Rotation', icon:<Glyph name="refresh-cw" size={16}/> },
16
- { id:'news', label:'Watchlist News', icon:<Glyph name="newspaper" size={16}/>, count:2 },
17
- { id:'research', label:'Auto Research', icon:<Glyph name="flask-conical" size={16}/> },
18
- { id:'automation', label:'Automation', icon:<Glyph name="clock" size={16}/> },
19
- { id:'model', label:'Model', icon:<Glyph name="cpu" size={16}/> },
20
  ];
21
  const View = {
22
  signals: window.SignalsView, rotation: window.RotationView,
@@ -45,7 +73,7 @@ function App() {
45
  </div>
46
 
47
  <main className="cc-main">
48
- {View ? <View/> : null}
49
  </main>
50
 
51
  <footer className="cc-footer">
 
8
  return <i data-lucide={name} style={{ width:size, height:size, color, display:'inline-flex', ...style }}></i>;
9
  }
10
 
11
+ /* Error boundary — a render error in any view shows a message instead of
12
+ blanking the entire page (the cause of "everything disappears"). */
13
+ class Boundary extends React.Component {
14
+ constructor(p){ super(p); this.state={err:null}; }
15
+ static getDerivedStateFromError(err){ return {err}; }
16
+ componentDidCatch(err, info){ console.error("View error:", err, info); }
17
+ render(){
18
+ if (this.state.err) {
19
+ return (
20
+ <div style={{padding:24}}>
21
+ <div style={{background:"#fff", border:"1px solid var(--border-hairline)",
22
+ borderLeft:"4px solid var(--down)", borderRadius:"var(--radius-lg)", padding:"16px 20px"}}>
23
+ <div style={{font:"var(--font-weight-bold) 15px var(--font-sans)", color:"var(--down)", marginBottom:6}}>
24
+ Something went wrong rendering this tab</div>
25
+ <div className="mono" style={{fontSize:12.5, color:"var(--text-muted)", whiteSpace:"pre-wrap"}}>
26
+ {String(this.state.err && this.state.err.message || this.state.err)}</div>
27
+ <button onClick={()=>this.setState({err:null})}
28
+ style={{marginTop:12, padding:"6px 14px", borderRadius:"var(--radius-full)",
29
+ border:"1px solid var(--border-hairline)", background:"var(--surface-card)", cursor:"pointer"}}>
30
+ Try again</button>
31
+ </div>
32
+ </div>
33
+ );
34
+ }
35
+ return this.props.children;
36
+ }
37
+ }
38
+
39
  function App() {
40
  const [tab, setTab] = useS('signals');
41
  const items = [
42
+ { id:'signals', label:'Signals', icon:<Glyph name="trending-up" size={16}/> },
43
+ { id:'rotation', label:'Sector Rotation', icon:<Glyph name="refresh-cw" size={16}/> },
44
+ { id:'news', label:'Watchlist News', icon:<Glyph name="newspaper" size={16}/> },
45
+ { id:'research', label:'Auto Research', icon:<Glyph name="flask-conical" size={16}/> },
46
+ { id:'automation', label:'Automation', icon:<Glyph name="clock" size={16}/> },
47
+ { id:'model', label:'Model', icon:<Glyph name="cpu" size={16}/> },
48
  ];
49
  const View = {
50
  signals: window.SignalsView, rotation: window.RotationView,
 
73
  </div>
74
 
75
  <main className="cc-main">
76
+ <Boundary key={tab}>{View ? <View/> : null}</Boundary>
77
  </main>
78
 
79
  <footer className="cc-footer">
ui_kits/chan-compass/views.jsx CHANGED
@@ -79,7 +79,8 @@ function SignalsView() {
79
  await API.signalSummary(sel, (m) => setAiText(m.text || ""));
80
  setAiBusy(false);
81
  };
82
- const counts = rows.reduce((a,r)=>{ const k=(r.Tomorrow||"").replace(/[^A-Z]/g,"");
 
83
  a[k]=(a[k]||0)+1; return a; }, {});
84
 
85
  return (
@@ -145,16 +146,35 @@ function SignalsView() {
145
  /* ─────────── Rotation ─────────── */
146
  function RotationView() {
147
  const [d1, setD1] = useState([]);
 
 
148
  const [asof, setAsof] = useState("");
149
  const [ai, setAi] = useState(""); const [busy, setBusy] = useState(false);
150
  const [loading, setLoading] = useState(false);
151
  const refresh = async () => {
152
  setLoading(true);
153
- try { const r = await API.rotation(); setD1(r.d1||[]); setAsof(r.asof||""); }
 
154
  catch(e){ setAsof("X "+e.message); }
155
  setLoading(false);
156
  };
157
  const narrate = async () => { setBusy(true); setAi(""); await API.rotationNarrative(m=>setAi(m.text||"")); setBusy(false); };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  return (
159
  <div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)"}}>
160
  <InlineAlert variant="informative" title="Where capital is flowing">
@@ -170,19 +190,9 @@ function RotationView() {
170
  </Button>
171
  {asof && <span style={{marginLeft:"auto"}}><StatusLight variant="neutral">{asof}</StatusLight></span>}
172
  </div>
173
- {d1.length>0 && (
174
- <Card title="Sector rotation" subtitle="1-day flow proxy - sorted">
175
- <div className="cc-table"><table>
176
- <thead><tr>{Object.keys(d1[0]).map(k=><th key={k} className={(k!=="Sector"&&k!=="ETF")?"num":""}>{k}</th>)}</tr></thead>
177
- <tbody>{d1.map((r,i)=>(
178
- <tr key={i}>{Object.entries(r).map(([k,v])=>(
179
- <td key={k} className={(k!=="Sector"&&k!=="ETF")?"num mono":""}
180
- style={{color: typeof v==="string"&&v.includes("%")?dirColor(parseFloat(v)):undefined}}>{v}</td>
181
- ))}</tr>
182
- ))}</tbody>
183
- </table></div>
184
- </Card>
185
- )}
186
  {ai && <InlineAlert variant="ai" title="Narrator sub-agent - Qwen3-1.7B">{ai}</InlineAlert>}
187
  <Card title="Email" subtitle="Send the current rotation narrative">
188
  <EmailRow getContent={()=>ai} tag="Sector rotation"/>
@@ -200,7 +210,7 @@ function NewsView() {
200
  const save = async () => { await API.saveHoldings(hold); };
201
  const check = async () => { setBusy(true); setOut(""); await API.checkNews(m=>setOut(m.text||"")); setBusy(false); };
202
  return (
203
- <div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)", maxWidth:820}}>
204
  <Card title="Watchlist news" subtitle="For each holding, only today's news is checked - streamed as it arrives">
205
  <div style={{display:"flex", gap:"var(--space-150)", alignItems:"flex-end", marginBottom:"var(--space-200)"}}>
206
  <Field label="My holdings" mono value={hold} onChange={e=>setHold(e.target.value)} className="grow"/>
@@ -318,8 +328,12 @@ function ModelView() {
318
  },[]);
319
  useEffect(()=>{ API.finetuneStatus().then(r=>setFt(r.status)).catch(()=>{}); },[]);
320
  const runTest = async () => { setTest("Testing..."); const r=await API.modelTest(); setTest(r.result); };
321
- const exportDs = async () => { const r = await API.exportDataset();
322
- setDl(r.path ? "Exported "+r.count+" pairs -> "+r.path : "No pairs captured yet."); };
 
 
 
 
323
  return (
324
  <div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)", maxWidth:760}}>
325
  <InlineAlert variant="informative" title="Everything runs locally through llama.cpp">
@@ -341,7 +355,8 @@ function ModelView() {
341
  <Card title="Fine-tuning dataset" subtitle="Every Signals AI summary is captured as a training pair on /data">
342
  <div className="dim" style={{marginBottom:10}}><Markdown text={ft}/></div>
343
  <Button variant="accent" onClick={exportDs}><Icon name="download" size={15}/> Export dataset (JSONL)</Button>
344
- {dl && <div className="dim" style={{marginTop:8, fontSize:13}}>{dl}</div>}
 
345
  </Card>
346
  </div>
347
  );
 
79
  await API.signalSummary(sel, (m) => setAiText(m.text || ""));
80
  setAiBusy(false);
81
  };
82
+ const counts = rows.reduce((a,r)=>{ const t=(r.Tomorrow||"").toUpperCase();
83
+ const k = t.includes("BUY")?"BUY":t.includes("SELL")?"SELL":t.includes("HOLD")?"HOLD":"WAIT";
84
  a[k]=(a[k]||0)+1; return a; }, {});
85
 
86
  return (
 
146
  /* ─────────── Rotation ─────────── */
147
  function RotationView() {
148
  const [d1, setD1] = useState([]);
149
+ const [d5, setD5] = useState([]);
150
+ const [d20, setD20] = useState([]);
151
  const [asof, setAsof] = useState("");
152
  const [ai, setAi] = useState(""); const [busy, setBusy] = useState(false);
153
  const [loading, setLoading] = useState(false);
154
  const refresh = async () => {
155
  setLoading(true);
156
+ try { const r = await API.rotation();
157
+ setD1(r.d1||[]); setD5(r.d5||[]); setD20(r.d20||[]); setAsof(r.asof||""); }
158
  catch(e){ setAsof("X "+e.message); }
159
  setLoading(false);
160
  };
161
  const narrate = async () => { setBusy(true); setAi(""); await API.rotationNarrative(m=>setAi(m.text||"")); setBusy(false); };
162
+
163
+ const Table = (rows) => {
164
+ if (!rows || !rows.length) return null;
165
+ return (
166
+ <div className="cc-table"><table>
167
+ <thead><tr>{Object.keys(rows[0]).map(k=><th key={k} className={(k!=="Sector"&&k!=="ETF")?"num":""}>{k}</th>)}</tr></thead>
168
+ <tbody>{rows.map((r,i)=>(
169
+ <tr key={i}>{Object.entries(r).map(([k,v])=>(
170
+ <td key={k} className={(k!=="Sector"&&k!=="ETF")?"num mono":""}
171
+ style={{color: typeof v==="string"&&v.includes("%")?dirColor(parseFloat(v)):undefined}}>{v}</td>
172
+ ))}</tr>
173
+ ))}</tbody>
174
+ </table></div>
175
+ );
176
+ };
177
+
178
  return (
179
  <div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)"}}>
180
  <InlineAlert variant="informative" title="Where capital is flowing">
 
190
  </Button>
191
  {asof && <span style={{marginLeft:"auto"}}><StatusLight variant="neutral">{asof}</StatusLight></span>}
192
  </div>
193
+ {d1.length>0 && <Card title="Sector rotation · 1-day flow" subtitle="change % × dollar volume · sorted">{Table(d1)}</Card>}
194
+ {d5.length>0 && <Card title="Sector rotation · 5-day flow" subtitle="medium-term trend">{Table(d5)}</Card>}
195
+ {d20.length>0 && <Card title="Sector rotation · 20-day flow" subtitle="longer-term trend + RS vs SPY">{Table(d20)}</Card>}
 
 
 
 
 
 
 
 
 
 
196
  {ai && <InlineAlert variant="ai" title="Narrator sub-agent - Qwen3-1.7B">{ai}</InlineAlert>}
197
  <Card title="Email" subtitle="Send the current rotation narrative">
198
  <EmailRow getContent={()=>ai} tag="Sector rotation"/>
 
210
  const save = async () => { await API.saveHoldings(hold); };
211
  const check = async () => { setBusy(true); setOut(""); await API.checkNews(m=>setOut(m.text||"")); setBusy(false); };
212
  return (
213
+ <div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)"}}>
214
  <Card title="Watchlist news" subtitle="For each holding, only today's news is checked - streamed as it arrives">
215
  <div style={{display:"flex", gap:"var(--space-150)", alignItems:"flex-end", marginBottom:"var(--space-200)"}}>
216
  <Field label="My holdings" mono value={hold} onChange={e=>setHold(e.target.value)} className="grow"/>
 
328
  },[]);
329
  useEffect(()=>{ API.finetuneStatus().then(r=>setFt(r.status)).catch(()=>{}); },[]);
330
  const runTest = async () => { setTest("Testing..."); const r=await API.modelTest(); setTest(r.result); };
331
+ const [dlUrl, setDlUrl] = useState("");
332
+ const exportDs = async () => {
333
+ const r = await API.exportDataset();
334
+ if (r.download) { setDl("Exported " + r.count + " pairs."); setDlUrl(r.download); }
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)", maxWidth:760}}>
339
  <InlineAlert variant="informative" title="Everything runs locally through llama.cpp">
 
355
  <Card title="Fine-tuning dataset" subtitle="Every Signals AI summary is captured as a training pair on /data">
356
  <div className="dim" style={{marginBottom:10}}><Markdown text={ft}/></div>
357
  <Button variant="accent" onClick={exportDs}><Icon name="download" size={15}/> Export dataset (JSONL)</Button>
358
+ {dl && <div className="dim" style={{marginTop:8, fontSize:13}}>{dl}{dlUrl &&
359
+ <> <a href={dlUrl} download style={{color:"var(--accent)", marginLeft:6}}>Download JSONL</a></>}</div>}
360
  </Card>
361
  </div>
362
  );