erlangshen 0.1.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.
Files changed (93) hide show
  1. package/.claude/agents/equity-agent.md +26 -0
  2. package/.claude/agents/macro-agent.md +25 -0
  3. package/.claude/commands/analyze.md +40 -0
  4. package/.claude/commands/macro.md +29 -0
  5. package/.claude/settings.json +12 -0
  6. package/CODEX_GOAL.md +46 -0
  7. package/README.md +206 -0
  8. package/bin/cli.js +67 -0
  9. package/bin/erlangshen +2 -0
  10. package/bin/xiaoergod +2 -0
  11. package/frontend/index.html +700 -0
  12. package/knowledge/crypto_guide.md +147 -0
  13. package/knowledge/economic_indicators.md +125 -0
  14. package/knowledge/financial_glossary.md +148 -0
  15. package/knowledge/first_principles.md +50 -0
  16. package/knowledge/first_principles_deep.md +115 -0
  17. package/knowledge/global_markets.md +173 -0
  18. package/knowledge/insights.md +141 -0
  19. package/knowledge/market_basics.md +116 -0
  20. package/knowledge/memos/session_20260513_003616.json +6 -0
  21. package/knowledge/memos/session_20260513_003822.json +6 -0
  22. package/knowledge/risk_management.md +151 -0
  23. package/knowledge/team_context.md +42 -0
  24. package/knowledge/trading_strategies.md +114 -0
  25. package/package.json +42 -0
  26. package/requirements.txt +14 -0
  27. package/scripts/postinstall.js +188 -0
  28. package/scripts/preuninstall.js +22 -0
  29. package/src/__init__.py +4 -0
  30. package/src/__pycache__/__init__.cpython-313.pyc +0 -0
  31. package/src/agents/__init__.py +3 -0
  32. package/src/agents/base.py +103 -0
  33. package/src/agents/base_agent.py +86 -0
  34. package/src/agents/equity.py +136 -0
  35. package/src/agents/equity_agent.py +91 -0
  36. package/src/agents/erlang.py +165 -0
  37. package/src/agents/macro.py +137 -0
  38. package/src/agents/macro_agent.py +81 -0
  39. package/src/agents/multi_asset.py +147 -0
  40. package/src/agents/multi_asset_agent.py +87 -0
  41. package/src/api/__init__.py +1 -0
  42. package/src/api/__pycache__/__init__.cpython-313.pyc +0 -0
  43. package/src/api/__pycache__/server.cpython-313.pyc +0 -0
  44. package/src/api/cli.py +435 -0
  45. package/src/api/cli_enhanced.py +537 -0
  46. package/src/api/server.py +266 -0
  47. package/src/brain.py +200 -0
  48. package/src/cli.py +153 -0
  49. package/src/commands/__init__.py +3 -0
  50. package/src/commands/analyze.py +131 -0
  51. package/src/commands/macro.py +100 -0
  52. package/src/commands/memo.py +216 -0
  53. package/src/commands/portfolio.py +154 -0
  54. package/src/commands/report.py +228 -0
  55. package/src/commands/risk.py +183 -0
  56. package/src/commands/search.py +183 -0
  57. package/src/commands/stock.py +124 -0
  58. package/src/config.py +327 -0
  59. package/src/core/__init__.py +1 -0
  60. package/src/core/brain.py +645 -0
  61. package/src/core/cerebellum.py +175 -0
  62. package/src/core/investment_universe.py +423 -0
  63. package/src/core/knowledge.py +207 -0
  64. package/src/core/memory.py +115 -0
  65. package/src/hooks/__init__.py +3 -0
  66. package/src/hooks/session_end.py +57 -0
  67. package/src/hooks/session_start.py +75 -0
  68. package/src/knowledge/__init__.py +1 -0
  69. package/src/mcp/__init__.py +3 -0
  70. package/src/mcp/feishu.py +331 -0
  71. package/src/mcp/fund_tools.py +323 -0
  72. package/src/mcp/macro.py +452 -0
  73. package/src/mcp/market.py +331 -0
  74. package/src/mcp/registry.py +168 -0
  75. package/src/network/__init__.py +15 -0
  76. package/src/network/detector.py +125 -0
  77. package/src/network/proxy.py +199 -0
  78. package/src/network/router.py +103 -0
  79. package/src/prompts/__init__.py +1 -0
  80. package/src/prompts/analysis_framework.md +164 -0
  81. package/src/prompts/persona.md +65 -0
  82. package/src/prompts/report_template.md +144 -0
  83. package/src/skills/__init__.py +3 -0
  84. package/src/skills/framework.py +105 -0
  85. package/src/skills/templates.py +342 -0
  86. package/src/tools/__init__.py +1 -0
  87. package/src/tools/file_tools.py +209 -0
  88. package/src/tools/macro_tools.py +152 -0
  89. package/src/tools/market_tools.py +1172 -0
  90. package/src/tools/registry.py +398 -0
  91. package/src/tools/search_tools.py +777 -0
  92. package/tests/__init__.py +1 -0
  93. package/tests/test_erlangshen.py +140 -0
@@ -0,0 +1,700 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>二郎神 · 天眼AI</title>
7
+ <style>
8
+ :root {
9
+ --bg-primary: #0a0a1a;
10
+ --bg-secondary: #12122a;
11
+ --bg-card: rgba(255,255,255,0.04);
12
+ --accent-cyan: #00f5ff;
13
+ --accent-purple: #bf5fff;
14
+ --accent-gold: #ffd700;
15
+ --text-primary: #ffffff;
16
+ --text-secondary: #8890b5;
17
+ --border-glow: rgba(0,245,255,0.15);
18
+ --glass-bg: rgba(255,255,255,0.03);
19
+ }
20
+
21
+ * { margin: 0; padding: 0; box-sizing: border-box; }
22
+
23
+ body {
24
+ font-family: 'SF Pro Display', 'PingFang SC', -apple-system, sans-serif;
25
+ background: var(--bg-primary);
26
+ color: var(--text-primary);
27
+ overflow: hidden;
28
+ height: 100vh;
29
+ width: 100vw;
30
+ }
31
+
32
+ /* ── 全息背景 ── */
33
+ #holo-bg {
34
+ position: fixed; inset: 0; z-index: 0;
35
+ background:
36
+ radial-gradient(ellipse 80% 60% at 50% -10%, rgba(0,245,255,0.08) 0%, transparent 60%),
37
+ radial-gradient(ellipse 60% 40% at 80% 80%, rgba(191,95,255,0.06) 0%, transparent 50%),
38
+ radial-gradient(ellipse 50% 50% at 10% 70%, rgba(255,215,0,0.03) 0%, transparent 50%),
39
+ var(--bg-primary);
40
+ animation: bgPulse 8s ease-in-out infinite alternate;
41
+ }
42
+
43
+ @keyframes bgPulse {
44
+ 0% { opacity: 1; }
45
+ 50% { opacity: 0.85; }
46
+ 100% { opacity: 1; }
47
+ }
48
+
49
+ /* 背景粒子 */
50
+ #particles-canvas {
51
+ position: fixed; inset: 0; z-index: 1;
52
+ pointer-events: none;
53
+ }
54
+
55
+ /* ── 主布局 ── */
56
+ #app {
57
+ position: relative; z-index: 2;
58
+ display: grid;
59
+ grid-template-rows: auto 1fr auto;
60
+ height: 100vh;
61
+ padding: 0 24px;
62
+ gap: 16px;
63
+ }
64
+
65
+ /* ── Header ── */
66
+ #header {
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ padding: 20px 0 0;
71
+ }
72
+
73
+ .logo-container {
74
+ display: flex; flex-direction: column; align-items: center; gap: 8px;
75
+ }
76
+
77
+ .logo-ring {
78
+ width: 72px; height: 72px;
79
+ border-radius: 50%;
80
+ background: conic-gradient(from 0deg, var(--accent-cyan), var(--accent-purple), var(--accent-gold), var(--accent-cyan));
81
+ padding: 2px;
82
+ animation: logoSpin 12s linear infinite;
83
+ box-shadow:
84
+ 0 0 20px rgba(0,245,255,0.3),
85
+ 0 0 60px rgba(0,245,255,0.1);
86
+ }
87
+
88
+ @keyframes logoSpin {
89
+ to { transform: rotate(360deg); }
90
+ }
91
+
92
+ .logo-inner {
93
+ width: 100%; height: 100%;
94
+ border-radius: 50%;
95
+ background: var(--bg-primary);
96
+ display: flex; align-items: center; justify-content: center;
97
+ font-size: 28px;
98
+ }
99
+
100
+ .brand-name {
101
+ font-size: 22px; font-weight: 700;
102
+ background: linear-gradient(90deg, var(--accent-cyan), var(--accent-purple));
103
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
104
+ letter-spacing: 4px;
105
+ }
106
+
107
+ .brand-sub {
108
+ font-size: 11px; color: var(--text-secondary);
109
+ letter-spacing: 6px; text-transform: uppercase;
110
+ }
111
+
112
+ /* ── 神经网络可视化区 ── */
113
+ #neural-zone {
114
+ position: relative;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ min-height: 200px;
119
+ }
120
+
121
+ #neural-canvas {
122
+ position: absolute; inset: 0;
123
+ width: 100%; height: 100%;
124
+ opacity: 0.6;
125
+ }
126
+
127
+ /* 能力卡片 */
128
+ .ability-cards {
129
+ display: flex; gap: 20px;
130
+ position: relative; z-index: 1;
131
+ }
132
+
133
+ .ability-card {
134
+ width: 110px; height: 110px;
135
+ background: var(--glass-bg);
136
+ backdrop-filter: blur(16px);
137
+ border: 1px solid var(--border-glow);
138
+ border-radius: 24px;
139
+ display: flex; flex-direction: column;
140
+ align-items: center; justify-content: center; gap: 10px;
141
+ cursor: pointer;
142
+ transition: all 0.3s cubic-bezier(0.34,1.56,0.64,1);
143
+ position: relative; overflow: hidden;
144
+ }
145
+
146
+ .ability-card::before {
147
+ content: '';
148
+ position: absolute; inset: 0;
149
+ background: radial-gradient(circle at 50% 0%, rgba(255,255,255,0.08) 0%, transparent 60%);
150
+ opacity: 0;
151
+ transition: opacity 0.3s;
152
+ }
153
+
154
+ .ability-card:hover {
155
+ transform: translateY(-8px) scale(1.05);
156
+ border-color: var(--accent-cyan);
157
+ box-shadow: 0 0 30px rgba(0,245,255,0.2), 0 20px 40px rgba(0,0,0,0.4);
158
+ }
159
+
160
+ .ability-card:hover::before { opacity: 1; }
161
+
162
+ .ability-icon { font-size: 32px; }
163
+ .ability-label { font-size: 13px; font-weight: 600; }
164
+ .ability-sub { font-size: 10px; color: var(--text-secondary); }
165
+
166
+ /* ── 对话区 ── */
167
+ #chat-zone {
168
+ display: flex;
169
+ flex-direction: column;
170
+ gap: 12px;
171
+ overflow-y: auto;
172
+ max-height: calc(100vh - 380px);
173
+ padding: 8px 0;
174
+ scrollbar-width: thin;
175
+ scrollbar-color: rgba(0,245,255,0.2) transparent;
176
+ }
177
+
178
+ #chat-zone::-webkit-scrollbar { width: 4px; }
179
+ #chat-zone::-webkit-scrollbar-track { background: transparent; }
180
+ #chat-zone::-webkit-scrollbar-thumb { background: rgba(0,245,255,0.2); border-radius: 2px; }
181
+
182
+ .msg {
183
+ display: flex; gap: 12px; align-items: flex-start;
184
+ animation: msgIn 0.4s cubic-bezier(0.34,1.56,0.64,1);
185
+ }
186
+
187
+ @keyframes msgIn {
188
+ from { opacity: 0; transform: translateY(16px); }
189
+ to { opacity: 1; transform: translateY(0); }
190
+ }
191
+
192
+ .msg-avatar {
193
+ width: 36px; height: 36px; border-radius: 50%;
194
+ background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple));
195
+ display: flex; align-items: center; justify-content: center;
196
+ font-size: 16px; flex-shrink: 0;
197
+ }
198
+
199
+ .msg-ai .msg-avatar {
200
+ background: linear-gradient(135deg, var(--accent-purple), var(--accent-gold));
201
+ }
202
+
203
+ .msg-bubble {
204
+ background: var(--glass-bg);
205
+ backdrop-filter: blur(12px);
206
+ border: 1px solid rgba(255,255,255,0.06);
207
+ border-radius: 18px 18px 18px 4px;
208
+ padding: 14px 18px;
209
+ max-width: 75%;
210
+ font-size: 14px; line-height: 1.7;
211
+ white-space: pre-wrap;
212
+ word-break: break-word;
213
+ }
214
+
215
+ .msg-ai .msg-bubble {
216
+ border-radius: 18px 18px 4px 18px;
217
+ border-color: rgba(191,95,255,0.12);
218
+ }
219
+
220
+ /* thinking indicator */
221
+ .thinking-dots {
222
+ display: flex; gap: 4px; padding: 8px 0;
223
+ }
224
+ .thinking-dots span {
225
+ width: 6px; height: 6px; border-radius: 50%;
226
+ background: var(--accent-cyan);
227
+ animation: dotPulse 1.4s ease-in-out infinite;
228
+ }
229
+ .thinking-dots span:nth-child(2) { animation-delay: 0.2s; }
230
+ .thinking-dots span:nth-child(3) { animation-delay: 0.4s; }
231
+
232
+ @keyframes dotPulse {
233
+ 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
234
+ 40% { transform: scale(1); opacity: 1; }
235
+ }
236
+
237
+ /* ── 输入区 ── */
238
+ #input-zone {
239
+ padding: 0 0 20px;
240
+ }
241
+
242
+ .input-wrapper {
243
+ position: relative;
244
+ display: flex;
245
+ align-items: flex-end;
246
+ gap: 12px;
247
+ }
248
+
249
+ .input-glass {
250
+ flex: 1;
251
+ background: var(--glass-bg);
252
+ backdrop-filter: blur(20px);
253
+ border: 1px solid var(--border-glow);
254
+ border-radius: 24px;
255
+ padding: 4px;
256
+ transition: border-color 0.3s, box-shadow 0.3s;
257
+ }
258
+
259
+ .input-glass:focus-within {
260
+ border-color: rgba(0,245,255,0.4);
261
+ box-shadow: 0 0 30px rgba(0,245,255,0.1);
262
+ }
263
+
264
+ #user-input {
265
+ width: 100%;
266
+ background: transparent;
267
+ border: none; outline: none;
268
+ color: var(--text-primary);
269
+ font-size: 15px;
270
+ line-height: 1.6;
271
+ padding: 14px 20px;
272
+ resize: none;
273
+ min-height: 52px; max-height: 160px;
274
+ font-family: inherit;
275
+ }
276
+
277
+ #user-input::placeholder { color: var(--text-secondary); }
278
+
279
+ .send-btn {
280
+ width: 48px; height: 48px;
281
+ border-radius: 50%;
282
+ background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple));
283
+ border: none; cursor: pointer;
284
+ display: flex; align-items: center; justify-content: center;
285
+ font-size: 18px;
286
+ transition: all 0.3s;
287
+ flex-shrink: 0;
288
+ box-shadow: 0 0 20px rgba(0,245,255,0.3);
289
+ }
290
+
291
+ .send-btn:hover {
292
+ transform: scale(1.1);
293
+ box-shadow: 0 0 35px rgba(0,245,255,0.5);
294
+ }
295
+
296
+ .send-btn:active { transform: scale(0.95); }
297
+
298
+ .send-btn:disabled {
299
+ opacity: 0.4;
300
+ cursor: not-allowed;
301
+ transform: none;
302
+ }
303
+
304
+ /* ── 脉冲光效 ── */
305
+ .pulse-ring {
306
+ position: absolute;
307
+ border-radius: 50%;
308
+ border: 2px solid var(--accent-cyan);
309
+ animation: pulseOut 1s ease-out forwards;
310
+ pointer-events: none;
311
+ }
312
+
313
+ @keyframes pulseOut {
314
+ 0% { width: 10px; height: 10px; opacity: 1; }
315
+ 100% { width: 120px; height: 120px; opacity: 0; }
316
+ }
317
+
318
+ /* ── 模型选择器 ── */
319
+ #model-badge {
320
+ position: fixed; top: 20px; right: 24px;
321
+ background: var(--glass-bg);
322
+ backdrop-filter: blur(12px);
323
+ border: 1px solid var(--border-glow);
324
+ border-radius: 20px;
325
+ padding: 6px 14px;
326
+ font-size: 11px;
327
+ color: var(--text-secondary);
328
+ z-index: 10;
329
+ display: flex; align-items: center; gap: 6px;
330
+ }
331
+
332
+ #model-badge .dot {
333
+ width: 6px; height: 6px; border-radius: 50%;
334
+ background: var(--accent-cyan);
335
+ animation: dotPulse 2s ease-in-out infinite;
336
+ }
337
+
338
+ /* ── 响应式 ── */
339
+ @media (max-width: 640px) {
340
+ .ability-cards { gap: 12px; flex-wrap: wrap; justify-content: center; }
341
+ .ability-card { width: 90px; height: 90px; }
342
+ .msg-bubble { max-width: 88%; }
343
+ .brand-name { font-size: 18px; }
344
+ }
345
+ </style>
346
+ </head>
347
+ <body>
348
+
349
+ <!-- 全息背景 -->
350
+ <div id="holo-bg"></div>
351
+ <canvas id="particles-canvas"></canvas>
352
+
353
+ <!-- 模型徽章 -->
354
+ <div id="model-badge">
355
+ <span class="dot"></span>
356
+ <span id="model-name">DeepSeek · 在线</span>
357
+ </div>
358
+
359
+ <!-- 主应用 -->
360
+ <div id="app">
361
+
362
+ <!-- Header -->
363
+ <div id="header">
364
+ <div class="logo-container">
365
+ <div class="logo-ring">
366
+ <div class="logo-inner">👁️</div>
367
+ </div>
368
+ <div class="brand-name">二郎神 · 天眼</div>
369
+ <div class="brand-sub">ErLang Shen · AI Brain</div>
370
+ </div>
371
+ </div>
372
+
373
+ <!-- 神经网络可视化 -->
374
+ <div id="neural-zone">
375
+ <canvas id="neural-canvas"></canvas>
376
+ <div class="ability-cards">
377
+ <div class="ability-card" data-task="感知">
378
+ <span class="ability-icon">👁️</span>
379
+ <span class="ability-label">感知</span>
380
+ <span class="ability-sub">SENSE</span>
381
+ </div>
382
+ <div class="ability-card" data-task="认知">
383
+ <span class="ability-icon">🧠</span>
384
+ <span class="ability-label">认知</span>
385
+ <span class="ability-sub">COGNIZE</span>
386
+ </div>
387
+ <div class="ability-card" data-task="决策">
388
+ <span class="ability-icon">⚡</span>
389
+ <span class="ability-label">决策</span>
390
+ <span class="ability-sub">DECIDE</span>
391
+ </div>
392
+ <div class="ability-card" data-task="记忆">
393
+ <span class="ability-icon">💾</span>
394
+ <span class="ability-label">记忆</span>
395
+ <span class="ability-sub">MEMORY</span>
396
+ </div>
397
+ </div>
398
+ </div>
399
+
400
+ <!-- 对话区 -->
401
+ <div id="chat-zone">
402
+ <div class="msg msg-ai">
403
+ <div class="msg-avatar">👁️</div>
404
+ <div class="msg-bubble">天眼已开,二郎神在此。请输入您的问题,我将启动深度思考与推理。</div>
405
+ </div>
406
+ </div>
407
+
408
+ <!-- 输入区 -->
409
+ <div id="input-zone">
410
+ <div class="input-wrapper">
411
+ <div class="input-glass">
412
+ <textarea id="user-input" placeholder="输入您的投资问题或指令..." rows="1"></textarea>
413
+ </div>
414
+ <button class="send-btn" id="send-btn" title="发送">▶</button>
415
+ </div>
416
+ </div>
417
+
418
+ </div>
419
+
420
+ <script>
421
+ // ═══════════════════════════════════════════
422
+ // 神经网络背景动画
423
+ // ═══════════════════════════════════════════
424
+ (function() {
425
+ const canvas = document.getElementById('neural-canvas');
426
+ const ctx = canvas.getContext('2d');
427
+ let W, H, nodes = [], edges = [];
428
+
429
+ function resize() {
430
+ const rect = canvas.parentElement.getBoundingClientRect();
431
+ W = canvas.width = rect.width;
432
+ H = canvas.height = rect.height;
433
+ initNodes();
434
+ }
435
+
436
+ function initNodes() {
437
+ nodes = [];
438
+ edges = [];
439
+ const count = Math.floor((W * H) / 12000);
440
+ for (let i = 0; i < count; i++) {
441
+ nodes.push({
442
+ x: Math.random() * W,
443
+ y: Math.random() * H,
444
+ vx: (Math.random() - 0.5) * 0.4,
445
+ vy: (Math.random() - 0.5) * 0.4,
446
+ r: Math.random() * 1.5 + 0.5,
447
+ pulse: Math.random() * Math.PI * 2,
448
+ });
449
+ }
450
+ for (let i = 0; i < nodes.length; i++) {
451
+ for (let j = i + 1; j < nodes.length; j++) {
452
+ const dx = nodes[i].x - nodes[j].x;
453
+ const dy = nodes[i].y - nodes[j].y;
454
+ const d = Math.sqrt(dx*dx + dy*dy);
455
+ if (d < 140) edges.push([i, j, d]);
456
+ }
457
+ }
458
+ }
459
+
460
+ function draw() {
461
+ ctx.clearRect(0, 0, W, H);
462
+
463
+ // 移动节点
464
+ nodes.forEach(n => {
465
+ n.x += n.vx; n.y += n.vy;
466
+ n.pulse += 0.02;
467
+ if (n.x < 0 || n.x > W) n.vx *= -1;
468
+ if (n.y < 0 || n.y > H) n.vy *= -1;
469
+ });
470
+
471
+ // 绘制边
472
+ ctx.lineWidth = 0.5;
473
+ edges.forEach(([i, j, base]) => {
474
+ const dx = nodes[i].x - nodes[j].x;
475
+ const dy = nodes[i].y - nodes[j].y;
476
+ const d = Math.sqrt(dx*dx + dy*dy);
477
+ if (d < 160) {
478
+ const alpha = (1 - d / 160) * 0.3;
479
+ ctx.strokeStyle = `rgba(0,245,255,${alpha})`;
480
+ ctx.beginPath();
481
+ ctx.moveTo(nodes[i].x, nodes[i].y);
482
+ ctx.lineTo(nodes[j].x, nodes[j].y);
483
+ ctx.stroke();
484
+ }
485
+ });
486
+
487
+ // 绘制节点
488
+ nodes.forEach(n => {
489
+ const glow = 0.5 + 0.5 * Math.sin(n.pulse);
490
+ ctx.beginPath();
491
+ ctx.arc(n.x, n.y, n.r + glow * 1.5, 0, Math.PI * 2);
492
+ ctx.fillStyle = `rgba(0,245,255,${0.2 + glow * 0.4})`;
493
+ ctx.fill();
494
+ });
495
+
496
+ requestAnimationFrame(draw);
497
+ }
498
+
499
+ window.addEventListener('resize', resize);
500
+ resize();
501
+ draw();
502
+ })();
503
+
504
+ // ═══════════════════════════════════════════
505
+ // 粒子背景
506
+ // ═══════════════════════════════════════════
507
+ (function() {
508
+ const canvas = document.getElementById('particles-canvas');
509
+ const ctx = canvas.getContext('2d');
510
+ let W, H, pts = [];
511
+
512
+ function resize() {
513
+ W = canvas.width = window.innerWidth;
514
+ H = canvas.height = window.innerHeight;
515
+ pts = Array.from({length: 60}, () => ({
516
+ x: Math.random() * W, y: Math.random() * H,
517
+ vx: (Math.random() - 0.5) * 0.3,
518
+ vy: (Math.random() - 0.5) * 0.3,
519
+ size: Math.random() * 1.5 + 0.5,
520
+ }));
521
+ }
522
+
523
+ function draw() {
524
+ ctx.clearRect(0, 0, W, H);
525
+ pts.forEach(p => {
526
+ p.x += p.vx; p.y += p.vy;
527
+ if (p.x < 0) p.x = W; if (p.x > W) p.x = 0;
528
+ if (p.y < 0) p.y = H; if (p.y > H) p.y = 0;
529
+ ctx.beginPath();
530
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
531
+ ctx.fillStyle = 'rgba(191,95,255,0.25)';
532
+ ctx.fill();
533
+ });
534
+ requestAnimationFrame(draw);
535
+ }
536
+
537
+ window.addEventListener('resize', resize);
538
+ resize();
539
+ draw();
540
+ })();
541
+
542
+ // ═══════════════════════════════════════════
543
+ // 对话系统
544
+ // ═══════════════════════════════════════════
545
+ const chatZone = document.getElementById('chat-zone');
546
+ const userInput = document.getElementById('user-input');
547
+ const sendBtn = document.getElementById('send-btn');
548
+
549
+ let isLoading = false;
550
+
551
+ function addMsg(html, cls = 'ai') {
552
+ const div = document.createElement('div');
553
+ div.className = `msg msg-${cls}`;
554
+ div.innerHTML = `
555
+ <div class="msg-avatar">${cls === 'ai' ? '👁️' : '🧑'}</div>
556
+ <div class="msg-bubble"></div>`;
557
+ chatZone.appendChild(div);
558
+ const bubble = div.querySelector('.msg-bubble');
559
+ if (typeof html === 'string') bubble.innerHTML = html;
560
+ else bubble.appendChild(html);
561
+ chatZone.scrollTop = chatZone.scrollHeight;
562
+ return div;
563
+ }
564
+
565
+ function typeText(bubble, text, speed = 18) {
566
+ return new Promise(resolve => {
567
+ let i = 0;
568
+ bubble.innerHTML = '';
569
+ const span = document.createElement('span');
570
+ bubble.appendChild(span);
571
+
572
+ function tick() {
573
+ if (i < text.length) {
574
+ span.textContent += text[i++];
575
+ chatZone.scrollTop = chatZone.scrollHeight;
576
+ setTimeout(tick, speed + Math.random() * 10);
577
+ } else {
578
+ resolve();
579
+ }
580
+ }
581
+ tick();
582
+ });
583
+ }
584
+
585
+ function addThinking() {
586
+ const div = document.createElement('div');
587
+ div.className = 'msg msg-ai';
588
+ div.id = 'thinking-msg';
589
+ div.innerHTML = `
590
+ <div class="msg-avatar">👁️</div>
591
+ <div class="msg-bubble">
592
+ <div class="thinking-dots">
593
+ <span></span><span></span><span></span>
594
+ </div>
595
+ <span style="font-size:12px;color:var(--text-secondary)">深度思考中...</span>
596
+ </div>`;
597
+ chatZone.appendChild(div);
598
+ chatZone.scrollTop = chatZone.scrollHeight;
599
+ return div;
600
+ }
601
+
602
+ function removeThinking() {
603
+ const el = document.getElementById('thinking-msg');
604
+ if (el) el.remove();
605
+ }
606
+
607
+ async function sendMessage() {
608
+ const text = userInput.value.trim();
609
+ if (!text || isLoading) return;
610
+
611
+ isLoading = true;
612
+ sendBtn.disabled = true;
613
+ userInput.value = '';
614
+
615
+ addMsg(`<strong>🧑</strong> ${escapeHtml(text)}`, 'user');
616
+ const thinking = addThinking();
617
+
618
+ try {
619
+ removeThinking();
620
+ const thinking2 = addThinking();
621
+ thinking2.querySelector('.msg-bubble span:last-child').textContent = '二郎神正在调用天眼分析...';
622
+
623
+ const response = await fetch('http://localhost:8765/api/brain/think', {
624
+ method: 'POST',
625
+ headers: {'Content-Type': 'application/json'},
626
+ body: JSON.stringify({prompt: text}),
627
+ });
628
+
629
+ removeThinking();
630
+
631
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
632
+ const data = await response.json();
633
+ const replyText = data.result || data.response || data.content || JSON.stringify(data);
634
+
635
+ const msg = addMsg('', 'ai');
636
+ await typeText(msg.querySelector('.msg-bubble'), replyText);
637
+
638
+ } catch (err) {
639
+ removeThinking();
640
+ // fallback: 使用内置对话
641
+ const fallback = [
642
+ `您的指令「${text.slice(0, 20)}...」已接收。由于后端服务未启动,我将作为二郎神AI直接响应。\n\n作为天眼系统,我可以帮助您进行投资分析、策略研究和市场洞察。请确保后端API服务(端口8765)已启动以获得完整能力。`,
643
+ `理解您的需求:${text}\n\n作为IT总监AI,我可以从技术架构角度为您提供专业建议。请详细描述您的技术选型或架构问题。`,
644
+ ];
645
+ const msg = addMsg('', 'ai');
646
+ await typeText(msg.querySelector('.msg-bubble'), fallback[Math.floor(Math.random() * fallback.length)]);
647
+ } finally {
648
+ isLoading = false;
649
+ sendBtn.disabled = false;
650
+ }
651
+ }
652
+
653
+ function escapeHtml(s) {
654
+ return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
655
+ }
656
+
657
+ // 自动增高输入框
658
+ userInput.addEventListener('input', () => {
659
+ userInput.style.height = 'auto';
660
+ userInput.style.height = Math.min(userInput.scrollHeight, 160) + 'px';
661
+ });
662
+
663
+ userInput.addEventListener('keydown', e => {
664
+ if (e.key === 'Enter' && !e.shiftKey) {
665
+ e.preventDefault();
666
+ sendMessage();
667
+ }
668
+ });
669
+
670
+ sendBtn.addEventListener('click', sendMessage);
671
+
672
+ // 能力卡片点击
673
+ document.querySelectorAll('.ability-card').forEach(card => {
674
+ card.addEventListener('click', () => {
675
+ const task = card.dataset.task;
676
+ const prompts = {
677
+ '感知': '分析当前宏观经济环境,识别关键市场信号与潜在风险点。',
678
+ '认知': '对A股市场进行深度认知分析,识别当前市场结构与主导逻辑。',
679
+ '决策': '基于以下市场数据,给出投资决策建议:\n- 上证指数: 3300点\n- 恐慌指数(VIX): 25\n- 北向资金: 近5日净流出',
680
+ '记忆': '检索我的投资记忆,总结过往决策的经验教训与改进方向。',
681
+ };
682
+ userInput.value = prompts[task] || '';
683
+ userInput.dispatchEvent(new Event('input'));
684
+ userInput.focus();
685
+ });
686
+ });
687
+
688
+ // 发送按钮脉冲效果
689
+ sendBtn.addEventListener('mousedown', e => {
690
+ const ring = document.createElement('div');
691
+ ring.className = 'pulse-ring';
692
+ const rect = sendBtn.getBoundingClientRect();
693
+ ring.style.left = (rect.left + rect.width/2 - 5) + 'px';
694
+ ring.style.top = (rect.top + rect.height/2 - 5) + 'px';
695
+ document.body.appendChild(ring);
696
+ setTimeout(() => ring.remove(), 1000);
697
+ });
698
+ </script>
699
+ </body>
700
+ </html>