bhg-helper 1.0.0
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/README.md +78 -0
- package/api/app.ts +53 -0
- package/api/index.ts +9 -0
- package/api/lib/logger.ts +65 -0
- package/api/lib/paths.ts +27 -0
- package/api/lib/providers.ts +66 -0
- package/api/lib/repository.ts +153 -0
- package/api/lib/types.ts +43 -0
- package/api/relay/config.ts +76 -0
- package/api/relay/protocol.ts +393 -0
- package/api/relay/server.ts +283 -0
- package/api/routes/backups.ts +73 -0
- package/api/routes/config.ts +197 -0
- package/api/routes/install.ts +158 -0
- package/api/routes/logs.ts +20 -0
- package/api/routes/providers.ts +13 -0
- package/api/routes/relay.ts +106 -0
- package/api/server.ts +40 -0
- package/cli/cli.js +454 -0
- package/dist/assets/index-BjvGHrGe.js +156 -0
- package/dist/assets/index-CQrGCyBr.css +1 -0
- package/dist/favicon.svg +4 -0
- package/dist/index.html +20 -0
- package/index.html +19 -0
- package/nodemon.json +10 -0
- package/package.json +82 -0
- package/postcss.config.js +10 -0
- package/scripts/install.bat +32 -0
- package/scripts/start.bat +46 -0
- package/scripts/start.ps1 +45 -0
- package/src/App.tsx +73 -0
- package/src/assets/react.svg +1 -0
- package/src/components/ConsolePanel.tsx +44 -0
- package/src/components/Empty.tsx +8 -0
- package/src/components/ErrorBoundary.tsx +54 -0
- package/src/components/Layout.tsx +17 -0
- package/src/components/Page.tsx +130 -0
- package/src/components/Sidebar.tsx +56 -0
- package/src/hooks/useTheme.ts +29 -0
- package/src/index.css +1350 -0
- package/src/lib/api.ts +120 -0
- package/src/lib/store.ts +166 -0
- package/src/lib/types.ts +117 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +10 -0
- package/src/pages/ConsolePage.tsx +48 -0
- package/src/pages/Dashboard.tsx +101 -0
- package/src/pages/Install.tsx +195 -0
- package/src/pages/Relay.tsx +409 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.js +13 -0
- package/tsconfig.json +40 -0
- package/vite.config.ts +28 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--bg-0: #0b0e14;--bg-1: #0f131c;--bg-2: #141a23;--bg-3: #1a2230;--bg-elev: #1c2532;--line-1: #1f2733;--line-2: #2a3441;--line-3: #3a4555;--ink-0: #e6edf3;--ink-1: #b8c1cc;--ink-2: #7d8590;--ink-3: #4a5563;--accent-amber: #ffb454;--accent-amber-dim: #b87f30;--accent-green: #3fb950;--accent-green-dim: #2a7a36;--accent-red: #f85149;--accent-red-dim: #a9322c;--accent-cyan: #58a6ff;--accent-cyan-dim: #2a5d9a;--accent-violet: #a371f7;--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", sans-serif;--font-display: "Space Grotesk", "Inter", sans-serif;--font-mono: "JetBrains Mono", "Cascadia Code", "Consolas", monospace;--sp-1: 4px;--sp-2: 8px;--sp-3: 12px;--sp-4: 16px;--sp-5: 20px;--sp-6: 24px;--sp-7: 32px;--sp-8: 40px;--sp-9: 48px;--sp-10: 64px;--r-0: 0px;--r-1: 2px;--r-2: 4px;--r-3: 6px;--r-pill: 999px;--shadow-1: 0 1px 0 rgba(0, 0, 0, .5), 0 0 0 1px var(--line-2);--shadow-glow-amber: 0 0 12px rgba(255, 180, 84, .35);--shadow-glow-green: 0 0 12px rgba(63, 185, 80, .35);--shadow-glow-red: 0 0 12px rgba(248, 81, 73, .35)}*{box-sizing:border-box}html,body,#root{height:100%;margin:0;padding:0}body{background:var(--bg-0);color:var(--ink-0);font-family:var(--font-sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility;overflow:hidden}body:before{content:"";position:fixed;top:0;right:0;bottom:0;left:0;background-image:linear-gradient(rgba(58,69,85,.06) 1px,transparent 1px),linear-gradient(90deg,rgba(58,69,85,.06) 1px,transparent 1px);background-size:40px 40px;pointer-events:none;z-index:0}body:after{content:"";position:fixed;top:0;right:0;bottom:0;left:0;background:radial-gradient(ellipse at top,rgba(88,166,255,.04),transparent 60%),radial-gradient(ellipse at bottom right,rgba(163,113,247,.04),transparent 50%);pointer-events:none;z-index:0}#root{position:relative;z-index:1}button{font-family:inherit;font-size:inherit;cursor:pointer;border:none;background:none;color:inherit}input,select,textarea{font-family:inherit;font-size:inherit;color:var(--ink-0)}a{color:var(--accent-cyan);text-decoration:none}::-moz-selection{background:var(--accent-cyan-dim);color:var(--ink-0)}::selection{background:var(--accent-cyan-dim);color:var(--ink-0)}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-0)}::-webkit-scrollbar-thumb{background:var(--line-2);border-radius:0}::-webkit-scrollbar-thumb:hover{background:var(--line-3)}.app-shell{display:grid;grid-template-columns:260px 1fr;grid-template-rows:1fr 200px;grid-template-areas:"sidebar main" "sidebar console";height:100vh;width:100vw}.app-sidebar{grid-area:sidebar;background:var(--bg-1);border-right:1px solid var(--line-2);display:flex;flex-direction:column;overflow:hidden}.app-main{grid-area:main;overflow:hidden;position:relative}.app-console{grid-area:console;border-top:1px solid var(--line-2);background:var(--bg-1);overflow:hidden;display:flex;flex-direction:column}.sidebar-brand{padding:var(--sp-6) var(--sp-5) var(--sp-4);border-bottom:1px solid var(--line-2);position:relative}.sidebar-brand-row{display:flex;align-items:center;gap:var(--sp-3)}.sidebar-logo{width:28px;height:28px;background:linear-gradient(135deg,var(--accent-amber),var(--accent-red));display:grid;place-items:center;font-family:var(--font-mono);font-weight:700;font-size:14px;color:var(--bg-0);position:relative}.sidebar-logo:after{content:"";position:absolute;top:-3px;right:-3px;bottom:-3px;left:-3px;border:1px solid var(--line-3);pointer-events:none}.sidebar-title{font-family:var(--font-display);font-size:16px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.sidebar-subtitle{font-family:var(--font-mono);font-size:10px;color:var(--ink-2);letter-spacing:.1em;text-transform:uppercase;margin-top:2px}.sidebar-meta{margin-top:var(--sp-4);padding-top:var(--sp-3);border-top:1px dashed var(--line-2);display:flex;flex-direction:column;gap:var(--sp-1);font-family:var(--font-mono);font-size:10px;color:var(--ink-2)}.sidebar-meta-row{display:flex;justify-content:space-between;gap:var(--sp-2)}.sidebar-meta-row .key{text-transform:uppercase;letter-spacing:.08em}.sidebar-meta-row .val{color:var(--ink-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px}.sidebar-nav{flex:1;padding:var(--sp-3) var(--sp-2);overflow-y:auto}.sidebar-section{font-family:var(--font-mono);font-size:10px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.12em;padding:var(--sp-3) var(--sp-3) var(--sp-2)}.sidebar-link{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-3);color:var(--ink-1);border-left:2px solid transparent;font-size:13px;font-weight:500;transition:all .12s ease;position:relative;text-transform:uppercase;letter-spacing:.04em;font-family:var(--font-display)}.sidebar-link:hover{background:var(--bg-2);color:var(--ink-0)}.sidebar-link.active{background:var(--bg-2);color:var(--ink-0);border-left-color:var(--accent-amber)}.sidebar-link.active:before{content:"▸";position:absolute;left:-2px;top:50%;transform:translateY(-50%);color:var(--accent-amber);font-size:10px}.sidebar-link svg{width:16px;height:16px;stroke-width:1.5}.sidebar-link .badge{margin-left:auto;font-family:var(--font-mono);font-size:9px;color:var(--ink-2);background:var(--bg-3);padding:1px 6px;border:1px solid var(--line-2);border-radius:1px}.sidebar-footer{padding:var(--sp-3) var(--sp-4);border-top:1px solid var(--line-2);display:flex;align-items:center;gap:var(--sp-2);font-family:var(--font-mono);font-size:10px;color:var(--ink-2);text-transform:uppercase;letter-spacing:.08em}.page{height:100%;display:flex;flex-direction:column;overflow:hidden}.page-header{padding:var(--sp-5) var(--sp-7) var(--sp-4);border-bottom:1px solid var(--line-2);background:var(--bg-1);display:flex;align-items:center;gap:var(--sp-4);flex-shrink:0}.page-title{font-family:var(--font-display);font-size:20px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;margin:0}.page-title-prefix{font-family:var(--font-mono);color:var(--ink-3);font-weight:400;margin-right:var(--sp-2)}.page-subtitle{font-size:12px;color:var(--ink-2);margin:0}.page-actions{margin-left:auto;display:flex;gap:var(--sp-2)}.page-body{flex:1;overflow-y:auto;padding:var(--sp-7)}.panel{background:var(--bg-2);border:1px solid var(--line-2);position:relative}.panel-header{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4);border-bottom:1px solid var(--line-2);background:linear-gradient(180deg,var(--bg-3),var(--bg-2));font-family:var(--font-mono);font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:var(--ink-1)}.panel-header:before{content:"";width:8px;height:8px;background:var(--accent-amber);border-radius:50%;box-shadow:var(--shadow-glow-amber)}.panel-body{padding:var(--sp-5)}.panel-grid{display:grid;gap:var(--sp-4)}.panel-grid.cols-3{grid-template-columns:repeat(3,1fr)}.panel-grid.cols-2,.panel-grid.cols-4{grid-template-columns:repeat(2,1fr)}@media(min-width:1600px){.panel-grid.cols-4{grid-template-columns:repeat(4,1fr)}}.led{display:inline-flex;align-items:center;gap:var(--sp-2);font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--ink-1)}.led-dot{width:10px;height:10px;border-radius:50%;background:var(--ink-3);position:relative;flex-shrink:0}.led-dot.amber{background:var(--accent-amber);box-shadow:var(--shadow-glow-amber);animation:led-pulse 1.6s ease-in-out infinite}.led-dot.green{background:var(--accent-green);box-shadow:var(--shadow-glow-green)}.led-dot.red{background:var(--accent-red);box-shadow:var(--shadow-glow-red);animation:led-pulse .8s ease-in-out infinite}.led-dot.gray{background:var(--ink-3)}@keyframes led-pulse{0%,to{opacity:1}50%{opacity:.4}}.status-card{background:var(--bg-2);border:1px solid var(--line-2);padding:var(--sp-5);position:relative;overflow:hidden}.status-card:before{content:"";position:absolute;top:0;left:0;right:0;height:2px;background:var(--line-2)}.status-card.amber:before{background:var(--accent-amber);box-shadow:var(--shadow-glow-amber)}.status-card.green:before{background:var(--accent-green);box-shadow:var(--shadow-glow-green)}.status-card.red:before{background:var(--accent-red);box-shadow:var(--shadow-glow-red)}.status-card-label{font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--ink-2);display:flex;align-items:center;gap:var(--sp-2)}.status-card-value{font-family:var(--font-mono);font-size:22px;font-weight:600;color:var(--ink-0);margin-top:var(--sp-3);word-break:break-all;line-height:1.2}.status-card-meta{font-family:var(--font-mono);font-size:10px;color:var(--ink-3);margin-top:var(--sp-2);text-transform:uppercase;letter-spacing:.06em}.btn{display:inline-flex;align-items:center;gap:var(--sp-2);padding:var(--sp-2) var(--sp-4);font-family:var(--font-display);font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;border:1px solid var(--line-2);background:var(--bg-2);color:var(--ink-1);border-radius:1px;transition:all .1s ease;position:relative;white-space:nowrap}.btn:hover{background:var(--bg-3);color:var(--ink-0);border-color:var(--line-3)}.btn:active{background:var(--bg-1)}.btn:disabled{opacity:.4;cursor:not-allowed}.btn-primary{background:var(--accent-amber);color:var(--bg-0);border-color:var(--accent-amber)}.btn-primary:hover{background:#ffc878;border-color:#ffc878;color:var(--bg-0)}.btn-danger{border-color:var(--accent-red-dim);color:var(--accent-red)}.btn-danger:hover{background:var(--accent-red-dim);color:var(--ink-0)}.btn-ghost{border-color:transparent;background:transparent}.btn svg{width:14px;height:14px;stroke-width:2}.btn-bar{position:absolute;left:-1px;top:8px;bottom:8px;width:3px;background:var(--accent-amber)}.field{display:flex;flex-direction:column;gap:var(--sp-2);margin-bottom:var(--sp-4)}.field-label{font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--ink-2);display:flex;align-items:center;gap:var(--sp-2)}.field-label .key{color:var(--ink-3)}.field-hint{font-family:var(--font-mono);font-size:10px;color:var(--ink-3);margin-top:2px}.input,.textarea,.select{background:var(--bg-1);border:1px solid var(--line-2);padding:var(--sp-3) var(--sp-3);font-family:var(--font-mono);font-size:13px;color:var(--ink-0);border-radius:1px;transition:border-color .12s;width:100%}.input:focus,.textarea:focus,.select:focus{outline:none;border-color:var(--accent-amber);box-shadow:0 0 0 1px var(--accent-amber-dim)}.input.mono{font-family:var(--font-mono);letter-spacing:.02em}.textarea{min-height:80px;resize:vertical;font-family:var(--font-mono)}.input-group{display:flex;gap:var(--sp-2);align-items:center}.input-group .input{flex:1}.table{width:100%;border-collapse:collapse;font-size:12px}.table thead th{text-align:left;padding:var(--sp-3) var(--sp-3);background:var(--bg-3);font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--ink-2);font-weight:500;border-bottom:1px solid var(--line-2)}.table tbody tr{border-bottom:1px solid var(--line-1);transition:background 80ms}.table tbody tr:hover{background:var(--bg-3)}.table tbody tr.active{background:#ffb4540d}.table td{padding:var(--sp-3) var(--sp-3);vertical-align:middle}.cell-id{font-family:var(--font-mono);font-size:12px;color:var(--ink-0);font-weight:500}.cell-mono{font-family:var(--font-mono);font-size:11px;color:var(--ink-1)}.cell-muted{color:var(--ink-2)}.cell-num{font-family:var(--font-mono);text-align:right}.toggle{position:relative;width:36px;height:18px;background:var(--bg-1);border:1px solid var(--line-3);border-radius:1px;cursor:pointer;transition:all .12s}.toggle:after{content:"";position:absolute;top:1px;left:1px;width:14px;height:14px;background:var(--ink-3);transition:all .12s}.toggle.on{background:var(--accent-green-dim);border-color:var(--accent-green)}.toggle.on:after{left:19px;background:var(--accent-green);box-shadow:0 0 6px #3fb95080}.provider-card{background:var(--bg-2);border:1px solid var(--line-2);padding:var(--sp-5);cursor:pointer;transition:all .16s;position:relative;display:flex;flex-direction:column;gap:var(--sp-3);min-height:160px}.provider-card:hover{border-color:var(--line-3);transform:translateY(-2px)}.provider-card.active{border-color:var(--accent-amber);background:var(--bg-3)}.provider-card.active:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;border:1px solid var(--accent-amber);pointer-events:none;box-shadow:var(--shadow-glow-amber)}.provider-card-head{display:flex;align-items:center;gap:var(--sp-3)}.provider-card-logo{width:36px;height:36px;background:var(--bg-1);border:1px solid var(--line-2);display:grid;place-items:center;font-family:var(--font-display);font-weight:700;font-size:14px;color:var(--accent-amber);position:relative}.provider-card.active .provider-card-logo{background:var(--accent-amber);color:var(--bg-0)}.provider-card-name{font-family:var(--font-display);font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:.04em}.provider-card-protocol{font-family:var(--font-mono);font-size:9px;text-transform:uppercase;letter-spacing:.1em;padding:1px 6px;border:1px solid var(--line-2);color:var(--ink-2);border-radius:1px;display:inline-block;align-self:flex-start}.provider-card-notes{font-size:11px;color:var(--ink-2);line-height:1.5;flex:1}.provider-card-models{font-family:var(--font-mono);font-size:10px;color:var(--ink-2);text-transform:uppercase;letter-spacing:.08em;padding-top:var(--sp-2);border-top:1px dashed var(--line-2)}.console{display:flex;flex-direction:column;height:100%;background:var(--bg-0)}.console-header{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-2) var(--sp-4);border-bottom:1px solid var(--line-2);background:var(--bg-1);font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--ink-2);flex-shrink:0}.console-body{flex:1;overflow-y:auto;padding:var(--sp-2) var(--sp-3);font-family:var(--font-mono);font-size:11px;line-height:1.5}.log-row{display:grid;grid-template-columns:60px 50px 90px 1fr;gap:var(--sp-3);padding:2px var(--sp-2);border-left:2px solid transparent;animation:log-slide .2s ease}.log-row:hover{background:var(--bg-2)}@keyframes log-slide{0%{opacity:0;transform:translate(-4px)}to{opacity:1;transform:translate(0)}}.log-ts{color:var(--ink-3)}.log-level{font-weight:600;text-align:center;border-radius:1px}.log-level.INFO{color:var(--accent-cyan)}.log-level.OK{color:var(--accent-green)}.log-level.WARN{color:var(--accent-amber)}.log-level.ERR{color:var(--accent-red)}.log-scope{color:var(--ink-2)}.log-msg{color:var(--ink-0);word-break:break-all}.log-row.ERR{border-left-color:var(--accent-red)}.log-row.WARN{border-left-color:var(--accent-amber)}.log-row.OK{border-left-color:var(--accent-green)}.drawer-mask{position:absolute;top:0;right:0;bottom:0;left:0;background:#00000080;z-index:100;animation:fade-in .2s ease}.drawer{position:absolute;top:0;right:0;bottom:0;width:520px;max-width:90%;background:var(--bg-1);border-left:1px solid var(--line-2);z-index:101;display:flex;flex-direction:column;animation:slide-in .22s ease;box-shadow:-8px 0 32px #00000080}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes slide-in{0%{transform:translate(100%)}to{transform:translate(0)}}.drawer-header{padding:var(--sp-4) var(--sp-5);border-bottom:1px solid var(--line-2);display:flex;align-items:center;gap:var(--sp-3);font-family:var(--font-display);text-transform:uppercase;letter-spacing:.06em;font-size:14px}.drawer-close{margin-left:auto;width:28px;height:28px;display:grid;place-items:center;border:1px solid var(--line-2);color:var(--ink-1)}.drawer-close:hover{background:var(--bg-2);color:var(--ink-0)}.drawer-body{flex:1;overflow-y:auto;padding:var(--sp-5)}.drawer-footer{padding:var(--sp-4) var(--sp-5);border-top:1px solid var(--line-2);display:flex;gap:var(--sp-2);justify-content:flex-end}.kbd{display:inline-block;padding:1px 6px;font-family:var(--font-mono);font-size:10px;background:var(--bg-1);border:1px solid var(--line-2);border-bottom-width:2px;color:var(--ink-1);border-radius:2px}.divider{height:1px;background:var(--line-2);margin:var(--sp-4) 0}.tag{display:inline-flex;align-items:center;font-family:var(--font-mono);font-size:9px;text-transform:uppercase;letter-spacing:.1em;padding:2px 6px;border:1px solid var(--line-2);color:var(--ink-2);border-radius:1px}.tag.amber{color:var(--accent-amber);border-color:var(--accent-amber-dim)}.tag.green{color:var(--accent-green);border-color:var(--accent-green-dim)}.tag.red{color:var(--accent-red);border-color:var(--accent-red-dim)}.tag.cyan{color:var(--accent-cyan);border-color:var(--accent-cyan-dim)}.empty{text-align:center;padding:var(--sp-10) var(--sp-5);color:var(--ink-2);font-family:var(--font-mono);font-size:12px}.empty-title{font-family:var(--font-display);font-size:16px;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-1);margin-bottom:var(--sp-2)}.copyable{cursor:pointer;position:relative}.copyable:hover:after{content:"点击复制";position:absolute;top:-22px;left:50%;transform:translate(-50%);font-size:9px;background:var(--bg-3);color:var(--ink-1);padding:2px 6px;border:1px solid var(--line-2);pointer-events:none;white-space:nowrap;font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.1em}.quick-switch{display:grid;grid-template-columns:1fr 1fr 1fr auto;gap:var(--sp-3);align-items:end}@media(max-width:900px){.app-shell{grid-template-columns:1fr;grid-template-areas:"main" "console"}.app-sidebar{display:none}.quick-switch{grid-template-columns:1fr}}.install-page{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px;background:var(--bg-0);background-image:radial-gradient(800px circle at 20% 0%,rgba(16,185,129,.06),transparent 50%),radial-gradient(600px circle at 80% 100%,rgba(99,102,241,.05),transparent 50%)}.install-card{width:100%;max-width:640px;background:var(--bg-1);border:1px solid var(--border-1, #1f2937);border-radius:16px;padding:32px;box-shadow:0 20px 60px #0006}.install-header{display:flex;align-items:center;gap:16px;margin-bottom:24px;padding-bottom:20px;border-bottom:1px solid var(--border-1, #1f2937)}.install-header h1{margin:0 0 4px;font-size:22px;font-weight:700;color:var(--ink-0, #f3f4f6)}.install-subtitle{margin:0;font-size:13px;color:var(--ink-3, #9ca3af)}.install-checks{display:flex;flex-direction:column;gap:10px;margin-bottom:20px}.check-row{display:flex;align-items:center;gap:12px;padding:10px 14px;background:var(--bg-2, #141a23);border:1px solid var(--border-1, #1f2937);border-radius:8px;font-size:13px}.check-ok{color:#10b981;flex-shrink:0}.check-warn{color:#f59e0b;flex-shrink:0}.check-label{font-weight:600;color:var(--ink-0, #f3f4f6);min-width:110px}.check-detail{font-family:var(--font-mono, monospace);font-size:12px;color:var(--ink-3, #9ca3af);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.install-error{display:flex;gap:14px;align-items:flex-start;padding:16px;background:#f59e0b14;border:1px solid rgba(245,158,11,.3);border-radius:10px;margin-bottom:20px}.install-error strong{color:#f59e0b;display:block;margin-bottom:4px}.install-error p{margin:4px 0;font-size:13px;color:var(--ink-2, #d1d5db)}.install-error a{color:#10b981}.install-error button{flex-shrink:0;align-self:center}.install-action{display:flex;flex-direction:column;gap:10px;align-items:center;margin-bottom:20px}.btn-large{padding:14px 28px;font-size:15px;font-weight:600;display:flex;align-items:center;gap:10px}.install-hint{margin:0;font-size:12px;color:var(--ink-3, #9ca3af);text-align:center}.install-hint code{background:var(--bg-2, #141a23);padding:2px 6px;border-radius:4px;font-size:11px}.install-running{display:flex;align-items:center;gap:10px;font-size:14px;color:var(--ink-1, #e5e7eb);font-weight:500}.install-done{display:flex;align-items:center;gap:14px;padding:16px;background:#10b98114;border:1px solid rgba(16,185,129,.3);border-radius:10px;margin-bottom:20px}.install-done strong{color:#10b981;display:block}.install-done p{margin:4px 0 0;font-size:13px;color:var(--ink-3, #9ca3af)}.install-logs{max-height:240px;overflow-y:auto;background:#000;border:1px solid var(--border-1, #1f2937);border-radius:8px;padding:12px;font-family:var(--font-mono, monospace);font-size:11px;line-height:1.5;margin-bottom:16px}.log-line{color:#d1d5db;white-space:pre-wrap;word-break:break-all}.log-err{color:#f87171;white-space:pre-wrap;word-break:break-all}.install-error-text{color:#f87171;font-size:12px;padding:8px 12px;background:#f8717114;border-radius:6px;margin-bottom:12px}.install-footer{display:flex;justify-content:space-between;align-items:center;font-size:11px;color:var(--ink-3, #9ca3af);padding-top:16px;border-top:1px solid var(--border-1, #1f2937)}.install-footer code{background:var(--bg-2, #141a23);padding:2px 6px;border-radius:4px;font-size:10px}.install-link{display:flex;align-items:center;gap:4px;color:var(--ink-3, #9ca3af);text-decoration:none}.install-link:hover{color:#10b981}.spin{animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}
|
package/dist/favicon.svg
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="32" height="32" fill="#0A0B0D"/>
|
|
3
|
+
<path d="M26.6677 23.7149H8.38057V20.6496H5.33301V8.38159H26.6677V23.7149ZM8.38057 20.6496H23.6201V11.4482H8.38057V20.6496ZM16.0011 16.0021L13.8461 18.1705L11.6913 16.0021L13.8461 13.8337L16.0011 16.0021ZM22.0963 16.0008L19.9414 18.1691L17.7865 16.0008L19.9414 13.8324L22.0963 16.0008Z" fill="#32F08C"/>
|
|
4
|
+
</svg>
|
package/dist/index.html
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>BHG-helper</title>
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
|
+
<link
|
|
11
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Space+Grotesk:wght@500;600;700&display=swap"
|
|
12
|
+
rel="stylesheet"
|
|
13
|
+
/>
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-BjvGHrGe.js"></script>
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CQrGCyBr.css">
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<div id="root"></div>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
package/index.html
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>BHG-helper</title>
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
|
+
<link
|
|
11
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Space+Grotesk:wght@500;600;700&display=swap"
|
|
12
|
+
rel="stylesheet"
|
|
13
|
+
/>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="root"></div>
|
|
17
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
package/nodemon.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bhg-helper",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "BHG-helper:AI 编程工具 DeepSeek 中转 & 可视化配置工具",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bhg-helper": "cli/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"cli/",
|
|
11
|
+
"api/",
|
|
12
|
+
"src/",
|
|
13
|
+
"dist/",
|
|
14
|
+
"scripts/",
|
|
15
|
+
"index.html",
|
|
16
|
+
"vite.config.ts",
|
|
17
|
+
"tsconfig.json",
|
|
18
|
+
"tailwind.config.js",
|
|
19
|
+
"postcss.config.js",
|
|
20
|
+
"nodemon.json"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"client:dev": "vite",
|
|
24
|
+
"build": "tsc -b && vite build",
|
|
25
|
+
"lint": "eslint .",
|
|
26
|
+
"preview": "vite preview",
|
|
27
|
+
"check": "tsc --noEmit",
|
|
28
|
+
"server:dev": "nodemon",
|
|
29
|
+
"dev": "concurrently \"npm run client:dev\" \"npm run server:dev\"",
|
|
30
|
+
"cli": "node cli/cli.js",
|
|
31
|
+
"server:raw": "tsx api/server.ts",
|
|
32
|
+
"start:raw": "concurrently \"vite\" \"tsx api/server.ts\""
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"deepseek",
|
|
36
|
+
"claude-code",
|
|
37
|
+
"ai",
|
|
38
|
+
"relay",
|
|
39
|
+
"proxy",
|
|
40
|
+
"opencode",
|
|
41
|
+
"gemini",
|
|
42
|
+
"cursor"
|
|
43
|
+
],
|
|
44
|
+
"author": "",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@inquirer/prompts": "^8.5.2",
|
|
48
|
+
"clsx": "^2.1.1",
|
|
49
|
+
"cors": "^2.8.5",
|
|
50
|
+
"dotenv": "^17.2.1",
|
|
51
|
+
"express": "^4.21.2",
|
|
52
|
+
"lucide-react": "^0.511.0",
|
|
53
|
+
"react": "^18.3.1",
|
|
54
|
+
"react-dom": "^18.3.1",
|
|
55
|
+
"react-router-dom": "^7.3.0",
|
|
56
|
+
"tailwind-merge": "^3.0.2",
|
|
57
|
+
"zustand": "^5.0.3"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@eslint/js": "^9.25.0",
|
|
61
|
+
"@types/cors": "^2.8.19",
|
|
62
|
+
"@types/express": "^4.17.21",
|
|
63
|
+
"@types/node": "^22.15.30",
|
|
64
|
+
"@types/react": "^18.3.12",
|
|
65
|
+
"@types/react-dom": "^18.3.1",
|
|
66
|
+
"@vitejs/plugin-react": "^4.4.1",
|
|
67
|
+
"autoprefixer": "^10.4.21",
|
|
68
|
+
"concurrently": "^9.2.0",
|
|
69
|
+
"eslint": "^9.25.0",
|
|
70
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
71
|
+
"eslint-plugin-react-refresh": "^0.4.19",
|
|
72
|
+
"globals": "^16.0.0",
|
|
73
|
+
"nodemon": "^3.1.10",
|
|
74
|
+
"postcss": "^8.5.3",
|
|
75
|
+
"tailwindcss": "^3.4.17",
|
|
76
|
+
"tsx": "^4.20.3",
|
|
77
|
+
"typescript": "~5.8.3",
|
|
78
|
+
"typescript-eslint": "^8.30.1",
|
|
79
|
+
"vite": "^6.3.5",
|
|
80
|
+
"vite-tsconfig-paths": "^5.1.4"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
cd /d "%~dp0.."
|
|
3
|
+
|
|
4
|
+
echo ====================================================
|
|
5
|
+
echo BHG-helper - Install Dependencies
|
|
6
|
+
echo ====================================================
|
|
7
|
+
echo.
|
|
8
|
+
echo (you can skip this - start.bat auto-installs)
|
|
9
|
+
echo.
|
|
10
|
+
|
|
11
|
+
node --version >nul 2>&1
|
|
12
|
+
if %errorlevel% neq 0 (
|
|
13
|
+
echo [X] Node.js not found
|
|
14
|
+
echo Download: https://nodejs.org/ (v18+)
|
|
15
|
+
pause
|
|
16
|
+
exit /b 1
|
|
17
|
+
)
|
|
18
|
+
for /f "tokens=1" %%v in ('node -v') do echo [OK] Node %%v
|
|
19
|
+
|
|
20
|
+
if not exist node_modules (
|
|
21
|
+
echo [..] Installing dependencies (~1-2 min)...
|
|
22
|
+
call npm install --no-audit --no-fund --loglevel=error
|
|
23
|
+
if errorlevel 1 ( echo [X] Failed & pause & exit /b 1 )
|
|
24
|
+
echo [OK] Install complete
|
|
25
|
+
) else (
|
|
26
|
+
echo [OK] node_modules already exists
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
echo.
|
|
30
|
+
echo Next: run scripts\start.bat
|
|
31
|
+
echo.
|
|
32
|
+
pause
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
setlocal enabledelayedexpansion
|
|
3
|
+
cd /d "%~dp0.."
|
|
4
|
+
|
|
5
|
+
echo.
|
|
6
|
+
echo ====================================================
|
|
7
|
+
echo BHG-helper — Setup and Launch
|
|
8
|
+
echo ====================================================
|
|
9
|
+
echo.
|
|
10
|
+
|
|
11
|
+
REM -- 1. Check Node.js --
|
|
12
|
+
node --version >nul 2>&1
|
|
13
|
+
if %errorlevel% neq 0 (
|
|
14
|
+
echo [X] Node.js not found
|
|
15
|
+
echo Download https://nodejs.org/ (v18+)
|
|
16
|
+
pause
|
|
17
|
+
exit /b 1
|
|
18
|
+
)
|
|
19
|
+
for /f "tokens=1" %%v in ('node -v') do echo [OK] Node %%v
|
|
20
|
+
|
|
21
|
+
REM -- 2. Install dependencies if needed --
|
|
22
|
+
if not exist node_modules (
|
|
23
|
+
echo [..] Installing dependencies (~1-2 min)...
|
|
24
|
+
call npm install --no-audit --no-fund --loglevel=error
|
|
25
|
+
if errorlevel 1 (
|
|
26
|
+
echo [X] Install failed
|
|
27
|
+
pause
|
|
28
|
+
exit /b 1
|
|
29
|
+
)
|
|
30
|
+
echo [OK] Dependencies installed
|
|
31
|
+
) else (
|
|
32
|
+
echo [OK] Dependencies ready
|
|
33
|
+
)
|
|
34
|
+
echo.
|
|
35
|
+
|
|
36
|
+
REM -- 3. Start background services --
|
|
37
|
+
echo [..] Starting services...
|
|
38
|
+
start "BHG-API" cmd /c "npm run dev"
|
|
39
|
+
echo [OK] Services starting in background...
|
|
40
|
+
|
|
41
|
+
REM -- 4. Wait for services to be ready, then launch CLI menu --
|
|
42
|
+
echo [..] Waiting for UI (http://localhost:5173)...
|
|
43
|
+
echo.
|
|
44
|
+
call npm run cli
|
|
45
|
+
|
|
46
|
+
endlocal
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# BHG-helper launcher (PowerShell)
|
|
2
|
+
# Usage: .\scripts\start.ps1
|
|
3
|
+
|
|
4
|
+
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
5
|
+
$ProjectDir = Split-Path -Parent $ScriptDir
|
|
6
|
+
Set-Location $ProjectDir
|
|
7
|
+
|
|
8
|
+
Write-Host ""
|
|
9
|
+
Write-Host "===================================================="
|
|
10
|
+
Write-Host " BHG-helper — Setup and Launch"
|
|
11
|
+
Write-Host "===================================================="
|
|
12
|
+
Write-Host ""
|
|
13
|
+
|
|
14
|
+
# 1. Check Node.js
|
|
15
|
+
$nodeVersion = try { node -v 2>$null } catch { $null }
|
|
16
|
+
if (-not $nodeVersion) {
|
|
17
|
+
Write-Host "[X] Node.js not found" -ForegroundColor Red
|
|
18
|
+
Write-Host " Download https://nodejs.org/ (v18+)"
|
|
19
|
+
Read-Host "Press Enter to exit"
|
|
20
|
+
exit 1
|
|
21
|
+
}
|
|
22
|
+
Write-Host "[OK] Node $nodeVersion"
|
|
23
|
+
|
|
24
|
+
# 2. Install deps
|
|
25
|
+
if (-not (Test-Path node_modules)) {
|
|
26
|
+
Write-Host "[..] Installing dependencies (~1-2 min)..."
|
|
27
|
+
npm install --no-audit --no-fund --loglevel=error
|
|
28
|
+
if ($LASTEXITCODE -ne 0) {
|
|
29
|
+
Write-Host "[X] Install failed" -ForegroundColor Red
|
|
30
|
+
Read-Host "Press Enter to exit"
|
|
31
|
+
exit 1
|
|
32
|
+
}
|
|
33
|
+
Write-Host "[OK] Dependencies installed"
|
|
34
|
+
} else {
|
|
35
|
+
Write-Host "[OK] Dependencies ready"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Write-Host ""
|
|
39
|
+
Write-Host "[..] Starting services..."
|
|
40
|
+
Start-Process cmd -ArgumentList "/c npm run dev" -WindowStyle Minimized
|
|
41
|
+
Write-Host "[OK] Services starting in background..."
|
|
42
|
+
Write-Host ""
|
|
43
|
+
|
|
44
|
+
# 3. Launch CLI
|
|
45
|
+
npm run cli
|
package/src/App.tsx
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
|
|
3
|
+
import Layout from './components/Layout'
|
|
4
|
+
import Dashboard from './pages/Dashboard'
|
|
5
|
+
import ConsolePage from './pages/ConsolePage'
|
|
6
|
+
import Relay from './pages/Relay'
|
|
7
|
+
import Install from './pages/Install'
|
|
8
|
+
import ErrorBoundary from './components/ErrorBoundary'
|
|
9
|
+
import { startLogStream, useStore } from './lib/store'
|
|
10
|
+
import { api } from './lib/api'
|
|
11
|
+
|
|
12
|
+
export default function App() {
|
|
13
|
+
const loadAll = useStore((s) => s.loadAll)
|
|
14
|
+
const [installState, setInstallState] = useState<'checking' | 'needs' | 'ready'>('checking')
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
void loadAll()
|
|
18
|
+
const stop = startLogStream()
|
|
19
|
+
let timer: number | undefined
|
|
20
|
+
function check() {
|
|
21
|
+
api.getInstallStatus()
|
|
22
|
+
.then((s) => {
|
|
23
|
+
// 安装进行中 → 保持 needs 状态,让 Install 页面自己管理
|
|
24
|
+
if (s.isRunning) setInstallState('needs')
|
|
25
|
+
// 需要安装且没在跑 → needs
|
|
26
|
+
else if (s.needsInstall) setInstallState('needs')
|
|
27
|
+
// 不需要安装 → 进入主界面
|
|
28
|
+
else setInstallState('ready')
|
|
29
|
+
})
|
|
30
|
+
.catch(() => setInstallState('ready'))
|
|
31
|
+
}
|
|
32
|
+
check()
|
|
33
|
+
timer = window.setInterval(check, 3000)
|
|
34
|
+
return () => {
|
|
35
|
+
stop()
|
|
36
|
+
if (timer) window.clearInterval(timer)
|
|
37
|
+
}
|
|
38
|
+
}, [loadAll])
|
|
39
|
+
|
|
40
|
+
// 安装模式:全屏展示,不进 Layout
|
|
41
|
+
if (installState === 'needs') {
|
|
42
|
+
return (
|
|
43
|
+
<ErrorBoundary>
|
|
44
|
+
<Install />
|
|
45
|
+
</ErrorBoundary>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
if (installState === 'checking') {
|
|
49
|
+
return (
|
|
50
|
+
<div style={{
|
|
51
|
+
minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
52
|
+
background: 'var(--bg-0, #0b0e14)', color: 'var(--ink-3, #9ca3af)', fontSize: 13
|
|
53
|
+
}}>
|
|
54
|
+
启动中...
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<ErrorBoundary>
|
|
61
|
+
<Router>
|
|
62
|
+
<Routes>
|
|
63
|
+
<Route element={<Layout />}>
|
|
64
|
+
<Route index element={<Dashboard />} />
|
|
65
|
+
<Route path="relay" element={<Relay />} />
|
|
66
|
+
<Route path="console" element={<ConsolePage />} />
|
|
67
|
+
<Route path="*" element={<Navigate to="/" replace />} />
|
|
68
|
+
</Route>
|
|
69
|
+
</Routes>
|
|
70
|
+
</Router>
|
|
71
|
+
</ErrorBoundary>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
import { useStore } from '../lib/store'
|
|
3
|
+
|
|
4
|
+
export default function ConsolePanel() {
|
|
5
|
+
const logs = useStore((s) => s.logs)
|
|
6
|
+
const bodyRef = useRef<HTMLDivElement | null>(null)
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (bodyRef.current) {
|
|
10
|
+
bodyRef.current.scrollTop = bodyRef.current.scrollHeight
|
|
11
|
+
}
|
|
12
|
+
}, [logs.length])
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="console">
|
|
16
|
+
<div className="console-header">
|
|
17
|
+
<span className="led">
|
|
18
|
+
<span className="led-dot green" /> LIVE
|
|
19
|
+
</span>
|
|
20
|
+
<span>SIGNAL · EVENT STREAM</span>
|
|
21
|
+
<span style={{ marginLeft: 'auto', color: 'var(--ink-3)' }}>
|
|
22
|
+
{logs.length} 条 · 按时间倒序
|
|
23
|
+
</span>
|
|
24
|
+
</div>
|
|
25
|
+
<div className="console-body" ref={bodyRef}>
|
|
26
|
+
{logs.length === 0 ? (
|
|
27
|
+
<div className="empty">
|
|
28
|
+
<div className="empty-title">— 等待信号 —</div>
|
|
29
|
+
<div>所有 API 调用、文件读写、备份事件会实时显示在此处。</div>
|
|
30
|
+
</div>
|
|
31
|
+
) : (
|
|
32
|
+
logs.map((l) => (
|
|
33
|
+
<div key={l.id} className={'log-row ' + l.level}>
|
|
34
|
+
<span className="log-ts">{l.ts.split(' ')[1]}</span>
|
|
35
|
+
<span className={'log-level ' + l.level}>{l.level}</span>
|
|
36
|
+
<span className="log-scope">{l.scope}</span>
|
|
37
|
+
<span className="log-msg">{l.message}</span>
|
|
38
|
+
</div>
|
|
39
|
+
))
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Component, type ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
interface State { error: Error | null }
|
|
4
|
+
|
|
5
|
+
export default class ErrorBoundary extends Component<{ children: ReactNode }, State> {
|
|
6
|
+
state: State = { error: null }
|
|
7
|
+
|
|
8
|
+
static getDerivedStateFromError(error: Error) {
|
|
9
|
+
return { error }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
override componentDidCatch(error: Error, info: { componentStack?: string }) {
|
|
13
|
+
// eslint-disable-next-line no-console
|
|
14
|
+
console.error('[ErrorBoundary] caught:', error, info)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override render() {
|
|
18
|
+
if (this.state.error) {
|
|
19
|
+
return (
|
|
20
|
+
<div style={{
|
|
21
|
+
minHeight: '100vh', padding: 32, background: '#0b0e14', color: '#e5e7eb',
|
|
22
|
+
fontFamily: 'ui-monospace, monospace', fontSize: 13, lineHeight: 1.6
|
|
23
|
+
}}>
|
|
24
|
+
<div style={{ maxWidth: 800, margin: '0 auto' }}>
|
|
25
|
+
<h1 style={{ color: '#f87171', fontSize: 18, marginBottom: 12 }}>
|
|
26
|
+
⚠ 页面渲染失败
|
|
27
|
+
</h1>
|
|
28
|
+
<div style={{ padding: 16, background: 'rgba(248,113,113,0.08)', border: '1px solid rgba(248,113,113,0.3)', borderRadius: 8, marginBottom: 16 }}>
|
|
29
|
+
<div style={{ color: '#f87171', fontWeight: 700, marginBottom: 8 }}>
|
|
30
|
+
{this.state.error.name}: {this.state.error.message}
|
|
31
|
+
</div>
|
|
32
|
+
<pre style={{
|
|
33
|
+
whiteSpace: 'pre-wrap', wordBreak: 'break-all', margin: 0, color: '#d1d5db',
|
|
34
|
+
maxHeight: 400, overflow: 'auto', fontSize: 11
|
|
35
|
+
}}>
|
|
36
|
+
{this.state.error.stack}
|
|
37
|
+
</pre>
|
|
38
|
+
</div>
|
|
39
|
+
<button
|
|
40
|
+
onClick={() => window.location.reload()}
|
|
41
|
+
style={{
|
|
42
|
+
background: '#10b981', color: '#000', border: 'none', padding: '8px 16px',
|
|
43
|
+
borderRadius: 4, fontWeight: 600, cursor: 'pointer'
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
硬刷页面
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
return this.props.children
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Outlet } from 'react-router-dom'
|
|
2
|
+
import Sidebar from './Sidebar'
|
|
3
|
+
import ConsolePanel from './ConsolePanel'
|
|
4
|
+
|
|
5
|
+
export default function Layout() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="app-shell">
|
|
8
|
+
<Sidebar />
|
|
9
|
+
<main className="app-main">
|
|
10
|
+
<Outlet />
|
|
11
|
+
</main>
|
|
12
|
+
<section className="app-console">
|
|
13
|
+
<ConsolePanel />
|
|
14
|
+
</section>
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
}
|