alive-ai 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 (168) hide show
  1. package/Dockerfile +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +143 -0
  4. package/alive_ai/__init__.py +3 -0
  5. package/brain/__init__.py +59 -0
  6. package/brain/almost_said.py +154 -0
  7. package/brain/bid_detector.py +636 -0
  8. package/brain/conversation_flow.py +135 -0
  9. package/brain/curiosity.py +328 -0
  10. package/brain/default_mode.py +1438 -0
  11. package/brain/dreams.py +220 -0
  12. package/brain/embeddings/__init__.py +82 -0
  13. package/brain/emotional_memory.py +949 -0
  14. package/brain/global_activity.py +173 -0
  15. package/brain/group_dynamics.py +63 -0
  16. package/brain/linguistic.py +235 -0
  17. package/brain/llm/__init__.py +63 -0
  18. package/brain/llm/base.py +33 -0
  19. package/brain/llm/fallback_router.py +309 -0
  20. package/brain/llm/manifest.md +30 -0
  21. package/brain/llm/ollama.py +218 -0
  22. package/brain/llm/openrouter.py +151 -0
  23. package/brain/llm/provider.py +205 -0
  24. package/brain/llm/unified.py +423 -0
  25. package/brain/llm/zai.py +169 -0
  26. package/brain/manifest.md +23 -0
  27. package/brain/memory/__init__.py +123 -0
  28. package/brain/memory/episodic.py +92 -0
  29. package/brain/memory/fact_extractor.py +209 -0
  30. package/brain/memory/index.py +54 -0
  31. package/brain/memory/manager.py +151 -0
  32. package/brain/memory/summarizer.py +102 -0
  33. package/brain/memory/vector_store.py +297 -0
  34. package/brain/memory/working.py +43 -0
  35. package/brain/narrative.py +343 -0
  36. package/brain/stt/__init__.py +4 -0
  37. package/brain/stt/google_stt.py +83 -0
  38. package/brain/stt/whisper_stt.py +82 -0
  39. package/brain/subconscious/__init__.py +33 -0
  40. package/brain/subconscious/actions.py +136 -0
  41. package/brain/subconscious/evaluation.py +166 -0
  42. package/brain/subconscious/goal_system.py +90 -0
  43. package/brain/subconscious/goals.py +41 -0
  44. package/brain/subconscious/impulse_generator.py +200 -0
  45. package/brain/subconscious/impulses.py +48 -0
  46. package/brain/subconscious/learning.py +24 -0
  47. package/brain/subconscious/learning_system.py +79 -0
  48. package/brain/subconscious/loop.py +398 -0
  49. package/brain/subconscious/manifest.md +32 -0
  50. package/brain/subconscious/relationship.py +47 -0
  51. package/brain/subconscious/relationship_memory.py +83 -0
  52. package/brain/subconscious/response_analyzer.py +74 -0
  53. package/brain/subconscious/templates.py +70 -0
  54. package/brain/subconscious/thought.py +37 -0
  55. package/brain/subconscious/working_memory.py +97 -0
  56. package/cli/index.js +371 -0
  57. package/config/directives.example.json +28 -0
  58. package/config/instructions.example.md +16 -0
  59. package/config/self.example.json +74 -0
  60. package/config/settings.example.json +95 -0
  61. package/core/__init__.py +1 -0
  62. package/core/config.py +54 -0
  63. package/core/directives.py +198 -0
  64. package/core/events.py +50 -0
  65. package/core/follow_up.py +267 -0
  66. package/core/hot_reload.py +174 -0
  67. package/core/initialization.py +253 -0
  68. package/core/manifest.md +28 -0
  69. package/core/media_handler.py +241 -0
  70. package/core/memory_monitor.py +200 -0
  71. package/core/message_handler.py +1440 -0
  72. package/core/proactive_generator.py +277 -0
  73. package/core/self.py +188 -0
  74. package/core/settings.py +169 -0
  75. package/core/skills_registry.py +357 -0
  76. package/core/state.py +27 -0
  77. package/core/subconscious_bridge.py +93 -0
  78. package/core/thinking.py +175 -0
  79. package/core/user_manager.py +306 -0
  80. package/core/user_tracker.py +144 -0
  81. package/demo/index.html +144 -0
  82. package/docker-compose.yml +28 -0
  83. package/docs/assets/logo.svg +15 -0
  84. package/docs/index.html +355 -0
  85. package/heart/__init__.py +93 -0
  86. package/heart/afterglow.py +215 -0
  87. package/heart/attachment.py +186 -0
  88. package/heart/circadian.py +251 -0
  89. package/heart/complex_emotions.py +114 -0
  90. package/heart/conflicts.py +589 -0
  91. package/heart/core.py +387 -0
  92. package/heart/emotional_decay.py +59 -0
  93. package/heart/emotional_memory.py +261 -0
  94. package/heart/emotional_state.py +146 -0
  95. package/heart/emotional_variability.py +156 -0
  96. package/heart/hormonal.py +424 -0
  97. package/heart/inconsistency.py +1222 -0
  98. package/heart/integrity.py +469 -0
  99. package/heart/interoception.py +997 -0
  100. package/heart/love.py +120 -0
  101. package/heart/manifest.md +25 -0
  102. package/heart/mood_shifts.py +169 -0
  103. package/heart/phantom_somatic.py +259 -0
  104. package/heart/predictive.py +374 -0
  105. package/heart/scars.py +474 -0
  106. package/heart/somatic.py +482 -0
  107. package/heart/soul.py +633 -0
  108. package/heart/telemetry.py +942 -0
  109. package/heart/triggers.py +119 -0
  110. package/heart/unconscious.py +443 -0
  111. package/input/__init__.py +1 -0
  112. package/input/manifest.md +24 -0
  113. package/input/telegram/__init__.py +1 -0
  114. package/input/telegram/commands.py +762 -0
  115. package/input/telegram/listener.py +532 -0
  116. package/main.py +90 -0
  117. package/manifest.md +28 -0
  118. package/mypics/.gitkeep +1 -0
  119. package/myvids/.gitkeep +1 -0
  120. package/output/__init__.py +1 -0
  121. package/output/images/__init__.py +1 -0
  122. package/output/images/fal_gen.py +43 -0
  123. package/output/manifest.md +26 -0
  124. package/output/text/__init__.py +1 -0
  125. package/output/text/sender.py +22 -0
  126. package/output/voice/__init__.py +64 -0
  127. package/output/voice/google_tts.py +252 -0
  128. package/output/voice/gtts_tts.py +214 -0
  129. package/output/voice/vibe_tts.py +190 -0
  130. package/package.json +58 -0
  131. package/pyproject.toml +23 -0
  132. package/requirements.txt +21 -0
  133. package/skills/__init__.py +1 -0
  134. package/skills/anticipation_engine/__init__.py +8 -0
  135. package/skills/anticipation_engine/engine.py +618 -0
  136. package/skills/anticipation_engine/manifest.md +192 -0
  137. package/skills/calendar/__init__.py +1 -0
  138. package/skills/content_unlocks/__init__.py +8 -0
  139. package/skills/content_unlocks/manifest.md +231 -0
  140. package/skills/content_unlocks/unlocks.py +945 -0
  141. package/skills/exclusive_moments/__init__.py +8 -0
  142. package/skills/exclusive_moments/manifest.md +145 -0
  143. package/skills/exclusive_moments/moments.py +506 -0
  144. package/skills/intimacy_layers/__init__.py +8 -0
  145. package/skills/intimacy_layers/layers.py +703 -0
  146. package/skills/intimacy_layers/manifest.md +203 -0
  147. package/skills/manifest.md +67 -0
  148. package/skills/memory_callbacks/__init__.py +9 -0
  149. package/skills/memory_callbacks/callbacks.py +748 -0
  150. package/skills/memory_callbacks/manifest.md +170 -0
  151. package/skills/message_scheduler/__init__.py +19 -0
  152. package/skills/message_scheduler/manifest.md +107 -0
  153. package/skills/message_scheduler/scheduler.py +510 -0
  154. package/skills/photo_manager/__init__.py +1 -0
  155. package/skills/photo_manager/scanner.py +296 -0
  156. package/skills/relationship_milestones/__init__.py +8 -0
  157. package/skills/relationship_milestones/manifest.md +206 -0
  158. package/skills/relationship_milestones/tracker.py +494 -0
  159. package/skills/self_authorship/__init__.py +23 -0
  160. package/skills/self_authorship/author.py +331 -0
  161. package/skills/self_authorship/manifest.md +24 -0
  162. package/skills/video_manager/__init__.py +5 -0
  163. package/skills/video_manager/manifest.md +37 -0
  164. package/skills/video_manager/scanner.py +229 -0
  165. package/webui/__init__.py +3 -0
  166. package/webui/app.py +936 -0
  167. package/webui/bridge.py +366 -0
  168. package/webui/static/index.html +2070 -0
@@ -0,0 +1,2070 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <meta name="theme-color" content="#0f0f1a">
7
+ <meta name="apple-mobile-web-app-capable" content="yes">
8
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
9
+ <title>Alive-AI</title>
10
+ <link rel="manifest" href="/static/manifest.json">
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
12
+ <style>
13
+ * {
14
+ margin: 0;
15
+ padding: 0;
16
+ box-sizing: border-box;
17
+ -webkit-tap-highlight-color: transparent;
18
+ }
19
+
20
+ :root {
21
+ --bg-primary: #0a0a14;
22
+ --bg-secondary: #12121f;
23
+ --bg-card: #1a1a2e;
24
+ --bg-elevated: #22223a;
25
+ --accent-pink: #ff6b9d;
26
+ --accent-purple: #c44569;
27
+ --accent-blue: #4a90d9;
28
+ --accent-gold: #f39c12;
29
+ --accent-green: #4caf50;
30
+ --text-primary: #ffffff;
31
+ --text-secondary: #b0b0c0;
32
+ --text-muted: #6c6c7c;
33
+ --glow-pink: rgba(255, 107, 157, 0.4);
34
+ --safe-top: env(safe-area-inset-top, 0px);
35
+ --safe-bottom: env(safe-area-inset-bottom, 0px);
36
+ }
37
+
38
+ html, body {
39
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
40
+ background: var(--bg-primary);
41
+ min-height: 100vh;
42
+ min-height: -webkit-fill-available;
43
+ color: var(--text-primary);
44
+ overflow-x: hidden;
45
+ -webkit-font-smoothing: antialiased;
46
+ }
47
+
48
+ /* Connection Banner */
49
+ .connection-banner {
50
+ position: fixed;
51
+ top: 0;
52
+ left: 0;
53
+ right: 0;
54
+ background: linear-gradient(90deg, #e74c3c, #c0392b);
55
+ color: white;
56
+ text-align: center;
57
+ padding: 8px;
58
+ font-size: 0.85rem;
59
+ font-weight: 500;
60
+ z-index: 1000;
61
+ transform: translateY(-100%);
62
+ transition: transform 0.3s ease;
63
+ }
64
+
65
+ .connection-banner.show {
66
+ transform: translateY(0);
67
+ }
68
+
69
+ /* Mobile App Shell */
70
+ .app {
71
+ min-height: 100vh;
72
+ min-height: -webkit-fill-available;
73
+ display: flex;
74
+ flex-direction: column;
75
+ padding-top: var(--safe-top);
76
+ padding-bottom: var(--safe-bottom);
77
+ }
78
+
79
+ /* Header */
80
+ .header {
81
+ position: sticky;
82
+ top: 0;
83
+ z-index: 100;
84
+ background: linear-gradient(180deg, var(--bg-primary) 0%, var(--bg-primary) 70%, transparent 100%);
85
+ padding: 12px 16px;
86
+ padding-top: calc(12px + var(--safe-top));
87
+ }
88
+
89
+ .header-content {
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: space-between;
93
+ max-width: 600px;
94
+ margin: 0 auto;
95
+ }
96
+
97
+ .header-left {
98
+ display: flex;
99
+ align-items: center;
100
+ gap: 12px;
101
+ }
102
+
103
+ .avatar-ring {
104
+ width: 44px;
105
+ height: 44px;
106
+ border-radius: 50%;
107
+ padding: 2px;
108
+ background: linear-gradient(135deg, var(--accent-pink), var(--accent-purple));
109
+ animation: pulse-ring 2s ease-in-out infinite;
110
+ }
111
+
112
+ @keyframes pulse-ring {
113
+ 0%, 100% { box-shadow: 0 0 0 0 var(--glow-pink); }
114
+ 50% { box-shadow: 0 0 0 8px transparent; }
115
+ }
116
+
117
+ .avatar-ring img {
118
+ width: 100%;
119
+ height: 100%;
120
+ border-radius: 50%;
121
+ object-fit: cover;
122
+ }
123
+
124
+ .header-info h1 {
125
+ font-size: 1.1rem;
126
+ font-weight: 600;
127
+ background: linear-gradient(135deg, var(--accent-pink), var(--accent-purple));
128
+ -webkit-background-clip: text;
129
+ -webkit-text-fill-color: transparent;
130
+ background-clip: text;
131
+ }
132
+
133
+ .header-status {
134
+ display: flex;
135
+ align-items: center;
136
+ gap: 6px;
137
+ font-size: 0.75rem;
138
+ color: var(--accent-green);
139
+ }
140
+
141
+ .status-dot {
142
+ width: 6px;
143
+ height: 6px;
144
+ background: var(--accent-green);
145
+ border-radius: 50%;
146
+ animation: blink 2s infinite;
147
+ }
148
+
149
+ @keyframes blink {
150
+ 0%, 100% { opacity: 1; }
151
+ 50% { opacity: 0.4; }
152
+ }
153
+
154
+ .mood-badge {
155
+ display: flex;
156
+ align-items: center;
157
+ gap: 6px;
158
+ padding: 6px 12px;
159
+ background: var(--bg-card);
160
+ border-radius: 20px;
161
+ font-size: 0.85rem;
162
+ }
163
+
164
+ .mood-emoji {
165
+ font-size: 1.1rem;
166
+ }
167
+
168
+ /* Main Content */
169
+ .main {
170
+ flex: 1;
171
+ overflow-y: auto;
172
+ padding: 0 16px 100px;
173
+ max-width: 600px;
174
+ margin: 0 auto;
175
+ width: 100%;
176
+ }
177
+
178
+ /* Stats Row */
179
+ .stats-row {
180
+ display: grid;
181
+ grid-template-columns: repeat(3, 1fr);
182
+ gap: 10px;
183
+ margin-bottom: 16px;
184
+ }
185
+
186
+ .stat-card {
187
+ background: var(--bg-card);
188
+ border-radius: 16px;
189
+ padding: 14px;
190
+ text-align: center;
191
+ transition: transform 0.2s;
192
+ }
193
+
194
+ .stat-card:active {
195
+ transform: scale(0.95);
196
+ }
197
+
198
+ .stat-value {
199
+ font-size: 1.5rem;
200
+ font-weight: 700;
201
+ background: linear-gradient(135deg, var(--accent-pink), var(--accent-purple));
202
+ -webkit-background-clip: text;
203
+ -webkit-text-fill-color: transparent;
204
+ background-clip: text;
205
+ }
206
+
207
+ .stat-label {
208
+ font-size: 0.7rem;
209
+ color: var(--text-muted);
210
+ text-transform: uppercase;
211
+ letter-spacing: 0.5px;
212
+ margin-top: 4px;
213
+ }
214
+
215
+ /* Section */
216
+ .section {
217
+ background: var(--bg-card);
218
+ border-radius: 20px;
219
+ padding: 16px;
220
+ margin-bottom: 12px;
221
+ }
222
+
223
+ .section-header {
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: space-between;
227
+ margin-bottom: 14px;
228
+ }
229
+
230
+ .section-title {
231
+ display: flex;
232
+ align-items: center;
233
+ gap: 8px;
234
+ font-size: 0.9rem;
235
+ font-weight: 600;
236
+ }
237
+
238
+ .section-icon {
239
+ font-size: 1.1rem;
240
+ }
241
+
242
+ /* Emotion Grid */
243
+ .emotion-grid {
244
+ display: grid;
245
+ gap: 10px;
246
+ }
247
+
248
+ .emotion-row {
249
+ display: flex;
250
+ align-items: center;
251
+ gap: 10px;
252
+ }
253
+
254
+ .emotion-icon {
255
+ width: 32px;
256
+ height: 32px;
257
+ border-radius: 10px;
258
+ display: flex;
259
+ align-items: center;
260
+ justify-content: center;
261
+ font-size: 0.9rem;
262
+ flex-shrink: 0;
263
+ }
264
+
265
+ .emotion-icon.arousal { background: linear-gradient(135deg, #4a90d9, #357abd); }
266
+ .emotion-icon.desire { background: linear-gradient(135deg, #ff6b9d, #e91e63); }
267
+ .emotion-icon.love { background: linear-gradient(135deg, #e91e63, #ff4081); }
268
+ .emotion-icon.joy { background: linear-gradient(135deg, #ffc107, #f39c12); }
269
+ .emotion-icon.sadness { background: linear-gradient(135deg, #5c6bc0, #3f51b5); }
270
+ .emotion-icon.trust { background: linear-gradient(135deg, #4caf50, #8bc34a); }
271
+ .emotion-icon.fear { background: linear-gradient(135deg, #9c27b0, #673ab7); }
272
+ .emotion-icon.anger { background: linear-gradient(135deg, #f44336, #d32f2f); }
273
+ .emotion-icon.boredom { background: linear-gradient(135deg, #9e9e9e, #757575); }
274
+ .emotion-icon.guilt { background: linear-gradient(135deg, #795548, #5d4037); }
275
+ .emotion-icon.pride { background: linear-gradient(135deg, #ff9800, #f57c00); }
276
+ .emotion-icon.jealousy { background: linear-gradient(135deg, #8bc34a, #689f38); }
277
+ .emotion-icon.embarrassment { background: linear-gradient(135deg, #ffab91, #ff8a65); }
278
+ .emotion-icon.anticipation { background: linear-gradient(135deg, #00bcd4, #0097a7); }
279
+ .emotion-icon.hope { background: linear-gradient(135deg, #64b5f6, #42a5f5); }
280
+ .emotion-icon.dread { background: linear-gradient(135deg, #455a64, #37474f); }
281
+
282
+ .emotion-content {
283
+ flex: 1;
284
+ }
285
+
286
+ .emotion-label {
287
+ display: flex;
288
+ justify-content: space-between;
289
+ font-size: 0.8rem;
290
+ margin-bottom: 4px;
291
+ }
292
+
293
+ .emotion-name {
294
+ color: var(--text-secondary);
295
+ }
296
+
297
+ .emotion-value {
298
+ font-weight: 600;
299
+ }
300
+
301
+ .emotion-bar {
302
+ height: 6px;
303
+ background: rgba(255, 255, 255, 0.1);
304
+ border-radius: 3px;
305
+ overflow: hidden;
306
+ }
307
+
308
+ .emotion-fill {
309
+ height: 100%;
310
+ border-radius: 3px;
311
+ transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
312
+ }
313
+
314
+ .emotion-fill.arousal { background: linear-gradient(90deg, #4a90d9, #357abd); }
315
+ .emotion-fill.desire { background: linear-gradient(90deg, #ff6b9d, #e91e63); }
316
+ .emotion-fill.love { background: linear-gradient(90deg, #e91e63, #ff4081); }
317
+ .emotion-fill.joy { background: linear-gradient(90deg, #ffc107, #f39c12); }
318
+ .emotion-fill.sadness { background: linear-gradient(90deg, #5c6bc0, #3f51b5); }
319
+ .emotion-fill.trust { background: linear-gradient(90deg, #4caf50, #8bc34a); }
320
+ .emotion-fill.fear { background: linear-gradient(90deg, #9c27b0, #673ab7); }
321
+ .emotion-fill.anger { background: linear-gradient(90deg, #f44336, #d32f2f); }
322
+ .emotion-fill.boredom { background: linear-gradient(90deg, #9e9e9e, #757575); }
323
+ .emotion-fill.guilt { background: linear-gradient(90deg, #795548, #5d4037); }
324
+ .emotion-fill.pride { background: linear-gradient(90deg, #ff9800, #f57c00); }
325
+ .emotion-fill.jealousy { background: linear-gradient(90deg, #8bc34a, #689f38); }
326
+ .emotion-fill.embarrassment { background: linear-gradient(90deg, #ffab91, #ff8a65); }
327
+ .emotion-fill.anticipation { background: linear-gradient(90deg, #00bcd4, #0097a7); }
328
+ .emotion-fill.hope { background: linear-gradient(90deg, #64b5f6, #42a5f5); }
329
+ .emotion-fill.dread { background: linear-gradient(90deg, #455a64, #37474f); }
330
+
331
+ /* Internal State Section */
332
+ .internal-section {
333
+ background: linear-gradient(135deg, rgba(255, 107, 157, 0.08), rgba(74, 144, 217, 0.05));
334
+ border: 1px solid rgba(255, 107, 157, 0.15);
335
+ }
336
+
337
+ .internal-grid {
338
+ display: flex;
339
+ flex-direction: column;
340
+ gap: 10px;
341
+ }
342
+
343
+ .internal-row {
344
+ display: flex;
345
+ align-items: center;
346
+ gap: 10px;
347
+ }
348
+
349
+ .internal-icon {
350
+ width: 28px;
351
+ height: 28px;
352
+ border-radius: 8px;
353
+ display: flex;
354
+ align-items: center;
355
+ justify-content: center;
356
+ font-size: 0.8rem;
357
+ flex-shrink: 0;
358
+ }
359
+
360
+ .internal-icon.energy { background: linear-gradient(135deg, #ffc107, #ff9800); }
361
+ .internal-icon.social { background: linear-gradient(135deg, #4caf50, #8bc34a); }
362
+ .internal-icon.valence { background: linear-gradient(135deg, #e91e63, #ff4081); }
363
+ .internal-icon.certainty { background: linear-gradient(135deg, #2196f3, #03a9f4); }
364
+
365
+ .internal-content {
366
+ flex: 1;
367
+ }
368
+
369
+ .internal-label {
370
+ display: flex;
371
+ justify-content: space-between;
372
+ font-size: 0.75rem;
373
+ margin-bottom: 3px;
374
+ }
375
+
376
+ .internal-name {
377
+ color: var(--text-secondary);
378
+ }
379
+
380
+ .internal-value {
381
+ font-weight: 600;
382
+ color: var(--text-primary);
383
+ }
384
+
385
+ .internal-bar {
386
+ height: 5px;
387
+ background: rgba(255, 255, 255, 0.1);
388
+ border-radius: 3px;
389
+ overflow: hidden;
390
+ }
391
+
392
+ .internal-fill {
393
+ height: 100%;
394
+ border-radius: 3px;
395
+ transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
396
+ }
397
+
398
+ .internal-fill.energy { background: linear-gradient(90deg, #ffc107, #ff9800); }
399
+ .internal-fill.social { background: linear-gradient(90deg, #4caf50, #8bc34a); }
400
+ .internal-fill.valence { background: linear-gradient(90deg, #e91e63, #ff4081); }
401
+ .internal-fill.certainty { background: linear-gradient(90deg, #2196f3, #03a9f4); }
402
+
403
+ .bodily-feeling {
404
+ display: flex;
405
+ align-items: center;
406
+ gap: 8px;
407
+ padding: 10px;
408
+ background: rgba(255, 255, 255, 0.05);
409
+ border-radius: 10px;
410
+ margin-top: 10px;
411
+ }
412
+
413
+ .feeling-icon {
414
+ font-size: 1rem;
415
+ }
416
+
417
+ .feeling-text {
418
+ font-size: 0.8rem;
419
+ color: var(--text-secondary);
420
+ font-style: italic;
421
+ }
422
+
423
+ /* Thoughts Section */
424
+ .thoughts-section {
425
+ background: linear-gradient(135deg, rgba(156, 39, 176, 0.1), rgba(103, 58, 183, 0.05));
426
+ border: 1px solid rgba(156, 39, 176, 0.2);
427
+ }
428
+
429
+ .current-thought {
430
+ display: flex;
431
+ align-items: flex-start;
432
+ gap: 12px;
433
+ padding: 12px;
434
+ background: rgba(255, 255, 255, 0.05);
435
+ border-radius: 12px;
436
+ margin-bottom: 12px;
437
+ }
438
+
439
+ .thought-icon {
440
+ font-size: 1.5rem;
441
+ animation: float 3s ease-in-out infinite;
442
+ }
443
+
444
+ @keyframes float {
445
+ 0%, 100% { transform: translateY(0); }
446
+ 50% { transform: translateY(-5px); }
447
+ }
448
+
449
+ .thought-text {
450
+ font-style: italic;
451
+ color: var(--text-secondary);
452
+ line-height: 1.5;
453
+ font-size: 0.9rem;
454
+ flex: 1;
455
+ }
456
+
457
+ .thought-text.empty {
458
+ font-style: normal;
459
+ color: var(--text-muted);
460
+ }
461
+
462
+ .idle-thoughts {
463
+ display: flex;
464
+ flex-direction: column;
465
+ gap: 6px;
466
+ max-height: 120px;
467
+ overflow-y: auto;
468
+ }
469
+
470
+ .idle-thought {
471
+ padding: 8px 12px;
472
+ background: rgba(255, 255, 255, 0.05);
473
+ border-radius: 10px;
474
+ font-size: 0.8rem;
475
+ color: var(--text-secondary);
476
+ border-left: 3px solid var(--accent-purple);
477
+ }
478
+
479
+ .idle-thought .thought-type {
480
+ font-size: 0.65rem;
481
+ color: var(--text-muted);
482
+ text-transform: uppercase;
483
+ margin-bottom: 2px;
484
+ }
485
+
486
+ .idle-empty {
487
+ text-align: center;
488
+ color: var(--text-muted);
489
+ padding: 15px;
490
+ font-size: 0.8rem;
491
+ }
492
+
493
+ /* Hormones Section */
494
+ .hormones-section {
495
+ background: linear-gradient(135deg, rgba(74, 144, 217, 0.1), rgba(255, 107, 157, 0.05));
496
+ border: 1px solid rgba(74, 144, 217, 0.2);
497
+ }
498
+
499
+ .hormone-grid {
500
+ display: grid;
501
+ grid-template-columns: repeat(4, 1fr);
502
+ gap: 8px;
503
+ }
504
+
505
+ .hormone-chip {
506
+ display: flex;
507
+ flex-direction: column;
508
+ align-items: center;
509
+ padding: 12px 6px;
510
+ background: rgba(255, 255, 255, 0.05);
511
+ border-radius: 12px;
512
+ transition: all 0.3s;
513
+ }
514
+
515
+ .hormone-chip.elevated {
516
+ background: rgba(255, 107, 157, 0.15);
517
+ }
518
+
519
+ .hormone-icon {
520
+ font-size: 1.2rem;
521
+ margin-bottom: 4px;
522
+ }
523
+
524
+ .hormone-name {
525
+ font-size: 0.6rem;
526
+ color: var(--text-muted);
527
+ text-transform: uppercase;
528
+ letter-spacing: 0.3px;
529
+ }
530
+
531
+ .hormone-val {
532
+ font-size: 0.85rem;
533
+ font-weight: 600;
534
+ color: var(--text-primary);
535
+ margin-top: 2px;
536
+ }
537
+
538
+ /* Conflicts Section */
539
+ .conflicts-section {
540
+ background: linear-gradient(135deg, rgba(231, 76, 60, 0.08), rgba(255, 152, 0, 0.05));
541
+ border: 1px solid rgba(231, 76, 60, 0.2);
542
+ }
543
+
544
+ .conflicts-header {
545
+ display: flex;
546
+ justify-content: space-between;
547
+ align-items: center;
548
+ margin-bottom: 10px;
549
+ }
550
+
551
+ .conflict-count-badge {
552
+ background: rgba(231, 76, 60, 0.3);
553
+ color: #e74c3c;
554
+ padding: 4px 12px;
555
+ border-radius: 12px;
556
+ font-weight: 600;
557
+ font-size: 0.8rem;
558
+ }
559
+
560
+ .conflicts-list {
561
+ display: flex;
562
+ flex-direction: column;
563
+ gap: 6px;
564
+ }
565
+
566
+ .conflict-item {
567
+ padding: 10px 14px;
568
+ background: rgba(255, 255, 255, 0.05);
569
+ border-radius: 10px;
570
+ font-size: 0.8rem;
571
+ color: var(--text-secondary);
572
+ }
573
+
574
+ .conflict-item .conflict-name {
575
+ color: var(--text-primary);
576
+ font-weight: 500;
577
+ margin-bottom: 2px;
578
+ }
579
+
580
+ .conflict-item .conflict-detail {
581
+ color: var(--text-muted);
582
+ font-size: 0.75rem;
583
+ }
584
+
585
+ .conflict-empty {
586
+ text-align: center;
587
+ color: var(--text-muted);
588
+ padding: 15px;
589
+ font-size: 0.8rem;
590
+ }
591
+
592
+ .tendency-row {
593
+ display: flex;
594
+ align-items: center;
595
+ justify-content: space-between;
596
+ padding: 10px;
597
+ background: rgba(0, 0, 0, 0.2);
598
+ border-radius: 10px;
599
+ margin-top: 12px;
600
+ }
601
+
602
+ .tendency-label {
603
+ font-size: 0.75rem;
604
+ color: var(--text-muted);
605
+ }
606
+
607
+ .tendency-badge {
608
+ padding: 4px 12px;
609
+ border-radius: 12px;
610
+ color: var(--text-secondary);
611
+ text-transform: capitalize;
612
+ font-size: 0.8rem;
613
+ background: rgba(255, 255, 255, 0.1);
614
+ }
615
+
616
+ .tendency-badge.open { background: rgba(76, 175, 80, 0.2); color: #4caf50; }
617
+ .tendency-badge.seeking { background: rgba(74, 144, 217, 0.2); color: #4a90d9; }
618
+ .tendency-badge.eager { background: rgba(255, 193, 7, 0.2); color: #ffc107; }
619
+ .tendency-badge.withdrawn { background: rgba(156, 39, 176, 0.2); color: #9c27b0; }
620
+ .tendency-badge.defensive { background: rgba(231, 76, 60, 0.2); color: #e74c3c; }
621
+ .tendency-badge.ambivalent { background: rgba(243, 156, 18, 0.2); color: #f39c12; }
622
+
623
+ /* Bottom Nav */
624
+ .bottom-nav {
625
+ position: fixed;
626
+ bottom: 0;
627
+ left: 0;
628
+ right: 0;
629
+ background: var(--bg-secondary);
630
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
631
+ padding: 8px 0;
632
+ padding-bottom: calc(8px + var(--safe-bottom));
633
+ display: flex;
634
+ justify-content: center;
635
+ gap: 40px;
636
+ z-index: 100;
637
+ }
638
+
639
+ .nav-item {
640
+ display: flex;
641
+ flex-direction: column;
642
+ align-items: center;
643
+ gap: 2px;
644
+ padding: 8px 16px;
645
+ border-radius: 12px;
646
+ transition: all 0.2s;
647
+ cursor: pointer;
648
+ }
649
+
650
+ .nav-item.active {
651
+ background: rgba(255, 107, 157, 0.1);
652
+ }
653
+
654
+ .nav-item:active {
655
+ transform: scale(0.9);
656
+ }
657
+
658
+ .nav-icon {
659
+ font-size: 1.3rem;
660
+ }
661
+
662
+ .nav-label {
663
+ font-size: 0.65rem;
664
+ color: var(--text-muted);
665
+ }
666
+
667
+ .nav-item.active .nav-label {
668
+ color: var(--accent-pink);
669
+ }
670
+
671
+ /* Desktop Enhancements */
672
+ @media (min-width: 768px) {
673
+ .app {
674
+ max-width: 480px;
675
+ margin: 0 auto;
676
+ border-radius: 24px;
677
+ margin-top: 20px;
678
+ margin-bottom: 20px;
679
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
680
+ overflow: hidden;
681
+ background: var(--bg-secondary);
682
+ }
683
+
684
+ .header {
685
+ position: relative;
686
+ background: var(--bg-secondary);
687
+ }
688
+
689
+ .bottom-nav {
690
+ position: relative;
691
+ background: var(--bg-secondary);
692
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
693
+ }
694
+
695
+ .main {
696
+ padding-bottom: 20px;
697
+ }
698
+ }
699
+
700
+ /* Circadian Section */
701
+ .circadian-section {
702
+ background: linear-gradient(135deg, rgba(38, 166, 154, 0.1), rgba(255, 183, 77, 0.05));
703
+ border: 1px solid rgba(38, 166, 154, 0.2);
704
+ }
705
+
706
+ .circadian-phase {
707
+ display: flex;
708
+ align-items: center;
709
+ justify-content: space-between;
710
+ margin-bottom: 12px;
711
+ }
712
+
713
+ .circadian-phase-name {
714
+ font-size: 1rem;
715
+ font-weight: 600;
716
+ color: var(--text-primary);
717
+ }
718
+
719
+ .circadian-sleep-badge {
720
+ display: flex;
721
+ align-items: center;
722
+ gap: 6px;
723
+ padding: 4px 12px;
724
+ border-radius: 12px;
725
+ font-size: 0.8rem;
726
+ font-weight: 500;
727
+ }
728
+
729
+ .circadian-sleep-badge.awake {
730
+ background: rgba(76, 175, 80, 0.2);
731
+ color: #4caf50;
732
+ }
733
+
734
+ .circadian-sleep-badge.sleeping {
735
+ background: rgba(156, 39, 176, 0.2);
736
+ color: #ce93d8;
737
+ }
738
+
739
+ .circadian-sleep-dot {
740
+ width: 8px;
741
+ height: 8px;
742
+ border-radius: 50%;
743
+ }
744
+
745
+ .circadian-sleep-badge.awake .circadian-sleep-dot { background: #4caf50; }
746
+ .circadian-sleep-badge.sleeping .circadian-sleep-dot { background: #ce93d8; }
747
+
748
+ .circadian-debt {
749
+ margin-bottom: 12px;
750
+ }
751
+
752
+ .circadian-debt-label {
753
+ display: flex;
754
+ justify-content: space-between;
755
+ font-size: 0.75rem;
756
+ margin-bottom: 4px;
757
+ }
758
+
759
+ .circadian-debt-label span:first-child { color: var(--text-secondary); }
760
+ .circadian-debt-label span:last-child { font-weight: 600; }
761
+
762
+ .circadian-debt-bar {
763
+ height: 5px;
764
+ background: rgba(255, 255, 255, 0.1);
765
+ border-radius: 3px;
766
+ overflow: hidden;
767
+ }
768
+
769
+ .circadian-debt-fill {
770
+ height: 100%;
771
+ border-radius: 3px;
772
+ background: linear-gradient(90deg, #26a69a, #ffb74d);
773
+ transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
774
+ }
775
+
776
+ .circadian-modifiers {
777
+ display: grid;
778
+ grid-template-columns: repeat(4, 1fr);
779
+ gap: 6px;
780
+ }
781
+
782
+ .circadian-mod-chip {
783
+ display: flex;
784
+ flex-direction: column;
785
+ align-items: center;
786
+ padding: 8px 4px;
787
+ background: rgba(255, 255, 255, 0.05);
788
+ border-radius: 10px;
789
+ }
790
+
791
+ .circadian-mod-name {
792
+ font-size: 0.55rem;
793
+ color: var(--text-muted);
794
+ text-transform: uppercase;
795
+ letter-spacing: 0.3px;
796
+ margin-bottom: 2px;
797
+ }
798
+
799
+ .circadian-mod-val {
800
+ font-size: 0.8rem;
801
+ font-weight: 600;
802
+ }
803
+
804
+ /* Attachment Section */
805
+ .attachment-section {
806
+ background: linear-gradient(135deg, rgba(255, 152, 0, 0.1), rgba(233, 30, 99, 0.05));
807
+ border: 1px solid rgba(255, 152, 0, 0.2);
808
+ }
809
+
810
+ .attachment-style-badge {
811
+ display: inline-block;
812
+ padding: 6px 16px;
813
+ border-radius: 16px;
814
+ font-size: 0.95rem;
815
+ font-weight: 600;
816
+ margin-bottom: 12px;
817
+ }
818
+
819
+ .attachment-style-badge.secure { background: rgba(76, 175, 80, 0.2); color: #4caf50; }
820
+ .attachment-style-badge.anxious { background: rgba(255, 193, 7, 0.2); color: #ffc107; }
821
+ .attachment-style-badge.avoidant { background: rgba(255, 152, 0, 0.2); color: #ff9800; }
822
+ .attachment-style-badge.disorganized { background: rgba(231, 76, 60, 0.2); color: #e74c3c; }
823
+
824
+ .attachment-security {
825
+ margin-bottom: 10px;
826
+ }
827
+
828
+ .attachment-security-label {
829
+ display: flex;
830
+ justify-content: space-between;
831
+ font-size: 0.75rem;
832
+ margin-bottom: 4px;
833
+ }
834
+
835
+ .attachment-security-label span:first-child { color: var(--text-secondary); }
836
+ .attachment-security-label span:last-child { font-weight: 600; }
837
+
838
+ .attachment-security-bar {
839
+ height: 6px;
840
+ background: rgba(255, 255, 255, 0.1);
841
+ border-radius: 3px;
842
+ overflow: hidden;
843
+ }
844
+
845
+ .attachment-security-fill {
846
+ height: 100%;
847
+ border-radius: 3px;
848
+ transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
849
+ }
850
+
851
+ .attachment-trend {
852
+ display: flex;
853
+ align-items: center;
854
+ gap: 8px;
855
+ padding: 8px 12px;
856
+ background: rgba(255, 255, 255, 0.05);
857
+ border-radius: 10px;
858
+ }
859
+
860
+ .attachment-trend-label {
861
+ font-size: 0.75rem;
862
+ color: var(--text-muted);
863
+ }
864
+
865
+ .attachment-trend-arrow {
866
+ font-size: 1rem;
867
+ font-weight: 700;
868
+ }
869
+
870
+ .attachment-trend-arrow.improving { color: #4caf50; }
871
+ .attachment-trend-arrow.declining { color: #e74c3c; }
872
+ .attachment-trend-arrow.stable { color: var(--text-secondary); }
873
+
874
+ /* Body Memory Section (Afterglow + Phantom) */
875
+ .body-memory-section {
876
+ background: linear-gradient(135deg, rgba(233, 30, 99, 0.08), rgba(156, 39, 176, 0.05));
877
+ border: 1px solid rgba(233, 30, 99, 0.15);
878
+ }
879
+
880
+ .body-memory-list {
881
+ display: flex;
882
+ flex-direction: column;
883
+ gap: 8px;
884
+ }
885
+
886
+ .body-memory-item {
887
+ display: flex;
888
+ align-items: center;
889
+ gap: 10px;
890
+ padding: 10px;
891
+ background: rgba(255, 255, 255, 0.05);
892
+ border-radius: 10px;
893
+ }
894
+
895
+ .body-memory-icon {
896
+ font-size: 1.2rem;
897
+ flex-shrink: 0;
898
+ }
899
+
900
+ .body-memory-content {
901
+ flex: 1;
902
+ }
903
+
904
+ .body-memory-name {
905
+ font-size: 0.8rem;
906
+ color: var(--text-primary);
907
+ font-weight: 500;
908
+ margin-bottom: 3px;
909
+ }
910
+
911
+ .body-memory-bar {
912
+ height: 4px;
913
+ background: rgba(255, 255, 255, 0.1);
914
+ border-radius: 2px;
915
+ overflow: hidden;
916
+ }
917
+
918
+ .body-memory-fill {
919
+ height: 100%;
920
+ border-radius: 2px;
921
+ background: linear-gradient(90deg, #e91e63, #9c27b0);
922
+ transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
923
+ }
924
+
925
+ .body-memory-meta {
926
+ font-size: 0.65rem;
927
+ color: var(--text-muted);
928
+ flex-shrink: 0;
929
+ }
930
+
931
+ .body-memory-empty {
932
+ text-align: center;
933
+ color: var(--text-muted);
934
+ padding: 15px;
935
+ font-size: 0.8rem;
936
+ }
937
+
938
+ .body-memory-sub-header {
939
+ font-size: 0.7rem;
940
+ color: var(--text-muted);
941
+ text-transform: uppercase;
942
+ letter-spacing: 0.5px;
943
+ margin-top: 12px;
944
+ margin-bottom: 6px;
945
+ }
946
+
947
+ .phantom-desc {
948
+ font-size: 0.7rem;
949
+ color: var(--text-secondary);
950
+ font-style: italic;
951
+ }
952
+
953
+ /* Story Section (Narrative + Dreams) */
954
+ .story-section {
955
+ background: linear-gradient(135deg, rgba(255, 183, 77, 0.1), rgba(255, 107, 157, 0.05));
956
+ border: 1px solid rgba(255, 183, 77, 0.2);
957
+ }
958
+
959
+ .story-phase-badge {
960
+ display: inline-block;
961
+ padding: 5px 14px;
962
+ border-radius: 14px;
963
+ font-size: 0.8rem;
964
+ font-weight: 500;
965
+ background: rgba(255, 183, 77, 0.2);
966
+ color: #ffb74d;
967
+ margin-bottom: 10px;
968
+ }
969
+
970
+ .story-stats {
971
+ display: flex;
972
+ gap: 16px;
973
+ margin-bottom: 12px;
974
+ }
975
+
976
+ .story-stat {
977
+ font-size: 0.8rem;
978
+ color: var(--text-secondary);
979
+ }
980
+
981
+ .story-stat strong {
982
+ color: var(--text-primary);
983
+ }
984
+
985
+ .dream-bubble {
986
+ display: flex;
987
+ align-items: flex-start;
988
+ gap: 10px;
989
+ padding: 12px;
990
+ background: rgba(156, 39, 176, 0.1);
991
+ border-radius: 12px;
992
+ border: 1px solid rgba(156, 39, 176, 0.15);
993
+ }
994
+
995
+ .dream-icon {
996
+ font-size: 1.3rem;
997
+ flex-shrink: 0;
998
+ }
999
+
1000
+ .dream-text {
1001
+ font-size: 0.85rem;
1002
+ color: var(--text-secondary);
1003
+ font-style: italic;
1004
+ line-height: 1.4;
1005
+ }
1006
+
1007
+ /* Curiosity Section */
1008
+ .curiosity-section {
1009
+ background: linear-gradient(135deg, rgba(74, 144, 217, 0.1), rgba(38, 166, 154, 0.05));
1010
+ border: 1px solid rgba(74, 144, 217, 0.2);
1011
+ }
1012
+
1013
+ .curiosity-grid {
1014
+ display: flex;
1015
+ flex-wrap: wrap;
1016
+ gap: 6px;
1017
+ }
1018
+
1019
+ .curiosity-chip {
1020
+ display: flex;
1021
+ align-items: center;
1022
+ gap: 6px;
1023
+ padding: 6px 10px;
1024
+ background: rgba(255, 255, 255, 0.05);
1025
+ border-radius: 10px;
1026
+ }
1027
+
1028
+ .curiosity-chip-name {
1029
+ font-size: 0.75rem;
1030
+ color: var(--text-secondary);
1031
+ }
1032
+
1033
+ .curiosity-chip-bar {
1034
+ width: 40px;
1035
+ height: 4px;
1036
+ background: rgba(255, 255, 255, 0.1);
1037
+ border-radius: 2px;
1038
+ overflow: hidden;
1039
+ }
1040
+
1041
+ .curiosity-chip-fill {
1042
+ height: 100%;
1043
+ border-radius: 2px;
1044
+ background: linear-gradient(90deg, #4a90d9, #26a69a);
1045
+ transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
1046
+ }
1047
+
1048
+ .curiosity-empty {
1049
+ text-align: center;
1050
+ color: var(--text-muted);
1051
+ padding: 10px;
1052
+ font-size: 0.8rem;
1053
+ }
1054
+
1055
+ /* Loading Skeleton */
1056
+ .skeleton {
1057
+ background: linear-gradient(90deg, var(--bg-card) 25%, var(--bg-elevated) 50%, var(--bg-card) 75%);
1058
+ background-size: 200% 100%;
1059
+ animation: shimmer 1.5s infinite;
1060
+ border-radius: 4px;
1061
+ }
1062
+
1063
+ @keyframes shimmer {
1064
+ 0% { background-position: 200% 0; }
1065
+ 100% { background-position: -200% 0; }
1066
+ }
1067
+ </style>
1068
+ </head>
1069
+ <body>
1070
+ <div class="connection-banner" id="connection-banner">Reconnecting...</div>
1071
+
1072
+ <div class="app">
1073
+ <header class="header">
1074
+ <div class="header-content">
1075
+ <div class="header-left">
1076
+ <div class="avatar-ring">
1077
+ <img src="/avatar" alt="Alive-AI" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><defs><linearGradient id=%22g%22 x1=%220%25%22 y1=%220%25%22 x2=%22100%25%22 y2=%22100%25%22><stop offset=%220%25%22 style=%22stop-color:%23ff6b9d%22/><stop offset=%22100%25%22 style=%22stop-color:%23c44569%22/></linearGradient></defs><circle cx=%2250%22 cy=%2240%22 r=%2225%22 fill=%22url(%23g)%22/><ellipse cx=%2250%22 cy=%2285%22 rx=%2235%22 ry=%2225%22 fill=%22url(%23g)%22/></svg>'">
1078
+ </div>
1079
+ <div class="header-info">
1080
+ <h1>Alive-AI</h1>
1081
+ <div class="header-status">
1082
+ <span class="status-dot"></span>
1083
+ <span>Active now</span>
1084
+ </div>
1085
+ </div>
1086
+ </div>
1087
+ <div class="mood-badge">
1088
+ <span class="mood-emoji" id="mood-emoji">😌</span>
1089
+ <span id="mood-text">neutral</span>
1090
+ </div>
1091
+ </div>
1092
+ </header>
1093
+
1094
+ <main class="main">
1095
+ <!-- Stats Row -->
1096
+ <div class="stats-row">
1097
+ <div class="stat-card">
1098
+ <div class="stat-value" id="stat-messages">0</div>
1099
+ <div class="stat-label">Messages</div>
1100
+ </div>
1101
+ <div class="stat-card">
1102
+ <div class="stat-value" id="stat-memories">0</div>
1103
+ <div class="stat-label">Memories</div>
1104
+ </div>
1105
+ <div class="stat-card">
1106
+ <div class="stat-value" id="stat-uptime">0m</div>
1107
+ <div class="stat-label">Uptime</div>
1108
+ </div>
1109
+ </div>
1110
+
1111
+ <!-- Emotions -->
1112
+ <div class="section">
1113
+ <div class="section-header">
1114
+ <div class="section-title">
1115
+ <span class="section-icon">💕</span>
1116
+ <span>Emotions</span>
1117
+ </div>
1118
+ </div>
1119
+ <div class="emotion-grid">
1120
+ <div class="emotion-row">
1121
+ <div class="emotion-icon arousal">⚡</div>
1122
+ <div class="emotion-content">
1123
+ <div class="emotion-label">
1124
+ <span class="emotion-name">Arousal</span>
1125
+ <span class="emotion-value" id="val-arousal">30%</span>
1126
+ </div>
1127
+ <div class="emotion-bar">
1128
+ <div class="emotion-fill arousal" id="bar-arousal" style="width: 30%"></div>
1129
+ </div>
1130
+ </div>
1131
+ </div>
1132
+ <div class="emotion-row">
1133
+ <div class="emotion-icon desire">🔥</div>
1134
+ <div class="emotion-content">
1135
+ <div class="emotion-label">
1136
+ <span class="emotion-name">Desire</span>
1137
+ <span class="emotion-value" id="val-desire">0%</span>
1138
+ </div>
1139
+ <div class="emotion-bar">
1140
+ <div class="emotion-fill desire" id="bar-desire" style="width: 0%"></div>
1141
+ </div>
1142
+ </div>
1143
+ </div>
1144
+ <div class="emotion-row">
1145
+ <div class="emotion-icon love">💖</div>
1146
+ <div class="emotion-content">
1147
+ <div class="emotion-label">
1148
+ <span class="emotion-name">Love</span>
1149
+ <span class="emotion-value" id="val-love">0%</span>
1150
+ </div>
1151
+ <div class="emotion-bar">
1152
+ <div class="emotion-fill love" id="bar-love" style="width: 0%"></div>
1153
+ </div>
1154
+ </div>
1155
+ </div>
1156
+ <div class="emotion-row">
1157
+ <div class="emotion-icon joy">✨</div>
1158
+ <div class="emotion-content">
1159
+ <div class="emotion-label">
1160
+ <span class="emotion-name">Joy</span>
1161
+ <span class="emotion-value" id="val-joy">0%</span>
1162
+ </div>
1163
+ <div class="emotion-bar">
1164
+ <div class="emotion-fill joy" id="bar-joy" style="width: 0%"></div>
1165
+ </div>
1166
+ </div>
1167
+ </div>
1168
+ <div class="emotion-row">
1169
+ <div class="emotion-icon sadness">💧</div>
1170
+ <div class="emotion-content">
1171
+ <div class="emotion-label">
1172
+ <span class="emotion-name">Sadness</span>
1173
+ <span class="emotion-value" id="val-sadness">0%</span>
1174
+ </div>
1175
+ <div class="emotion-bar">
1176
+ <div class="emotion-fill sadness" id="bar-sadness" style="width: 0%"></div>
1177
+ </div>
1178
+ </div>
1179
+ </div>
1180
+ <div class="emotion-row">
1181
+ <div class="emotion-icon trust">🤝</div>
1182
+ <div class="emotion-content">
1183
+ <div class="emotion-label">
1184
+ <span class="emotion-name">Trust</span>
1185
+ <span class="emotion-value" id="val-trust">50%</span>
1186
+ </div>
1187
+ <div class="emotion-bar">
1188
+ <div class="emotion-fill trust" id="bar-trust" style="width: 50%"></div>
1189
+ </div>
1190
+ </div>
1191
+ </div>
1192
+ <div class="emotion-row">
1193
+ <div class="emotion-icon fear">😱</div>
1194
+ <div class="emotion-content">
1195
+ <div class="emotion-label">
1196
+ <span class="emotion-name">Fear</span>
1197
+ <span class="emotion-value" id="val-fear">10%</span>
1198
+ </div>
1199
+ <div class="emotion-bar">
1200
+ <div class="emotion-fill fear" id="bar-fear" style="width: 10%"></div>
1201
+ </div>
1202
+ </div>
1203
+ </div>
1204
+ <div class="emotion-row">
1205
+ <div class="emotion-icon anger">😤</div>
1206
+ <div class="emotion-content">
1207
+ <div class="emotion-label">
1208
+ <span class="emotion-name">Anger</span>
1209
+ <span class="emotion-value" id="val-anger">0%</span>
1210
+ </div>
1211
+ <div class="emotion-bar">
1212
+ <div class="emotion-fill anger" id="bar-anger" style="width: 0%"></div>
1213
+ </div>
1214
+ </div>
1215
+ </div>
1216
+ <div class="emotion-row">
1217
+ <div class="emotion-icon boredom">😑</div>
1218
+ <div class="emotion-content">
1219
+ <div class="emotion-label">
1220
+ <span class="emotion-name">Boredom</span>
1221
+ <span class="emotion-value" id="val-boredom">0%</span>
1222
+ </div>
1223
+ <div class="emotion-bar">
1224
+ <div class="emotion-fill boredom" id="bar-boredom" style="width: 0%"></div>
1225
+ </div>
1226
+ </div>
1227
+ </div>
1228
+ <div class="emotion-row">
1229
+ <div class="emotion-icon guilt">😔</div>
1230
+ <div class="emotion-content">
1231
+ <div class="emotion-label">
1232
+ <span class="emotion-name">Guilt</span>
1233
+ <span class="emotion-value" id="val-guilt">0%</span>
1234
+ </div>
1235
+ <div class="emotion-bar">
1236
+ <div class="emotion-fill guilt" id="bar-guilt" style="width: 0%"></div>
1237
+ </div>
1238
+ </div>
1239
+ </div>
1240
+ <div class="emotion-row">
1241
+ <div class="emotion-icon pride">🏆</div>
1242
+ <div class="emotion-content">
1243
+ <div class="emotion-label">
1244
+ <span class="emotion-name">Pride</span>
1245
+ <span class="emotion-value" id="val-pride">0%</span>
1246
+ </div>
1247
+ <div class="emotion-bar">
1248
+ <div class="emotion-fill pride" id="bar-pride" style="width: 0%"></div>
1249
+ </div>
1250
+ </div>
1251
+ </div>
1252
+ <div class="emotion-row">
1253
+ <div class="emotion-icon jealousy">💚</div>
1254
+ <div class="emotion-content">
1255
+ <div class="emotion-label">
1256
+ <span class="emotion-name">Jealousy</span>
1257
+ <span class="emotion-value" id="val-jealousy">0%</span>
1258
+ </div>
1259
+ <div class="emotion-bar">
1260
+ <div class="emotion-fill jealousy" id="bar-jealousy" style="width: 0%"></div>
1261
+ </div>
1262
+ </div>
1263
+ </div>
1264
+ <div class="emotion-row">
1265
+ <div class="emotion-icon embarrassment">😳</div>
1266
+ <div class="emotion-content">
1267
+ <div class="emotion-label">
1268
+ <span class="emotion-name">Embarrassment</span>
1269
+ <span class="emotion-value" id="val-embarrassment">0%</span>
1270
+ </div>
1271
+ <div class="emotion-bar">
1272
+ <div class="emotion-fill embarrassment" id="bar-embarrassment" style="width: 0%"></div>
1273
+ </div>
1274
+ </div>
1275
+ </div>
1276
+ <div class="emotion-row">
1277
+ <div class="emotion-icon anticipation">🎯</div>
1278
+ <div class="emotion-content">
1279
+ <div class="emotion-label">
1280
+ <span class="emotion-name">Anticipation</span>
1281
+ <span class="emotion-value" id="val-anticipation">0%</span>
1282
+ </div>
1283
+ <div class="emotion-bar">
1284
+ <div class="emotion-fill anticipation" id="bar-anticipation" style="width: 0%"></div>
1285
+ </div>
1286
+ </div>
1287
+ </div>
1288
+ <div class="emotion-row">
1289
+ <div class="emotion-icon hope">🌟</div>
1290
+ <div class="emotion-content">
1291
+ <div class="emotion-label">
1292
+ <span class="emotion-name">Hope</span>
1293
+ <span class="emotion-value" id="val-hope">50%</span>
1294
+ </div>
1295
+ <div class="emotion-bar">
1296
+ <div class="emotion-fill hope" id="bar-hope" style="width: 50%"></div>
1297
+ </div>
1298
+ </div>
1299
+ </div>
1300
+ <div class="emotion-row">
1301
+ <div class="emotion-icon dread">💀</div>
1302
+ <div class="emotion-content">
1303
+ <div class="emotion-label">
1304
+ <span class="emotion-name">Dread</span>
1305
+ <span class="emotion-value" id="val-dread">10%</span>
1306
+ </div>
1307
+ <div class="emotion-bar">
1308
+ <div class="emotion-fill dread" id="bar-dread" style="width: 10%"></div>
1309
+ </div>
1310
+ </div>
1311
+ </div>
1312
+ </div>
1313
+ </div>
1314
+
1315
+ <!-- Internal State (Body) -->
1316
+ <div class="section internal-section">
1317
+ <div class="section-header">
1318
+ <div class="section-title">
1319
+ <span class="section-icon">🫀</span>
1320
+ <span>Internal State</span>
1321
+ </div>
1322
+ </div>
1323
+ <div class="internal-grid">
1324
+ <div class="internal-row">
1325
+ <div class="internal-icon energy">⚡</div>
1326
+ <div class="internal-content">
1327
+ <div class="internal-label">
1328
+ <span class="internal-name">Energy</span>
1329
+ <span class="internal-value" id="val-energy">70%</span>
1330
+ </div>
1331
+ <div class="internal-bar">
1332
+ <div class="internal-fill energy" id="bar-energy" style="width: 70%"></div>
1333
+ </div>
1334
+ </div>
1335
+ </div>
1336
+ <div class="internal-row">
1337
+ <div class="internal-icon social">👥</div>
1338
+ <div class="internal-content">
1339
+ <div class="internal-label">
1340
+ <span class="internal-name">Social Satiety</span>
1341
+ <span class="internal-value" id="val-social-satiety">50%</span>
1342
+ </div>
1343
+ <div class="internal-bar">
1344
+ <div class="internal-fill social" id="bar-social-satiety" style="width: 50%"></div>
1345
+ </div>
1346
+ </div>
1347
+ </div>
1348
+ <div class="internal-row">
1349
+ <div class="internal-icon valence">💫</div>
1350
+ <div class="internal-content">
1351
+ <div class="internal-label">
1352
+ <span class="internal-name">Emotional Valence</span>
1353
+ <span class="internal-value" id="val-emotional-valence">+50%</span>
1354
+ </div>
1355
+ <div class="internal-bar">
1356
+ <div class="internal-fill valence" id="bar-emotional-valence" style="width: 50%"></div>
1357
+ </div>
1358
+ </div>
1359
+ </div>
1360
+ <div class="internal-row">
1361
+ <div class="internal-icon certainty">🎯</div>
1362
+ <div class="internal-content">
1363
+ <div class="internal-label">
1364
+ <span class="internal-name">Certainty</span>
1365
+ <span class="internal-value" id="val-certainty">60%</span>
1366
+ </div>
1367
+ <div class="internal-bar">
1368
+ <div class="internal-fill certainty" id="bar-certainty" style="width: 60%"></div>
1369
+ </div>
1370
+ </div>
1371
+ </div>
1372
+ </div>
1373
+ <div class="bodily-feeling" id="bodily-feeling">
1374
+ <span class="feeling-icon">💭</span>
1375
+ <span class="feeling-text">feeling balanced and at ease</span>
1376
+ </div>
1377
+ </div>
1378
+
1379
+ <!-- Thoughts (Merged) -->
1380
+ <div class="section thoughts-section">
1381
+ <div class="section-header">
1382
+ <div class="section-title">
1383
+ <span class="section-icon">💭</span>
1384
+ <span>Thoughts</span>
1385
+ </div>
1386
+ </div>
1387
+
1388
+ <!-- Status Flags -->
1389
+ <div id="status-flags" style="text-align:center;font-size:0.8rem;color:#ff6b9d;font-weight:600;margin-bottom:8px;min-height:1.2em;"></div>
1390
+
1391
+ <!-- Current Thought -->
1392
+ <div class="current-thought">
1393
+ <span class="thought-icon">💭</span>
1394
+ <p class="thought-text" id="thought-text">
1395
+ <span class="empty">No active thoughts right now...</span>
1396
+ </p>
1397
+ </div>
1398
+
1399
+ <!-- Recent Thought History -->
1400
+ <div id="thought-history" style="font-size:0.7rem;color:#999;padding:4px 8px;max-height:120px;overflow-y:auto;"></div>
1401
+
1402
+ <!-- Idle Thoughts -->
1403
+ <div class="idle-thoughts" id="idle-thoughts">
1404
+ <div class="idle-empty">No recent background thoughts...</div>
1405
+ </div>
1406
+ </div>
1407
+
1408
+ <!-- Hormones -->
1409
+ <div class="section hormones-section">
1410
+ <div class="section-header">
1411
+ <div class="section-title">
1412
+ <span class="section-icon">🧪</span>
1413
+ <span>Hormones</span>
1414
+ </div>
1415
+ </div>
1416
+ <div class="hormone-grid">
1417
+ <div class="hormone-chip" id="hormone-oxytocin">
1418
+ <span class="hormone-icon">💕</span>
1419
+ <span class="hormone-name">Oxytocin</span>
1420
+ <span class="hormone-val" id="val-oxytocin">30%</span>
1421
+ </div>
1422
+ <div class="hormone-chip" id="hormone-dopamine">
1423
+ <span class="hormone-icon">🎯</span>
1424
+ <span class="hormone-name">Dopamine</span>
1425
+ <span class="hormone-val" id="val-dopamine">40%</span>
1426
+ </div>
1427
+ <div class="hormone-chip" id="hormone-cortisol">
1428
+ <span class="hormone-icon">⚡</span>
1429
+ <span class="hormone-name">Cortisol</span>
1430
+ <span class="hormone-val" id="val-cortisol">20%</span>
1431
+ </div>
1432
+ <div class="hormone-chip" id="hormone-serotonin">
1433
+ <span class="hormone-icon">✨</span>
1434
+ <span class="hormone-name">Serotonin</span>
1435
+ <span class="hormone-val" id="val-serotonin">50%</span>
1436
+ </div>
1437
+ <div class="hormone-chip" id="hormone-melatonin">
1438
+ <span class="hormone-icon">🌙</span>
1439
+ <span class="hormone-name">Melatonin</span>
1440
+ <span class="hormone-val" id="val-melatonin">30%</span>
1441
+ </div>
1442
+ </div>
1443
+ </div>
1444
+
1445
+ <!-- Circadian Rhythm -->
1446
+ <div class="section circadian-section" id="circadian-section" style="display:none;">
1447
+ <div class="section-header">
1448
+ <div class="section-title">
1449
+ <span class="section-icon">🌙</span>
1450
+ <span>Circadian Rhythm</span>
1451
+ </div>
1452
+ </div>
1453
+ <div class="circadian-phase">
1454
+ <span class="circadian-phase-name" id="circadian-phase">--</span>
1455
+ <span class="circadian-sleep-badge awake" id="circadian-sleep-badge">
1456
+ <span class="circadian-sleep-dot"></span>
1457
+ <span id="circadian-sleep-text">Awake</span>
1458
+ </span>
1459
+ </div>
1460
+ <div class="circadian-debt">
1461
+ <div class="circadian-debt-label">
1462
+ <span>Sleep Debt</span>
1463
+ <span id="circadian-debt-val">0%</span>
1464
+ </div>
1465
+ <div class="circadian-debt-bar">
1466
+ <div class="circadian-debt-fill" id="circadian-debt-bar" style="width:0%"></div>
1467
+ </div>
1468
+ </div>
1469
+ <div class="circadian-modifiers">
1470
+ <div class="circadian-mod-chip">
1471
+ <span class="circadian-mod-name">Energy</span>
1472
+ <span class="circadian-mod-val" id="circadian-mod-energy">0%</span>
1473
+ </div>
1474
+ <div class="circadian-mod-chip">
1475
+ <span class="circadian-mod-name">Inhibition</span>
1476
+ <span class="circadian-mod-val" id="circadian-mod-inhibition">0%</span>
1477
+ </div>
1478
+ <div class="circadian-mod-chip">
1479
+ <span class="circadian-mod-name">Warmth</span>
1480
+ <span class="circadian-mod-val" id="circadian-mod-warmth">0%</span>
1481
+ </div>
1482
+ <div class="circadian-mod-chip">
1483
+ <span class="circadian-mod-name">Verbosity</span>
1484
+ <span class="circadian-mod-val" id="circadian-mod-verbosity">0%</span>
1485
+ </div>
1486
+ </div>
1487
+ </div>
1488
+
1489
+ <!-- Attachment -->
1490
+ <div class="section attachment-section" id="attachment-section" style="display:none;">
1491
+ <div class="section-header">
1492
+ <div class="section-title">
1493
+ <span class="section-icon">🔗</span>
1494
+ <span>Attachment</span>
1495
+ </div>
1496
+ </div>
1497
+ <div>
1498
+ <span class="attachment-style-badge secure" id="attachment-style-badge">Secure</span>
1499
+ </div>
1500
+ <div class="attachment-security">
1501
+ <div class="attachment-security-label">
1502
+ <span>Security Score</span>
1503
+ <span id="attachment-security-val">0%</span>
1504
+ </div>
1505
+ <div class="attachment-security-bar">
1506
+ <div class="attachment-security-fill" id="attachment-security-bar" style="width:0%"></div>
1507
+ </div>
1508
+ </div>
1509
+ <div class="attachment-trend">
1510
+ <span class="attachment-trend-label">Trend</span>
1511
+ <span class="attachment-trend-arrow stable" id="attachment-trend-arrow">&rarr;</span>
1512
+ </div>
1513
+ </div>
1514
+
1515
+ <!-- Body Memory (Afterglow + Phantom) -->
1516
+ <div class="section body-memory-section" id="body-memory-section" style="display:none;">
1517
+ <div class="section-header">
1518
+ <div class="section-title">
1519
+ <span class="section-icon">✨</span>
1520
+ <span>Body Memory</span>
1521
+ </div>
1522
+ </div>
1523
+ <div id="body-memory-content">
1524
+ <div class="body-memory-empty">No active body memories</div>
1525
+ </div>
1526
+ </div>
1527
+
1528
+ <!-- Story (Narrative + Dreams) -->
1529
+ <div class="section story-section" id="story-section" style="display:none;">
1530
+ <div class="section-header">
1531
+ <div class="section-title">
1532
+ <span class="section-icon">📖</span>
1533
+ <span>Story</span>
1534
+ </div>
1535
+ </div>
1536
+ <div>
1537
+ <span class="story-phase-badge" id="story-phase-badge">--</span>
1538
+ </div>
1539
+ <div class="story-stats">
1540
+ <span class="story-stat"><strong id="story-msg-count">0</strong> messages</span>
1541
+ <span class="story-stat"><strong id="story-moments-count">0</strong> key moments</span>
1542
+ </div>
1543
+ <div id="story-dream-container" style="display:none;">
1544
+ <div class="dream-bubble">
1545
+ <span class="dream-icon">💭</span>
1546
+ <span class="dream-text" id="story-dream-text"></span>
1547
+ </div>
1548
+ </div>
1549
+ </div>
1550
+
1551
+ <!-- Curiosity -->
1552
+ <div class="section curiosity-section" id="curiosity-section" style="display:none;">
1553
+ <div class="section-header">
1554
+ <div class="section-title">
1555
+ <span class="section-icon">🔍</span>
1556
+ <span>Curiosity</span>
1557
+ </div>
1558
+ </div>
1559
+ <div class="curiosity-grid" id="curiosity-grid">
1560
+ <div class="curiosity-empty">No topics yet</div>
1561
+ </div>
1562
+ </div>
1563
+
1564
+ <!-- Conflicts -->
1565
+ <div class="section conflicts-section">
1566
+ <div class="section-header">
1567
+ <div class="section-title">
1568
+ <span class="section-icon">⚖️</span>
1569
+ <span>Conflicts</span>
1570
+ </div>
1571
+ <span class="conflict-count-badge" id="conflict-count">0</span>
1572
+ </div>
1573
+ <div class="conflicts-list" id="conflicts-list">
1574
+ <div class="conflict-empty">No active internal conflicts</div>
1575
+ </div>
1576
+ <div class="tendency-row">
1577
+ <span class="tendency-label">Response Tendency</span>
1578
+ <span class="tendency-badge" id="tendency-badge">neutral</span>
1579
+ </div>
1580
+ </div>
1581
+ </main>
1582
+
1583
+ <nav class="bottom-nav">
1584
+ <div class="nav-item active">
1585
+ <span class="nav-icon">🏠</span>
1586
+ <span class="nav-label">Home</span>
1587
+ </div>
1588
+ <div class="nav-item">
1589
+ <span class="nav-icon">💬</span>
1590
+ <span class="nav-label">Chat</span>
1591
+ </div>
1592
+ <div class="nav-item">
1593
+ <span class="nav-icon">⚙️</span>
1594
+ <span class="nav-label">Settings</span>
1595
+ </div>
1596
+ </nav>
1597
+ </div>
1598
+
1599
+ <script>
1600
+ const moodEmojis = {
1601
+ 'high_desire': '🔥', 'excited': '🤩', 'happy': '😊', 'in_love': '🥰',
1602
+ 'playful': '😏', 'flirty': '💋', 'sad': '😢', 'bored': '😐',
1603
+ 'angry': '😤', 'neutral': '😌', 'calm': '😌', 'loving': '💕'
1604
+ };
1605
+
1606
+ let eventSource;
1607
+ let startTime = null; // Will be set from server's start_time
1608
+ const banner = document.getElementById('connection-banner');
1609
+
1610
+ function connect() {
1611
+ eventSource = new EventSource('/events');
1612
+
1613
+ eventSource.onopen = () => banner.classList.remove('show');
1614
+
1615
+ eventSource.onerror = () => {
1616
+ banner.classList.add('show');
1617
+ eventSource.close();
1618
+ setTimeout(connect, 3000);
1619
+ };
1620
+
1621
+ eventSource.addEventListener('state', (e) => {
1622
+ try {
1623
+ updateUI(JSON.parse(e.data));
1624
+ } catch(err) {
1625
+ console.warn('[WebUI] SSE state update error:', err);
1626
+ }
1627
+ });
1628
+ }
1629
+
1630
+ function updateUI(data) {
1631
+ // Mood
1632
+ const mood = data.mood || 'neutral';
1633
+ document.getElementById('mood-emoji').textContent = moodEmojis[mood] || '😌';
1634
+ document.getElementById('mood-text').textContent = mood.replace(/_/g, ' ');
1635
+
1636
+ // Emotions - all 17 emotions
1637
+ ['arousal', 'desire', 'love', 'joy', 'sadness', 'trust', 'fear', 'anger', 'boredom', 'guilt', 'pride', 'jealousy', 'embarrassment', 'anticipation', 'hope', 'dread'].forEach(em => {
1638
+ const val = Math.round((data[em] || 0) * 100);
1639
+ const valEl = document.getElementById(`val-${em}`);
1640
+ const barEl = document.getElementById(`bar-${em}`);
1641
+ if (valEl) valEl.textContent = `${val}%`;
1642
+ if (barEl) barEl.style.width = `${val}%`;
1643
+ });
1644
+
1645
+ // Stats
1646
+ if (data.stats) {
1647
+ document.getElementById('stat-messages').textContent = data.stats.messages || 0;
1648
+ document.getElementById('stat-memories').textContent = data.stats.memories || 0;
1649
+ }
1650
+
1651
+ // Server start time (for accurate uptime)
1652
+ if (data.start_time && !startTime) {
1653
+ startTime = new Date(data.start_time).getTime();
1654
+ } else if (!startTime) {
1655
+ startTime = Date.now();
1656
+ }
1657
+
1658
+ // Current thought
1659
+ const thoughtEl = document.getElementById('thought-text');
1660
+ if (data.current_thought) {
1661
+ thoughtEl.innerHTML = `"${escapeHtml(data.current_thought)}"`;
1662
+ thoughtEl.classList.remove('empty');
1663
+ } else {
1664
+ thoughtEl.innerHTML = '<span class="empty">No active thoughts right now...</span>';
1665
+ thoughtEl.classList.add('empty');
1666
+ }
1667
+
1668
+ // Recent thoughts (subconscious impulses)
1669
+ if (data.recent_thoughts && data.recent_thoughts.length > 0) {
1670
+ const historyEl = document.getElementById('thought-history');
1671
+ if (historyEl) {
1672
+ historyEl.innerHTML = data.recent_thoughts.slice(-5).reverse().map(t =>
1673
+ `<div class="thought-entry"><span class="thought-time">${t.time || ''}</span> <span class="thought-type">${t.type || ''}</span> ${escapeHtml(t.thought || '')}</div>`
1674
+ ).join('');
1675
+ }
1676
+ }
1677
+
1678
+ // Status flags
1679
+ if (data.is_high_desire || data.is_in_love) {
1680
+ let flags = [];
1681
+ if (data.is_in_love) flags.push('In Love');
1682
+ if (data.is_high_desire) flags.push('High desire');
1683
+ const flagEl = document.getElementById('status-flags');
1684
+ if (flagEl) flagEl.textContent = flags.join(' + ');
1685
+ }
1686
+ }
1687
+
1688
+ // Update uptime
1689
+ function updateUptime() {
1690
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
1691
+ let display;
1692
+ if (elapsed < 60) {
1693
+ display = `${elapsed}s`;
1694
+ } else if (elapsed < 3600) {
1695
+ display = `${Math.floor(elapsed / 60)}m`;
1696
+ } else if (elapsed < 86400) {
1697
+ const hours = Math.floor(elapsed / 3600);
1698
+ const mins = Math.floor((elapsed % 3600) / 60);
1699
+ display = `${hours}h ${mins}m`;
1700
+ } else {
1701
+ const days = Math.floor(elapsed / 86400);
1702
+ const hours = Math.floor((elapsed % 86400) / 3600);
1703
+ display = `${days}d ${hours}h`;
1704
+ }
1705
+ document.getElementById('stat-uptime').textContent = display;
1706
+ }
1707
+
1708
+ // Soul Architecture update - only hormones now
1709
+ function updateSoulUI(soulData) {
1710
+ if (!soulData) return;
1711
+
1712
+ // Hormones
1713
+ const hormonal = soulData.hormonal || {};
1714
+ updateHormone('oxytocin', hormonal.oxytocin || 0.3);
1715
+ updateHormone('dopamine', hormonal.dopamine || 0.4);
1716
+ updateHormone('cortisol', hormonal.cortisol || 0.2);
1717
+ updateHormone('serotonin', hormonal.serotonin || 0.5);
1718
+ updateHormone('melatonin', hormonal.melatonin || 0.3);
1719
+
1720
+ // Note: Conflicts are now handled by updateInconsistencyUI() from the aliveness API
1721
+ }
1722
+
1723
+ function updateHormone(name, value) {
1724
+ const chip = document.getElementById(`hormone-${name}`);
1725
+ const valEl = document.getElementById(`val-${name}`);
1726
+ const pct = Math.round(value * 100);
1727
+ if (valEl) valEl.textContent = `${pct}%`;
1728
+ if (chip) {
1729
+ chip.classList.toggle('elevated', value > 0.6);
1730
+ }
1731
+ }
1732
+
1733
+ // Interoceptive states update
1734
+ function updateInteroceptiveUI(data) {
1735
+ if (!data || !data.states) return;
1736
+
1737
+ const states = data.states;
1738
+
1739
+ const stateMappings = {
1740
+ 'energy': { min: 0, max: 1 },
1741
+ 'social_satiety': { min: 0, max: 1 },
1742
+ 'emotional_valence': { min: -1, max: 1 },
1743
+ 'certainty': { min: 0, max: 1 }
1744
+ };
1745
+
1746
+ for (const [stateName, config] of Object.entries(stateMappings)) {
1747
+ const state = states[stateName];
1748
+ if (!state) continue;
1749
+
1750
+ const value = state.current_value;
1751
+ const valEl = document.getElementById(`val-${stateName.replace(/_/g, '-')}`);
1752
+ const barEl = document.getElementById(`bar-${stateName.replace(/_/g, '-')}`);
1753
+
1754
+ if (valEl && barEl) {
1755
+ if (config.min < 0) {
1756
+ const displayVal = value >= 0 ? `+${Math.round(value * 100)}%` : `${Math.round(value * 100)}%`;
1757
+ valEl.textContent = displayVal;
1758
+ const barWidth = ((value - config.min) / (config.max - config.min)) * 100;
1759
+ barEl.style.width = `${barWidth}%`;
1760
+ } else {
1761
+ valEl.textContent = `${Math.round(value * 100)}%`;
1762
+ barEl.style.width = `${value * 100}%`;
1763
+ }
1764
+ }
1765
+ }
1766
+
1767
+ // Update bodily feeling
1768
+ if (data.bodily_description) {
1769
+ const feelingEl = document.querySelector('.bodily-feeling .feeling-text');
1770
+ if (feelingEl) feelingEl.textContent = data.bodily_description;
1771
+ }
1772
+ }
1773
+
1774
+ // Idle thoughts update
1775
+ function updateIdleUI(data) {
1776
+ if (!data) return;
1777
+
1778
+ const thoughtsEl = document.getElementById('idle-thoughts');
1779
+ if (thoughtsEl && data.recent_thoughts && data.recent_thoughts.length > 0) {
1780
+ thoughtsEl.innerHTML = data.recent_thoughts.slice(0, 5).map(t => `
1781
+ <div class="idle-thought">
1782
+ <div class="thought-type">${t.thought_type || 'thought'}</div>
1783
+ ${escapeHtml(t.content || t.thought || '')}
1784
+ </div>
1785
+ `).join('');
1786
+ } else if (thoughtsEl) {
1787
+ thoughtsEl.innerHTML = '<div class="idle-empty">No recent background thoughts...</div>';
1788
+ }
1789
+ }
1790
+
1791
+ // Inconsistency/conflicts update
1792
+ function updateInconsistencyUI(data) {
1793
+ if (!data) return;
1794
+
1795
+ const conflictsEl = document.getElementById('conflicts-list');
1796
+ const badgeEl = document.getElementById('conflict-count');
1797
+
1798
+ // Support both old and new API formats
1799
+ const conflicts = data.active_conflicts || data.conflicts || [];
1800
+ const count = data.count || conflicts.length;
1801
+
1802
+ if (badgeEl) {
1803
+ badgeEl.textContent = count;
1804
+ }
1805
+
1806
+ if (conflictsEl && conflicts.length > 0) {
1807
+ conflictsEl.innerHTML = conflicts.slice(0, 5).map(c => {
1808
+ // Handle both old format (name/desire/fear) and new format (id/side_a/side_b)
1809
+ const name = c.name || c.id || 'Unknown';
1810
+ const desire = c.desire || c.side_a || '';
1811
+ const fear = c.fear || c.side_b || '';
1812
+ const tension = Math.round((c.tension_level || c.intensity || 0) * 100);
1813
+
1814
+ return `
1815
+ <div class="conflict-item">
1816
+ <div class="conflict-name">${escapeHtml(name.replace(/_/g, ' '))}</div>
1817
+ <div class="conflict-detail">${escapeHtml(desire)} <span style="color:var(--text-muted)">vs</span> ${escapeHtml(fear)}</div>
1818
+ <div class="conflict-tension" style="margin-top:4px;height:3px;background:rgba(255,255,255,0.1);border-radius:2px;overflow:hidden;">
1819
+ <div style="width:${tension}%;height:100%;background:linear-gradient(90deg,#e74c3c,#ff9800);border-radius:2px;"></div>
1820
+ </div>
1821
+ </div>
1822
+ `}).join('');
1823
+ } else if (conflictsEl) {
1824
+ conflictsEl.innerHTML = '<div class="conflict-empty">No active internal conflicts</div>';
1825
+ }
1826
+ }
1827
+
1828
+ // Fetch all data
1829
+ function fetchSoulState() {
1830
+ fetch('/api/soul')
1831
+ .then(r => r.json())
1832
+ .then(updateSoulUI)
1833
+ .catch(e => console.log('Soul fetch error:', e));
1834
+ }
1835
+
1836
+ function fetchInteroceptive() {
1837
+ fetch('/api/aliveness/interoceptive')
1838
+ .then(r => r.json())
1839
+ .then(updateInteroceptiveUI)
1840
+ .catch(e => console.log('Interoception fetch error:', e));
1841
+ }
1842
+
1843
+ function fetchIdleState() {
1844
+ fetch('/api/aliveness/idle')
1845
+ .then(r => r.json())
1846
+ .then(updateIdleUI)
1847
+ .catch(e => console.log('Idle fetch error:', e));
1848
+ }
1849
+
1850
+ function fetchInconsistency() {
1851
+ fetch('/api/aliveness/inconsistency')
1852
+ .then(r => r.json())
1853
+ .then(updateInconsistencyUI)
1854
+ .catch(e => console.log('Inconsistency fetch error:', e));
1855
+ }
1856
+
1857
+ function escapeHtml(text) {
1858
+ const div = document.createElement('div');
1859
+ div.textContent = text;
1860
+ return div.innerHTML;
1861
+ }
1862
+
1863
+ // Init
1864
+ fetch('/state').then(r => r.json()).then(updateUI).then(connect).catch(err => {
1865
+ console.warn('[WebUI] Initial fetch failed, retrying...', err);
1866
+ setTimeout(connect, 3000);
1867
+ });
1868
+ fetchSoulState();
1869
+ fetchInteroceptive();
1870
+ fetchIdleState();
1871
+ fetchInconsistency();
1872
+
1873
+ // Update intervals
1874
+ setInterval(updateUptime, 1000);
1875
+ setInterval(fetchSoulState, 5000);
1876
+ setInterval(fetchInteroceptive, 5000);
1877
+ setInterval(fetchIdleState, 5000);
1878
+ setInterval(fetchInconsistency, 5000);
1879
+
1880
+ // --- New Aliveness Systems ---
1881
+ const afterglowIcons = {
1882
+ 'intimate_afterglow': '💋',
1883
+ 'love_afterglow': '💕',
1884
+ 'fight_hangover': '💢',
1885
+ 'excitement_buzz': '⚡',
1886
+ 'vulnerability_rawness': '🫧'
1887
+ };
1888
+
1889
+ function fetchNewAliveness() {
1890
+ fetch('/api/aliveness/new')
1891
+ .then(r => r.json())
1892
+ .then(updateNewAlivenessUI)
1893
+ .catch(e => console.log('New aliveness fetch error:', e));
1894
+ }
1895
+
1896
+ function updateNewAlivenessUI(data) {
1897
+ if (!data) return;
1898
+ updateCircadianUI(data.circadian);
1899
+ updateAttachmentUI(data.attachment);
1900
+ updateBodyMemoryUI(data.afterglow, data.phantom_somatic);
1901
+ updateStoryUI(data.narrative, data.dreams);
1902
+ updateCuriosityUI(data.curiosity);
1903
+ }
1904
+
1905
+ function updateCircadianUI(c) {
1906
+ const sec = document.getElementById('circadian-section');
1907
+ if (!c) { sec.style.display = 'none'; return; }
1908
+ sec.style.display = '';
1909
+
1910
+ document.getElementById('circadian-phase').textContent = (c.phase || '--').replace(/_/g, ' ');
1911
+
1912
+ const badge = document.getElementById('circadian-sleep-badge');
1913
+ const sleeping = c.sleeping || c.is_sleeping || false;
1914
+ badge.className = 'circadian-sleep-badge ' + (sleeping ? 'sleeping' : 'awake');
1915
+ document.getElementById('circadian-sleep-text').textContent = sleeping ? 'Sleeping' : 'Awake';
1916
+
1917
+ const debt = Math.round((c.sleep_debt || 0) * 100);
1918
+ document.getElementById('circadian-debt-val').textContent = debt + '%';
1919
+ document.getElementById('circadian-debt-bar').style.width = debt + '%';
1920
+
1921
+ const mods = c.modifiers || {};
1922
+ ['energy', 'inhibition', 'warmth', 'verbosity'].forEach(m => {
1923
+ const el = document.getElementById('circadian-mod-' + m);
1924
+ if (el) el.textContent = Math.round((mods[m] || 0) * 100) + '%';
1925
+ });
1926
+ }
1927
+
1928
+ function updateAttachmentUI(a) {
1929
+ const sec = document.getElementById('attachment-section');
1930
+ if (!a) { sec.style.display = 'none'; return; }
1931
+ sec.style.display = '';
1932
+
1933
+ const style = (a.style || 'secure').toLowerCase();
1934
+ const badge = document.getElementById('attachment-style-badge');
1935
+ badge.textContent = style.charAt(0).toUpperCase() + style.slice(1);
1936
+ badge.className = 'attachment-style-badge ' + style;
1937
+
1938
+ // Fix: API returns 'security', not 'security_score'
1939
+ const score = Math.round((a.security || a.security_score || 0) * 100);
1940
+ document.getElementById('attachment-security-val').textContent = score + '%';
1941
+ document.getElementById('attachment-security-bar').style.width = score + '%';
1942
+
1943
+ const fill = document.getElementById('attachment-security-bar');
1944
+ if (score > 70) fill.style.background = '#4caf50';
1945
+ else if (score > 40) fill.style.background = '#ffc107';
1946
+ else if (score > 30) fill.style.background = '#ff9800';
1947
+ else fill.style.background = '#e74c3c';
1948
+
1949
+ const trend = a.trend || 'stable';
1950
+ const arrow = document.getElementById('attachment-trend-arrow');
1951
+ if (trend === 'improving') { arrow.innerHTML = '&uarr;'; arrow.className = 'attachment-trend-arrow improving'; }
1952
+ else if (trend === 'declining') { arrow.innerHTML = '&darr;'; arrow.className = 'attachment-trend-arrow declining'; }
1953
+ else { arrow.innerHTML = '&rarr;'; arrow.className = 'attachment-trend-arrow stable'; }
1954
+ }
1955
+
1956
+ function updateBodyMemoryUI(afterglow, phantom) {
1957
+ const sec = document.getElementById('body-memory-section');
1958
+ const container = document.getElementById('body-memory-content');
1959
+
1960
+ // Fix: API returns {active: [...], count: N}, not a direct array
1961
+ const afterglowList = afterglow && afterglow.active ? afterglow.active : (afterglow || []);
1962
+ const phantomList = phantom && phantom.active ? phantom.active : (phantom || []);
1963
+
1964
+ const hasAfterglow = afterglowList.length > 0;
1965
+ const hasPhantom = phantomList.length > 0;
1966
+
1967
+ if (!hasAfterglow && !hasPhantom) {
1968
+ sec.style.display = '';
1969
+ container.innerHTML = '<div class="body-memory-empty">No active body memories</div>';
1970
+ return;
1971
+ }
1972
+ sec.style.display = '';
1973
+ let html = '';
1974
+
1975
+ if (hasAfterglow) {
1976
+ html += '<div class="body-memory-list">';
1977
+ afterglowList.forEach(a => {
1978
+ const icon = afterglowIcons[a.type] || '✨';
1979
+ const intensity = Math.round((a.intensity || 0) * 100);
1980
+ const ago = a.hours_ago ? a.hours_ago.toFixed(1) + 'h ago' : '';
1981
+ html += `<div class="body-memory-item">
1982
+ <span class="body-memory-icon">${icon}</span>
1983
+ <div class="body-memory-content">
1984
+ <div class="body-memory-name">${escapeHtml((a.type || '').replace(/_/g, ' '))}</div>
1985
+ <div class="body-memory-bar"><div class="body-memory-fill" style="width:${intensity}%"></div></div>
1986
+ </div>
1987
+ <span class="body-memory-meta">${escapeHtml(ago)}</span>
1988
+ </div>`;
1989
+ });
1990
+ html += '</div>';
1991
+ }
1992
+
1993
+ if (hasPhantom) {
1994
+ html += '<div class="body-memory-sub-header">Phantom Sensations</div><div class="body-memory-list">';
1995
+ phantomList.forEach(p => {
1996
+ const intensity = Math.round((p.intensity || 0) * 100);
1997
+ html += `<div class="body-memory-item">
1998
+ <span class="body-memory-icon">👻</span>
1999
+ <div class="body-memory-content">
2000
+ <div class="body-memory-name">${escapeHtml((p.type || '').replace(/_/g, ' '))}</div>
2001
+ <div class="body-memory-bar"><div class="body-memory-fill" style="width:${intensity}%"></div></div>
2002
+ ${p.description ? '<div class="phantom-desc">' + escapeHtml(p.description) + '</div>' : ''}
2003
+ </div>
2004
+ </div>`;
2005
+ });
2006
+ html += '</div>';
2007
+ }
2008
+
2009
+ container.innerHTML = html;
2010
+ }
2011
+
2012
+ function updateStoryUI(narrative, dreams) {
2013
+ const sec = document.getElementById('story-section');
2014
+ if (!narrative) { sec.style.display = 'none'; return; }
2015
+ sec.style.display = '';
2016
+
2017
+ const phase = (narrative.phase || '--').replace(/_/g, ' ');
2018
+ document.getElementById('story-phase-badge').textContent = phase;
2019
+ document.getElementById('story-msg-count').textContent = narrative.message_count || 0;
2020
+ // Fix: API returns 'moments', not 'key_moments_count'
2021
+ document.getElementById('story-moments-count').textContent = narrative.moments || narrative.key_moments_count || 0;
2022
+
2023
+ const dreamContainer = document.getElementById('story-dream-container');
2024
+ if (dreams && dreams.last_dream) {
2025
+ dreamContainer.style.display = '';
2026
+ document.getElementById('story-dream-text').textContent = dreams.last_dream;
2027
+ } else {
2028
+ dreamContainer.style.display = 'none';
2029
+ }
2030
+ }
2031
+
2032
+ function updateCuriosityUI(curiosity) {
2033
+ const sec = document.getElementById('curiosity-section');
2034
+ const grid = document.getElementById('curiosity-grid');
2035
+
2036
+ // Fix: API returns topics as object {name: level}, not array
2037
+ if (!curiosity || !curiosity.topics) {
2038
+ sec.style.display = 'none';
2039
+ return;
2040
+ }
2041
+
2042
+ const topicsObj = curiosity.topics;
2043
+ const topicsArray = Object.entries(topicsObj).map(([name, level]) => ({name, level}));
2044
+
2045
+ if (topicsArray.length === 0) {
2046
+ sec.style.display = 'none';
2047
+ return;
2048
+ }
2049
+
2050
+ sec.style.display = '';
2051
+ // Sort by curiosity level (lowest knowledge = highest curiosity first)
2052
+ topicsArray.sort((a, b) => a.level - b.level);
2053
+
2054
+ grid.innerHTML = topicsArray.map(t => {
2055
+ const knowledge = Math.round((t.level || 0) * 100);
2056
+ const curiosityLevel = 100 - knowledge; // Invert: less knowledge = more curious
2057
+ const emoji = curiosityLevel > 80 ? '🤔' : curiosityLevel > 50 ? '💭' : '✓';
2058
+ return `<div class="curiosity-chip">
2059
+ <span class="curiosity-chip-name">${emoji} ${escapeHtml(t.name.replace(/_/g, ' '))}</span>
2060
+ <div class="curiosity-chip-bar"><div class="curiosity-chip-fill" style="width:${curiosityLevel}%"></div></div>
2061
+ </div>`;
2062
+ }).join('');
2063
+ }
2064
+
2065
+ // Init + polling for new aliveness
2066
+ fetchNewAliveness();
2067
+ setInterval(fetchNewAliveness, 5000);
2068
+ </script>
2069
+ </body>
2070
+ </html>