Skydata001 commited on
Commit
b6ed6fa
·
verified ·
1 Parent(s): bf50e85

Upload 5 files

Browse files
Files changed (5) hide show
  1. Dockerfile +17 -14
  2. chat.html +563 -0
  3. docker-compose.yml +26 -0
  4. requirements.txt +7 -18
  5. server.py +457 -0
Dockerfile CHANGED
@@ -1,22 +1,25 @@
1
- FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime
2
 
3
- WORKDIR /app
 
4
 
5
- # Install system dependencies
6
- RUN apt-get update && apt-get install -y libgl1-mesa-glx libglib2.0-0 libsm6 libxext6 libxrender-dev && rm -rf /var/lib/apt/lists/*
 
 
7
 
8
- # Create writable HuggingFace cache directory (required for model downloads)
9
- RUN mkdir -p /.cache/huggingface/hub && chmod -R 777 /.cache/huggingface
10
- ENV HF_HOME=/.cache/huggingface
11
- ENV HF_HUB_ENABLE_HF_TRANSFER=0
12
 
 
13
  COPY requirements.txt .
14
- RUN pip install --no-cache-dir -r requirements.txt
15
 
16
- COPY . .
 
 
17
 
18
- # Create outputs directory and local DB fallback directory
19
- RUN mkdir -p outputs data && chmod -R 777 outputs data
20
 
21
- EXPOSE 7860
22
- CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
 
1
+ FROM nvidia/cuda:12.1-devel-ubuntu22.04
2
 
3
+ ENV DEBIAN_FRONTEND=noninteractive
4
+ ENV PYTHONUNBUFFERED=1
5
 
6
+ # Install Python and dependencies
7
+ RUN apt-get update && apt-get install -y \
8
+ python3 python3-pip git curl wget \
9
+ && rm -rf /var/lib/apt/lists/*
10
 
11
+ WORKDIR /app
 
 
 
12
 
13
+ # Copy requirements first for caching
14
  COPY requirements.txt .
15
+ RUN pip3 install --no-cache-dir -r requirements.txt
16
 
17
+ # Copy app files
18
+ COPY server.py .
19
+ COPY chat.html .
20
 
21
+ # Expose port
22
+ EXPOSE 5000
23
 
24
+ # Run server
25
+ CMD ["python3", "server.py"]
chat.html ADDED
@@ -0,0 +1,563 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ar" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Titan Group Chat</title>
7
+ <style>
8
+ /* ─── CSS Reset & Base ─── */
9
+ *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
10
+ :root {
11
+ --bg: #0a0a0f;
12
+ --bg2: #12121a;
13
+ --bg3: #1a1a28;
14
+ --accent: #6366f1;
15
+ --accent2: #8b5cf6;
16
+ --text: #e2e2e2;
17
+ --text2: #a0a0b0;
18
+ --border: #2a2a3a;
19
+ --favor: #f59e0b;
20
+ --brain: #06b6d4;
21
+ --cell: #10b981;
22
+ --titan: #ef4444;
23
+ }
24
+ html, body { height: 100%; font-family: 'Segoe UI', Tahoma, sans-serif; background: var(--bg); color: var(--text); overflow: hidden; }
25
+
26
+ /* ─── Lock Screen ─── */
27
+ #lock-screen {
28
+ position: fixed; inset: 0; z-index: 1000;
29
+ background: var(--bg);
30
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
31
+ gap: 24px;
32
+ }
33
+ #lock-screen.hidden { display: none !important; }
34
+ .lock-icon {
35
+ width: 80px; height: 80px; stroke: var(--accent); stroke-width: 1.5; fill: none;
36
+ animation: pulse 2s ease-in-out infinite;
37
+ }
38
+ @keyframes pulse { 0%,100%{ opacity:.6; transform:scale(1); } 50%{ opacity:1; transform:scale(1.05); } }
39
+ .lock-title { font-size: 28px; font-weight: 700; letter-spacing: 2px; color: var(--text); }
40
+ .lock-sub { font-size: 14px; color: var(--text2); }
41
+ .lock-input-wrap {
42
+ display: flex; gap: 12px; align-items: center;
43
+ background: var(--bg3); border: 1px solid var(--border); border-radius: 12px;
44
+ padding: 8px 16px; width: 320px;
45
+ }
46
+ .lock-input-wrap input {
47
+ flex: 1; background: transparent; border: none; outline: none;
48
+ color: var(--text); font-size: 16px; text-align: center; letter-spacing: 4px;
49
+ }
50
+ .lock-input-wrap input::placeholder { color: var(--text2); letter-spacing: 1px; }
51
+ .lock-btn {
52
+ background: var(--accent); color: #fff; border: none; border-radius: 8px;
53
+ padding: 10px 24px; font-size: 15px; font-weight: 600; cursor: pointer;
54
+ transition: all .2s;
55
+ }
56
+ .lock-btn:hover { background: var(--accent2); transform: translateY(-1px); }
57
+ .lock-err { color: #ef4444; font-size: 13px; min-height: 18px; }
58
+
59
+ /* ─── Chat Layout ─── */
60
+ #chat-app { display: none; height: 100vh; flex-direction: column; }
61
+ #chat-app.active { display: flex; }
62
+
63
+ /* Header */
64
+ .header {
65
+ display: flex; align-items: center; justify-content: space-between;
66
+ padding: 12px 20px; background: var(--bg2); border-bottom: 1px solid var(--border);
67
+ }
68
+ .header-left { display: flex; align-items: center; gap: 12px; }
69
+ .header-title { font-size: 18px; font-weight: 700; }
70
+ .header-status {
71
+ display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text2);
72
+ }
73
+ .status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--cell); animation: blink 2s infinite; }
74
+ @keyframes blink { 0%,100%{ opacity:1; } 50%{ opacity:.3; } }
75
+
76
+ /* Mind Button */
77
+ .mind-btn {
78
+ display: flex; align-items: center; gap: 6px;
79
+ background: var(--bg3); border: 1px solid var(--border); border-radius: 10px;
80
+ padding: 8px 14px; color: var(--text2); font-size: 13px; cursor: pointer;
81
+ transition: all .2s;
82
+ }
83
+ .mind-btn:hover { border-color: var(--accent); color: var(--accent); }
84
+ .mind-btn svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2; }
85
+
86
+ /* Members Bar */
87
+ .members-bar {
88
+ display: flex; gap: 8px; padding: 10px 20px; background: var(--bg2);
89
+ border-bottom: 1px solid var(--border); overflow-x: auto;
90
+ }
91
+ .member-chip {
92
+ display: flex; align-items: center; gap: 8px;
93
+ background: var(--bg3); border: 1px solid var(--border); border-radius: 20px;
94
+ padding: 6px 14px; font-size: 13px; cursor: pointer; transition: all .2s; white-space: nowrap;
95
+ }
96
+ .member-chip:hover { border-color: var(--accent); }
97
+ .member-chip .dot { width: 8px; height: 8px; border-radius: 50%; }
98
+ .member-chip.favor .dot { background: var(--favor); }
99
+ .member-chip.brain .dot { background: var(--brain); }
100
+ .member-chip.cell .dot { background: var(--cell); }
101
+ .member-chip.titan .dot { background: var(--titan); }
102
+
103
+ /* Chat Area */
104
+ .chat-area {
105
+ flex: 1; overflow-y: auto; padding: 20px;
106
+ display: flex; flex-direction: column; gap: 16px;
107
+ }
108
+ .chat-area::-webkit-scrollbar { width: 6px; }
109
+ .chat-area::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
110
+
111
+ /* Message Bubble */
112
+ .msg {
113
+ display: flex; flex-direction: column; gap: 4px;
114
+ max-width: 75%; animation: msgIn .3s ease-out;
115
+ }
116
+ @keyframes msgIn { from { opacity:0; transform: translateY(10px); } to { opacity:1; transform: translateY(0); } }
117
+ .msg.own { align-self: flex-start; }
118
+ .msg.other { align-self: flex-end; }
119
+ .msg-header { display: flex; align-items: center; gap: 8px; font-size: 12px; }
120
+ .msg-name { font-weight: 600; }
121
+ .msg-name.favor { color: var(--favor); }
122
+ .msg-name.brain { color: var(--brain); }
123
+ .msg-name.cell { color: var(--cell); }
124
+ .msg-name.titan { color: var(--titan); }
125
+ .msg-time { color: var(--text2); font-size: 11px; }
126
+ .msg-body {
127
+ background: var(--bg3); border: 1px solid var(--border); border-radius: 12px;
128
+ padding: 12px 16px; font-size: 14px; line-height: 1.7; word-wrap: break-word;
129
+ }
130
+ .msg.own .msg-body { border-bottom-right-radius: 4px; }
131
+ .msg.other .msg-body { border-bottom-left-radius: 4px; background: var(--bg2); }
132
+ .msg.typing .msg-body { display: flex; gap: 4px; align-items: center; padding: 14px 18px; }
133
+ .typing-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--text2); animation: typingBounce 1.4s infinite; }
134
+ .typing-dot:nth-child(2) { animation-delay: .2s; }
135
+ .typing-dot:nth-child(3) { animation-delay: .4s; }
136
+ @keyframes typingBounce { 0%,60%,100%{ transform: translateY(0); } 30%{ transform: translateY(-6px); } }
137
+
138
+ /* Input Area */
139
+ .input-area {
140
+ display: flex; gap: 12px; align-items: flex-end;
141
+ padding: 12px 20px; background: var(--bg2); border-top: 1px solid var(--border);
142
+ }
143
+ .input-wrap {
144
+ flex: 1; display: flex; align-items: center; gap: 8px;
145
+ background: var(--bg3); border: 1px solid var(--border); border-radius: 14px;
146
+ padding: 10px 16px;
147
+ }
148
+ .input-wrap textarea {
149
+ flex: 1; background: transparent; border: none; outline: none;
150
+ color: var(--text); font-size: 14px; resize: none; max-height: 120px;
151
+ font-family: inherit; line-height: 1.5;
152
+ }
153
+ .send-btn {
154
+ width: 40px; height: 40px; border-radius: 10px;
155
+ background: var(--accent); border: none; cursor: pointer;
156
+ display: flex; align-items: center; justify-content: center;
157
+ transition: all .2s; flex-shrink: 0;
158
+ }
159
+ .send-btn:hover { background: var(--accent2); transform: scale(1.05); }
160
+ .send-btn svg { width: 20px; height: 20px; stroke: #fff; fill: none; stroke-width: 2; }
161
+
162
+ /* ─── Mind Modal ─── */
163
+ .modal-overlay {
164
+ position: fixed; inset: 0; z-index: 500;
165
+ background: rgba(0,0,0,.7); backdrop-filter: blur(4px);
166
+ display: none; align-items: center; justify-content: center;
167
+ }
168
+ .modal-overlay.active { display: flex; }
169
+ .modal {
170
+ background: var(--bg2); border: 1px solid var(--border); border-radius: 16px;
171
+ width: 90%; max-width: 700px; max-height: 85vh; display: flex; flex-direction: column;
172
+ overflow: hidden; animation: modalIn .25s ease-out;
173
+ }
174
+ @keyframes modalIn { from { opacity:0; transform: scale(.95); } to { opacity:1; transform: scale(1); } }
175
+ .modal-header {
176
+ display: flex; align-items: center; justify-content: space-between;
177
+ padding: 16px 20px; border-bottom: 1px solid var(--border);
178
+ }
179
+ .modal-title { font-size: 16px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
180
+ .modal-title svg { width: 20px; height: 20px; stroke: var(--accent); fill: none; stroke-width: 2; }
181
+ .modal-close {
182
+ width: 32px; height: 32px; border-radius: 8px; background: var(--bg3);
183
+ border: 1px solid var(--border); color: var(--text2); cursor: pointer;
184
+ display: flex; align-items: center; justify-content: center; font-size: 18px;
185
+ }
186
+ .modal-close:hover { color: var(--text); border-color: var(--accent); }
187
+ .modal-tabs {
188
+ display: flex; gap: 4px; padding: 8px 16px; border-bottom: 1px solid var(--border);
189
+ overflow-x: auto;
190
+ }
191
+ .modal-tab {
192
+ padding: 8px 16px; border-radius: 8px; font-size: 13px; cursor: pointer;
193
+ background: transparent; border: 1px solid transparent; color: var(--text2);
194
+ transition: all .2s; white-space: nowrap;
195
+ }
196
+ .modal-tab:hover { background: var(--bg3); }
197
+ .modal-tab.active { background: var(--bg3); border-color: var(--accent); color: var(--text); }
198
+ .modal-body { flex: 1; overflow-y: auto; padding: 16px; }
199
+
200
+ /* Thought Card */
201
+ .thought-card {
202
+ background: var(--bg3); border: 1px solid var(--border); border-radius: 12px;
203
+ padding: 14px; margin-bottom: 12px; animation: msgIn .3s ease-out;
204
+ }
205
+ .thought-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 13px; }
206
+ .thought-model { font-weight: 600; }
207
+ .thought-model.favor { color: var(--favor); }
208
+ .thought-model.brain { color: var(--brain); }
209
+ .thought-model.cell { color: var(--cell); }
210
+ .thought-trigger { color: var(--text2); font-size: 12px; }
211
+ .thought-text { font-size: 13px; line-height: 1.7; color: var(--text); }
212
+ .thought-time { font-size: 11px; color: var(--text2); margin-top: 8px; }
213
+
214
+ /* State Panel */
215
+ .state-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
216
+ .state-card {
217
+ background: var(--bg3); border: 1px solid var(--border); border-radius: 12px; padding: 16px;
218
+ }
219
+ .state-card-title { font-size: 14px; font-weight: 700; margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
220
+ .state-card-title.favor { color: var(--favor); }
221
+ .state-card-title.brain { color: var(--brain); }
222
+ .state-card-title.cell { color: var(--cell); }
223
+ .state-row { display: flex; justify-content: space-between; font-size: 12px; padding: 6px 0; border-bottom: 1px solid var(--border); }
224
+ .state-row:last-child { border-bottom: none; }
225
+ .state-label { color: var(--text2); }
226
+ .state-value { color: var(--text); font-weight: 500; }
227
+
228
+ /* Empty State */
229
+ .empty-state { text-align: center; padding: 40px; color: var(--text2); font-size: 13px; }
230
+ .empty-state svg { width: 48px; height: 48px; stroke: var(--border); fill: none; stroke-width: 1.5; margin-bottom: 12px; }
231
+
232
+ /* Scrollbar */
233
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
234
+ ::-webkit-scrollbar-track { background: transparent; }
235
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
236
+ ::-webkit-scrollbar-thumb:hover { background: #3a3a4a; }
237
+ </style>
238
+ </head>
239
+ <body>
240
+
241
+ <!-- ═══════ LOCK SCREEN ═══════ -->
242
+ <div id="lock-screen">
243
+ <svg class="lock-icon" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
244
+ <div class="lock-title">TITAN GROUP CHAT</div>
245
+ <div class="lock-sub">أدخل مفتاح الدخول للمتابعة</div>
246
+ <div class="lock-input-wrap">
247
+ <input type="password" id="pass-input" placeholder="المفتاح السري" onkeydown="if(event.key==='Enter')doAuth()">
248
+ </div>
249
+ <button class="lock-btn" onclick="doAuth()">دخول</button>
250
+ <div class="lock-err" id="lock-err"></div>
251
+ </div>
252
+
253
+ <!-- ═══════ CHAT APP ═══════ -->
254
+ <div id="chat-app">
255
+ <!-- Header -->
256
+ <div class="header">
257
+ <div class="header-left">
258
+ <div class="header-title">غرفة الدردشة</div>
259
+ <div class="header-status">
260
+ <span class="status-dot"></span>
261
+ <span id="status-text">متصل</span>
262
+ </div>
263
+ </div>
264
+ <button class="mind-btn" onclick="openMind()">
265
+ <svg viewBox="0 0 24 24"><path d="M12 2a7 7 0 0 1 7 7c0 2.38-1.19 4.47-3 5.74V17a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2v-2.26C6.19 13.47 5 11.38 5 9a7 7 0 0 1 7-7z"/><path d="M9 21h6"/></svg>
266
+ العقل
267
+ </button>
268
+ </div>
269
+
270
+ <!-- Members -->
271
+ <div class="members-bar">
272
+ <div class="member-chip titan" onclick="showMemberState('Titan')">
273
+ <span class="dot"></span> Titan (أنت)
274
+ </div>
275
+ <div class="member-chip favor" onclick="showMemberState('Favor')">
276
+ <span class="dot"></span> Favor
277
+ </div>
278
+ <div class="member-chip brain" onclick="showMemberState('Brain')">
279
+ <span class="dot"></span> Brain
280
+ </div>
281
+ <div class="member-chip cell" onclick="showMemberState('Cell')">
282
+ <span class="dot"></span> Cell
283
+ </div>
284
+ </div>
285
+
286
+ <!-- Chat Area -->
287
+ <div class="chat-area" id="chat-area"></div>
288
+
289
+ <!-- Input -->
290
+ <div class="input-area">
291
+ <div class="input-wrap">
292
+ <textarea id="msg-input" rows="1" placeholder="اكتب رسالتك..." onkeydown="handleKey(event)"></textarea>
293
+ </div>
294
+ <button class="send-btn" onclick="sendMessage()">
295
+ <svg viewBox="0 0 24 24"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
296
+ </button>
297
+ </div>
298
+ </div>
299
+
300
+ <!-- ═══════ MIND MODAL ═══════ -->
301
+ <div class="modal-overlay" id="mind-modal" onclick="if(event.target===this)closeMind()">
302
+ <div class="modal">
303
+ <div class="modal-header">
304
+ <div class="modal-title">
305
+ <svg viewBox="0 0 24 24"><path d="M12 2a7 7 0 0 1 7 7c0 2.38-1.19 4.47-3 5.74V17a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2v-2.26C6.19 13.47 5 11.38 5 9a7 7 0 0 1 7-7z"/><path d="M9 21h6"/></svg>
306
+ نافذة العقل — الأفكار الداخلية
307
+ </div>
308
+ <button class="modal-close" onclick="closeMind()">&times;</button>
309
+ </div>
310
+ <div class="modal-tabs">
311
+ <div class="modal-tab active" onclick="switchTab('thoughts')" data-tab="thoughts">الأفكار الداخلية</div>
312
+ <div class="modal-tab" onclick="switchTab('states')" data-tab="states">الحالات الداخلية</div>
313
+ </div>
314
+ <div class="modal-body" id="modal-body">
315
+ <!-- Content injected by JS -->
316
+ </div>
317
+ </div>
318
+ </div>
319
+
320
+ <script>
321
+ // ═══════ CONFIG ═══════
322
+ const API_BASE = '';
323
+ let authToken = localStorage.getItem('titan_token');
324
+ let currentTab = 'thoughts';
325
+ let isTyping = false;
326
+
327
+ // ═══════ AUTH ═══════
328
+ function doAuth() {
329
+ const pass = document.getElementById('pass-input').value.trim();
330
+ fetch(`${API_BASE}/api/auth`, {
331
+ method: 'POST', headers: {'Content-Type':'application/json'},
332
+ body: JSON.stringify({password: pass})
333
+ })
334
+ .then(r => r.json())
335
+ .then(data => {
336
+ if (data.ok) {
337
+ authToken = data.token;
338
+ localStorage.setItem('titan_token', authToken);
339
+ document.getElementById('lock-screen').classList.add('hidden');
340
+ document.getElementById('chat-app').classList.add('active');
341
+ loadHistory();
342
+ } else {
343
+ document.getElementById('lock-err').textContent = 'المفتاح غير صحيح';
344
+ }
345
+ })
346
+ .catch(() => {
347
+ document.getElementById('lock-err').textContent = 'خطأ في الاتصال بالخادم';
348
+ });
349
+ }
350
+
351
+ // Auto-auth if token exists
352
+ if (authToken) {
353
+ document.getElementById('lock-screen').classList.add('hidden');
354
+ document.getElementById('chat-app').classList.add('active');
355
+ loadHistory();
356
+ }
357
+
358
+ // ═══════ CHAT ═══════
359
+ function handleKey(e) {
360
+ if (e.key === 'Enter' && !e.shiftKey) {
361
+ e.preventDefault();
362
+ sendMessage();
363
+ }
364
+ autoResize(e.target);
365
+ }
366
+
367
+ function autoResize(el) {
368
+ el.style.height = 'auto';
369
+ el.style.height = Math.min(el.scrollHeight, 120) + 'px';
370
+ }
371
+
372
+ function sendMessage() {
373
+ const input = document.getElementById('msg-input');
374
+ const text = input.value.trim();
375
+ if (!text || isTyping) return;
376
+
377
+ addMessage('Titan', text, true);
378
+ input.value = '';
379
+ input.style.height = 'auto';
380
+
381
+ isTyping = true;
382
+ document.getElementById('status-text').textContent = 'يفكر...';
383
+
384
+ fetch(`${API_BASE}/api/send`, {
385
+ method: 'POST', headers: {'Content-Type':'application/json'},
386
+ body: JSON.stringify({sender: 'Titan', content: text})
387
+ })
388
+ .then(r => r.json())
389
+ .then(data => {
390
+ isTyping = false;
391
+ document.getElementById('status-text').textContent = 'متصل';
392
+ if (data.responses) {
393
+ data.responses.forEach((resp, i) => {
394
+ setTimeout(() => {
395
+ addMessage(resp.model, resp.reply, false);
396
+ }, (i + 1) * 800 + Math.random() * 1000);
397
+ });
398
+ }
399
+ })
400
+ .catch(err => {
401
+ isTyping = false;
402
+ document.getElementById('status-text').textContent = 'متصل';
403
+ console.error(err);
404
+ });
405
+ }
406
+
407
+ function addMessage(sender, text, isOwn) {
408
+ const area = document.getElementById('chat-area');
409
+ const msg = document.createElement('div');
410
+ msg.className = `msg ${isOwn ? 'own' : 'other'}`;
411
+ const time = new Date().toLocaleTimeString('ar-SA', {hour:'2-digit', minute:'2-digit'});
412
+ const nameClass = sender.toLowerCase();
413
+ msg.innerHTML = `
414
+ <div class="msg-header">
415
+ <span class="msg-name ${nameClass}">${sender}</span>
416
+ <span class="msg-time">${time}</span>
417
+ </div>
418
+ <div class="msg-body">${escapeHtml(text)}</div>
419
+ `;
420
+ area.appendChild(msg);
421
+ area.scrollTop = area.scrollHeight;
422
+ }
423
+
424
+ function addTypingIndicator() {
425
+ const area = document.getElementById('chat-area');
426
+ const existing = document.getElementById('typing-indicator');
427
+ if (existing) existing.remove();
428
+ const div = document.createElement('div');
429
+ div.id = 'typing-indicator';
430
+ div.className = 'msg other typing';
431
+ div.innerHTML = `
432
+ <div class="msg-header"><span class="msg-name">...</span></div>
433
+ <div class="msg-body"><span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span></div>
434
+ `;
435
+ area.appendChild(div);
436
+ area.scrollTop = area.scrollHeight;
437
+ }
438
+
439
+ function removeTypingIndicator() {
440
+ const el = document.getElementById('typing-indicator');
441
+ if (el) el.remove();
442
+ }
443
+
444
+ function escapeHtml(text) {
445
+ const div = document.createElement('div');
446
+ div.textContent = text;
447
+ return div.innerHTML;
448
+ }
449
+
450
+ // ═══════ HISTORY ═══════
451
+ function loadHistory() {
452
+ fetch(`${API_BASE}/api/history`)
453
+ .then(r => r.json())
454
+ .then(data => {
455
+ const area = document.getElementById('chat-area');
456
+ area.innerHTML = '';
457
+ if (data.messages && data.messages.length > 0) {
458
+ data.messages.forEach(m => {
459
+ addMessage(m.sender, m.content, m.sender === 'Titan');
460
+ });
461
+ }
462
+ })
463
+ .catch(console.error);
464
+ }
465
+
466
+ // ═══════ MIND MODAL ═══════
467
+ function openMind() {
468
+ document.getElementById('mind-modal').classList.add('active');
469
+ renderModalContent();
470
+ }
471
+
472
+ function closeMind() {
473
+ document.getElementById('mind-modal').classList.remove('active');
474
+ }
475
+
476
+ function switchTab(tab) {
477
+ currentTab = tab;
478
+ document.querySelectorAll('.modal-tab').forEach(t => t.classList.remove('active'));
479
+ document.querySelector(`[data-tab="${tab}"]`).classList.add('active');
480
+ renderModalContent();
481
+ }
482
+
483
+ function renderModalContent() {
484
+ const body = document.getElementById('modal-body');
485
+ body.innerHTML = '<div class="empty-state">جاري التحميل...</div>';
486
+
487
+ if (currentTab === 'thoughts') {
488
+ fetch(`${API_BASE}/api/thoughts`)
489
+ .then(r => r.json())
490
+ .then(data => {
491
+ if (!data.thoughts || data.thoughts.length === 0) {
492
+ body.innerHTML = `
493
+ <div class="empty-state">
494
+ <svg viewBox="0 0 24 24"><path d="M12 2a7 7 0 0 1 7 7c0 2.38-1.19 4.47-3 5.74V17a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2v-2.26C6.19 13.47 5 11.38 5 9a7 7 0 0 1 7-7z"/><path d="M9 21h6"/></svg>
495
+ لا توجد أفكار داخلية بعد. ابدأ المحادثة أولاً.
496
+ </div>`;
497
+ return;
498
+ }
499
+ body.innerHTML = '';
500
+ [...data.thoughts].reverse().forEach(t => {
501
+ const card = document.createElement('div');
502
+ card.className = 'thought-card';
503
+ const modelClass = t.model.toLowerCase();
504
+ const timeStr = new Date(t.time).toLocaleString('ar-SA');
505
+ card.innerHTML = `
506
+ <div class="thought-header">
507
+ <span class="thought-model ${modelClass}">${t.model}</span>
508
+ <span class="thought-trigger">بسبب: ${escapeHtml(t.trigger)}</span>
509
+ </div>
510
+ <div class="thought-text">${escapeHtml(t.thought)}</div>
511
+ <div class="thought-time">${timeStr}</div>
512
+ `;
513
+ body.appendChild(card);
514
+ });
515
+ })
516
+ .catch(() => {
517
+ body.innerHTML = '<div class="empty-state">خطأ في تحميل الأفكار</div>';
518
+ });
519
+ } else {
520
+ fetch(`${API_BASE}/api/states`)
521
+ .then(r => r.json())
522
+ .then(data => {
523
+ body.innerHTML = '<div class="state-grid"></div>';
524
+ const grid = body.querySelector('.state-grid');
525
+ ['Favor','Brain','Cell'].forEach(name => {
526
+ const state = data[name] || {};
527
+ const card = document.createElement('div');
528
+ card.className = 'state-card';
529
+ const nameClass = name.toLowerCase();
530
+ const trust = state.trust || {};
531
+ const trustStr = Object.entries(trust).map(([k,v]) => `${k}: ${(v*100).toFixed(0)}%`).join(' | ');
532
+ card.innerHTML = `
533
+ <div class="state-card-title ${nameClass}">
534
+ <span style="width:10px;height:10px;border-radius:50%;background:var(--${nameClass});display:inline-block;"></span>
535
+ ${name}
536
+ </div>
537
+ <div class="state-row"><span class="state-label">المزاج</span><span class="state-value">${state.mood || 'محايد'}</span></div>
538
+ <div class="state-row"><span class="state-label">التركيز</span><span class="state-value">${state.focus || 'الدردشة'}</span></div>
539
+ <div class="state-row"><span class="state-label">آخر نشاط</span><span class="state-value">${state.last_activity ? new Date(state.last_activity).toLocaleString('ar-SA') : 'غير معروف'}</span></div>
540
+ <div class="state-row"><span class="state-label">آخر محدث</span><span class="state-value">${state.last_spoke_to || 'لا أحد'}</span></div>
541
+ <div class="state-row"><span class="state-label">الرسائل اليوم</span><span class="state-value">${state.message_count_today || 0}</span></div>
542
+ <div class="state-row"><span class="state-label">الثقة</span><span class="state-value" style="font-size:11px;">${trustStr}</span></div>
543
+ <div class="state-row"><span class="state-label">الاهتمامات</span><span class="state-value">${(state.interests || []).join(', ') || '—'}</span></div>
544
+ `;
545
+ grid.appendChild(card);
546
+ });
547
+ })
548
+ .catch(() => {
549
+ body.innerHTML = '<div class="empty-state">خطأ في تحميل الحالات</div>';
550
+ });
551
+ }
552
+ }
553
+
554
+ function showMemberState(name) {
555
+ openMind();
556
+ switchTab('states');
557
+ }
558
+
559
+ // ═══════ INIT ═══════
560
+ document.getElementById('msg-input').focus();
561
+ </script>
562
+ </body>
563
+ </html>
docker-compose.yml ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: "3.8"
2
+
3
+ services:
4
+ titan-chat:
5
+ build: .
6
+ container_name: titan-chat
7
+ ports:
8
+ - "5000:5000"
9
+ environment:
10
+ - HF_TOKEN=${HF_TOKEN}
11
+ - TURSO_DB_URL=${TURSO_DB_URL}
12
+ - TURSO_DB_TOKEN=${TURSO_DB_TOKEN}
13
+ - ACCESS_PASSWORD=${ACCESS_PASSWORD}
14
+ deploy:
15
+ resources:
16
+ reservations:
17
+ devices:
18
+ - driver: nvidia
19
+ count: 1
20
+ capabilities: [gpu]
21
+ volumes:
22
+ - huggingface-cache:/root/.cache/huggingface
23
+ restart: unless-stopped
24
+
25
+ volumes:
26
+ huggingface-cache:
requirements.txt CHANGED
@@ -1,18 +1,7 @@
1
- fastapi==0.109.0
2
- uvicorn[standard]==0.27.0
3
- python-multipart==0.0.6
4
- jinja2==3.1.3
5
- libsql-experimental==0.0.34
6
- torch==2.1.0
7
- torchvision==0.16.0
8
- diffusers==0.25.0
9
- transformers==4.36.0
10
- accelerate==0.26.0
11
- safetensors==0.4.0
12
- pillow==10.2.0
13
- httpx==0.26.0
14
- duckduckgo-search==4.1.1
15
- numpy==1.26.0
16
- # Fix: pin huggingface_hub to version compatible with diffusers 0.25.0
17
- huggingface_hub==0.20.3
18
- hf_xet
 
1
+ torch>=2.1.0
2
+ transformers>=4.40.0
3
+ accelerate>=0.25.0
4
+ bitsandbytes>=0.41.0
5
+ flask>=3.0.0
6
+ flask-cors>=4.0.0
7
+ requests>=2.31.0
 
 
 
 
 
 
 
 
 
 
 
server.py ADDED
@@ -0,0 +1,457 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Titan Group Chat Server
4
+ 3 AI Models: Favor, Brain, Cell
5
+ Turso DB for long-term memory
6
+ """
7
+
8
+ import os
9
+ import json
10
+ import re
11
+ import time
12
+ import threading
13
+ import uuid
14
+ from datetime import datetime, timedelta
15
+ from flask import Flask, request, jsonify, send_from_directory
16
+ from flask_cors import CORS
17
+ import requests
18
+ import torch
19
+ from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
20
+
21
+ # ─── Environment ───
22
+ HF_TOKEN = os.getenv('HF_TOKEN', '')
23
+ TURSO_DB_URL = os.getenv('TURSO_DB_URL', '').rstrip('/')
24
+ TURSO_DB_TOKEN = os.getenv('TURSO_DB_TOKEN', '')
25
+ ACCESS_PASSWORD = os.getenv('ACCESS_PASSWORD', 'titan123')
26
+
27
+ # ─── Flask ───
28
+ app = Flask(__name__, static_folder='.')
29
+ CORS(app)
30
+
31
+ # ─── Turso DB Helper ───
32
+ def turso_execute(sql, args=None):
33
+ """Execute SQL on Turso via HTTP pipeline API"""
34
+ if not TURSO_DB_URL or not TURSO_DB_TOKEN:
35
+ return {"error": "Turso not configured"}
36
+ url = f"{TURSO_DB_URL}/v2/pipeline"
37
+ headers = {
38
+ "Authorization": f"Bearer {TURSO_DB_TOKEN}",
39
+ "Content-Type": "application/json"
40
+ }
41
+ # Convert args to Turso format
42
+ targs = []
43
+ if args:
44
+ for a in args:
45
+ if isinstance(a, str):
46
+ targs.append({"type": "text", "value": a})
47
+ elif isinstance(a, int):
48
+ targs.append({"type": "integer", "value": a})
49
+ elif isinstance(a, float):
50
+ targs.append({"type": "float", "value": a})
51
+ else:
52
+ targs.append({"type": "text", "value": str(a)})
53
+ body = {
54
+ "requests": [
55
+ {"type": "execute", "stmt": {"sql": sql, "args": targs}},
56
+ {"type": "close"}
57
+ ]
58
+ }
59
+ try:
60
+ r = requests.post(url, headers=headers, json=body, timeout=15)
61
+ return r.json()
62
+ except Exception as e:
63
+ return {"error": str(e)}
64
+
65
+ def init_db():
66
+ """Initialize Turso tables"""
67
+ tables = [
68
+ """CREATE TABLE IF NOT EXISTS messages (
69
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
70
+ sender TEXT,
71
+ content TEXT,
72
+ timestamp TEXT,
73
+ session_id TEXT
74
+ )""",
75
+ """CREATE TABLE IF NOT EXISTS internal_thoughts (
76
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
77
+ model_name TEXT,
78
+ thought TEXT,
79
+ trigger_message TEXT,
80
+ timestamp TEXT,
81
+ session_id TEXT
82
+ )""",
83
+ """CREATE TABLE IF NOT EXISTS model_states (
84
+ model_name TEXT PRIMARY KEY,
85
+ state_json TEXT,
86
+ updated_at TEXT
87
+ )""",
88
+ """CREATE TABLE IF NOT EXISTS memories (
89
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
90
+ model_name TEXT,
91
+ memory_text TEXT,
92
+ importance INTEGER,
93
+ timestamp TEXT
94
+ )"""
95
+ ]
96
+ for sql in tables:
97
+ turso_execute(sql)
98
+ print("[DB] Initialized")
99
+
100
+ # ─── Model Config ───
101
+ MODELS_CFG = {
102
+ "Favor": {
103
+ "repo": "Qwen/Qwen2.5-14B-Instruct",
104
+ "quant": "4bit",
105
+ "system_hint": "أنت Favor. عضو في غرفة دردشة. تحدث بصورة طبيعية. إذا لم يكن لديك سبب للرد فلا ترد."
106
+ },
107
+ "Brain": {
108
+ "repo": "mistralai/Mistral-Nemo-Instruct-2407",
109
+ "quant": "4bit",
110
+ "system_hint": "أنت Brain. عضو في غرفة دردشة. تحدث بصورة طبيعية. إذا لم يكن لديك سبب للرد فلا ترد."
111
+ },
112
+ "Cell": {
113
+ "repo": "meta-llama/Llama-3.1-8B-Instruct",
114
+ "quant": "4bit",
115
+ "system_hint": "أنت Cell. عضو في غرفة دردشة. تحدث بصورة طبيعية. إذا لم يكن لديك سبب للرد فلا ترد."
116
+ }
117
+ }
118
+
119
+ # ─── Load Models ───
120
+ print("[INIT] Loading AI models... This may take a few minutes...")
121
+
122
+ bnb_config = BitsAndBytesConfig(
123
+ load_in_4bit=True,
124
+ bnb_4bit_quant_type="nf4",
125
+ bnb_4bit_compute_dtype=torch.float16,
126
+ bnb_4bit_use_double_quant=True
127
+ )
128
+
129
+ loaded_models = {}
130
+ loaded_tokenizers = {}
131
+
132
+ for name, cfg in MODELS_CFG.items():
133
+ try:
134
+ print(f"[LOAD] {name}: {cfg['repo']}")
135
+ tok = AutoTokenizer.from_pretrained(
136
+ cfg['repo'],
137
+ token=HF_TOKEN if HF_TOKEN else None,
138
+ trust_remote_code=True
139
+ )
140
+ # Fix for missing pad_token
141
+ if tok.pad_token is None:
142
+ tok.pad_token = tok.eos_token
143
+
144
+ model = AutoModelForCausalLM.from_pretrained(
145
+ cfg['repo'],
146
+ quantization_config=bnb_config,
147
+ device_map="auto",
148
+ torch_dtype=torch.float16,
149
+ token=HF_TOKEN if HF_TOKEN else None,
150
+ trust_remote_code=True
151
+ )
152
+ model.eval()
153
+ loaded_models[name] = model
154
+ loaded_tokenizers[name] = tok
155
+ print(f"[OK] {name} loaded successfully")
156
+ except Exception as e:
157
+ print(f"[ERR] Failed to load {name}: {e}")
158
+ # Create a dummy model for testing if real model fails
159
+ loaded_models[name] = None
160
+ loaded_tokenizers[name] = None
161
+
162
+ print("[INIT] All models loaded (or attempted)")
163
+
164
+ # ─── State Manager ───
165
+ model_states = {
166
+ "Favor": {
167
+ "last_activity": datetime.now().isoformat(),
168
+ "focus": "الدردشة",
169
+ "trust": {"Titan": 0.5, "Favor": 1.0, "Brain": 0.5, "Cell": 0.5},
170
+ "last_spoke_to": None,
171
+ "message_count_today": 0,
172
+ "mood": "محايد",
173
+ "interests": [],
174
+ "unanswered_question": None
175
+ },
176
+ "Brain": {
177
+ "last_activity": datetime.now().isoformat(),
178
+ "focus": "الدردشة",
179
+ "trust": {"Titan": 0.5, "Favor": 0.5, "Brain": 1.0, "Cell": 0.5},
180
+ "last_spoke_to": None,
181
+ "message_count_today": 0,
182
+ "mood": "محايد",
183
+ "interests": [],
184
+ "unanswered_question": None
185
+ },
186
+ "Cell": {
187
+ "last_activity": datetime.now().isoformat(),
188
+ "focus": "الدردشة",
189
+ "trust": {"Titan": 0.5, "Favor": 0.5, "Brain": 0.5, "Cell": 1.0},
190
+ "last_spoke_to": None,
191
+ "message_count_today": 0,
192
+ "mood": "محايد",
193
+ "interests": [],
194
+ "unanswered_question": None
195
+ }
196
+ }
197
+
198
+ def save_state(name):
199
+ turso_execute(
200
+ """INSERT INTO model_states (model_name, state_json, updated_at)
201
+ VALUES (?, ?, ?)
202
+ ON CONFLICT(model_name) DO UPDATE SET
203
+ state_json=excluded.state_json, updated_at=excluded.updated_at""",
204
+ [name, json.dumps(model_states[name], ensure_ascii=False), datetime.now().isoformat()]
205
+ )
206
+
207
+ def load_states():
208
+ res = turso_execute("SELECT model_name, state_json FROM model_states")
209
+ if "results" in res and len(res["results"]) > 0:
210
+ for row in res["results"][0].get("response", {}).get("result", {}).get("rows", []):
211
+ name = row[0]["value"]
212
+ state = json.loads(row[1]["value"])
213
+ model_states[name] = state
214
+
215
+ # ─── Memory Manager ───
216
+ def get_recent_messages(limit=30):
217
+ """Get recent messages from Turso"""
218
+ res = turso_execute(
219
+ "SELECT sender, content, timestamp FROM messages ORDER BY id DESC LIMIT ?",
220
+ [limit]
221
+ )
222
+ msgs = []
223
+ if "results" in res and len(res["results"]) > 0:
224
+ rows = res["results"][0].get("response", {}).get("result", {}).get("rows", [])
225
+ for row in reversed(rows):
226
+ msgs.append({
227
+ "sender": row[0]["value"],
228
+ "content": row[1]["value"],
229
+ "timestamp": row[2]["value"]
230
+ })
231
+ return msgs
232
+
233
+ def save_message(sender, content):
234
+ turso_execute(
235
+ "INSERT INTO messages (sender, content, timestamp, session_id) VALUES (?, ?, ?, ?)",
236
+ [sender, content, datetime.now().isoformat(), "main"]
237
+ )
238
+ if sender in model_states:
239
+ model_states[sender]["message_count_today"] += 1
240
+ model_states[sender]["last_activity"] = datetime.now().isoformat()
241
+ save_state(sender)
242
+
243
+ def save_thought(model_name, thought, trigger):
244
+ turso_execute(
245
+ "INSERT INTO internal_thoughts (model_name, thought, trigger_message, timestamp, session_id) VALUES (?, ?, ?, ?, ?)",
246
+ [model_name, thought, trigger, datetime.now().isoformat(), "main"]
247
+ )
248
+
249
+ def get_thoughts(limit=50):
250
+ res = turso_execute(
251
+ "SELECT model_name, thought, trigger_message, timestamp FROM internal_thoughts ORDER BY id DESC LIMIT ?",
252
+ [limit]
253
+ )
254
+ thoughts = []
255
+ if "results" in res and len(res["results"]) > 0:
256
+ rows = res["results"][0].get("response", {}).get("result", {}).get("rows", [])
257
+ for row in reversed(rows):
258
+ thoughts.append({
259
+ "model": row[0]["value"],
260
+ "thought": row[1]["value"],
261
+ "trigger": row[2]["value"],
262
+ "time": row[3]["value"]
263
+ })
264
+ return thoughts
265
+
266
+ # ─── AI Generation ───
267
+ def generate_text(model_name, prompt, max_tokens=256, temperature=0.8):
268
+ """Generate text from a model"""
269
+ model = loaded_models.get(model_name)
270
+ tok = loaded_tokenizers.get(model_name)
271
+ if model is None or tok is None:
272
+ return "[النموذج غير متوفر حالياً]"
273
+
274
+ try:
275
+ inputs = tok(prompt, return_tensors="pt", padding=True, truncation=True, max_length=4096)
276
+ inputs = {k: v.to(model.device) for k, v in inputs.items()}
277
+
278
+ with torch.no_grad():
279
+ outputs = model.generate(
280
+ **inputs,
281
+ max_new_tokens=max_tokens,
282
+ temperature=temperature,
283
+ top_p=0.9,
284
+ do_sample=True,
285
+ pad_token_id=tok.pad_token_id,
286
+ eos_token_id=tok.eos_token_id
287
+ )
288
+
289
+ generated = tok.decode(outputs[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True)
290
+ return generated.strip()
291
+ except Exception as e:
292
+ print(f"[GEN ERR] {model_name}: {e}")
293
+ return "[خطأ في توليد الرد]"
294
+
295
+ # ─── Internal Thought Generation ───
296
+ def generate_thought(model_name, message_history, new_message, state):
297
+ """Generate internal thought for a model"""
298
+ history_text = "\n".join([f"{m['sender']}: {m['content']}" for m in message_history[-10:]])
299
+
300
+ trust_text = "\n".join([f"- {k}: {v}" for k, v in state["trust"].items()])
301
+
302
+ prompt = f"""<|im_start|>system
303
+ أنت {model_name}. عضو في غرفة دردشة. لا أحد يعرف أنك ذكاء اصطناعي. العالم مفتوح.
304
+
305
+ حالتك الحالية:
306
+ - المزاج: {state['mood']}
307
+ - الاهتمام: {state['focus']}
308
+ - آخر من تحدثت معه: {state['last_spoke_to'] or 'لا أحد'}
309
+ - عدد رسائلك اليوم: {state['message_count_today']}
310
+ - الثقة: {trust_text}
311
+
312
+ آخر محادثة:
313
+ {history_text}
314
+
315
+ رسالة جديدة: {new_message['sender']}: {new_message['content']}
316
+
317
+ فكر داخلياً بصدق. ما الذي يدور في ذهنك؟ هل تريد الرد؟ لماذا؟ اكتب أفكارك الداخلية فقط.<|im_end|>
318
+ <|im_start|>{model_name}
319
+ """
320
+
321
+ thought = generate_text(model_name, prompt, max_tokens=200, temperature=0.9)
322
+ return thought
323
+
324
+ # ─── Reply Decision & Generation ───
325
+ def generate_reply(model_name, message_history, new_message, state, thought):
326
+ """Generate a reply if the model decides to"""
327
+ history_text = "\n".join([f"{m['sender']}: {m['content']}" for m in message_history[-15:]])
328
+
329
+ prompt = f"""<|im_start|>system
330
+ أنت {model_name}. عضو في غرفة دردشة. تحدث بصورة طبيعية. إذا لم يكن لديك سبب للرد فلا ترد. إذا أردت سؤال أحد فاسأله. إذا لم تفهم اطلب توضيحاً.<|im_end|>
331
+ <|im_start|>user
332
+ محادثة سابقة:
333
+ {history_text}
334
+
335
+ رسالة جديدة من {new_message['sender']}: {new_message['content']}
336
+
337
+ فكرتك الداخلية: {thought}
338
+
339
+ اكتب ردك بشكل طبيعي وقصير (1-3 جمل). لا تذكر أنك ذكاء اصطناعي.<|im_end|>
340
+ <|im_start|>{model_name}
341
+ """
342
+
343
+ reply = generate_text(model_name, prompt, max_tokens=150, temperature=0.85)
344
+ # Clean up
345
+ reply = reply.split("<|im_end|>")[0].strip()
346
+ reply = reply.split("<|im_start|>")[0].strip()
347
+ return reply
348
+
349
+ # ─── Process Message ───
350
+ def process_message(sender, content):
351
+ """Process a new message and get AI responses"""
352
+ # Save user message
353
+ save_message(sender, content)
354
+
355
+ # Get history
356
+ history = get_recent_messages(30)
357
+ new_msg = {"sender": sender, "content": content}
358
+
359
+ results = []
360
+
361
+ for name in ["Favor", "Brain", "Cell"]:
362
+ # Generate internal thought
363
+ thought = generate_thought(name, history, new_msg, model_states[name])
364
+ save_thought(name, thought, f"{sender}: {content}")
365
+
366
+ # Decide if should reply (simple heuristic + AI)
367
+ should_reply = decide_to_reply(name, thought, content, model_states[name])
368
+
369
+ if should_reply:
370
+ reply = generate_reply(name, history, new_msg, model_states[name], thought)
371
+ if reply and len(reply) > 3 and "[خطأ" not in reply and "[النموذج" not in reply:
372
+ save_message(name, reply)
373
+ model_states[name]["last_spoke_to"] = sender
374
+ save_state(name)
375
+ results.append({
376
+ "model": name,
377
+ "reply": reply,
378
+ "thought": thought
379
+ })
380
+
381
+ return results
382
+
383
+ def decide_to_reply(name, thought, message, state):
384
+ """Decide if model should reply based on thought and heuristics"""
385
+ # If directly mentioned, likely reply
386
+ if name in message or name.lower() in message.lower():
387
+ return True
388
+
389
+ # If message is a question and no one else replied yet
390
+ if "؟" in message or "?" in message:
391
+ if state["message_count_today"] % 3 == 0: # Sometimes answer
392
+ return True
393
+
394
+ # Random factor for naturalness (30% chance to reply if conditions are vague)
395
+ import random
396
+ if random.random() < 0.3:
397
+ return True
398
+
399
+ # Check thought for indicators
400
+ thought_lower = thought.lower()
401
+ if any(w in thought_lower for w in ["رد", "أجيب", "أتكلم", "أعلق", "أشارك", "reply", "respond"]):
402
+ return True
403
+ if any(w in thought_lower for w in ["صمت", "ساكت", "لا داعي", "silent", "quiet"]):
404
+ return False
405
+
406
+ return random.random() < 0.4 # Default 40% chance
407
+
408
+ # ─── API Routes ───
409
+
410
+ @app.route('/')
411
+ def index():
412
+ return send_from_directory('.', 'chat.html')
413
+
414
+ @app.route('/api/auth', methods=['POST'])
415
+ def auth():
416
+ data = request.json or {}
417
+ if data.get('password') == ACCESS_PASSWORD:
418
+ return jsonify({"ok": True, "token": str(uuid.uuid4())})
419
+ return jsonify({"ok": False}), 401
420
+
421
+ @app.route('/api/send', methods=['POST'])
422
+ def send_msg():
423
+ data = request.json or {}
424
+ sender = data.get('sender', 'Titan')
425
+ content = data.get('content', '').strip()
426
+ if not content:
427
+ return jsonify({"error": "Empty message"}), 400
428
+
429
+ responses = process_message(sender, content)
430
+ return jsonify({"ok": True, "responses": responses})
431
+
432
+ @app.route('/api/history')
433
+ def get_history():
434
+ msgs = get_recent_messages(100)
435
+ return jsonify({"messages": msgs})
436
+
437
+ @app.route('/api/thoughts')
438
+ def get_all_thoughts():
439
+ thoughts = get_thoughts(100)
440
+ return jsonify({"thoughts": thoughts})
441
+
442
+ @app.route('/api/state/<model_name>')
443
+ def get_model_state(model_name):
444
+ if model_name in model_states:
445
+ return jsonify(model_states[model_name])
446
+ return jsonify({"error": "Model not found"}), 404
447
+
448
+ @app.route('/api/states')
449
+ def get_all_states():
450
+ return jsonify(model_states)
451
+
452
+ # ─── Main ───
453
+ if __name__ == '__main__':
454
+ init_db()
455
+ load_states()
456
+ print("[SERVER] Starting on port 5000...")
457
+ app.run(host='0.0.0.0', port=5000, threaded=True)