TheRealSpamton commited on
Commit
6f5c451
·
verified ·
1 Parent(s): 3a13d70

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. app.html +568 -0
  2. app.py +9 -207
  3. requirements.txt +1 -4
app.html ADDED
@@ -0,0 +1,568 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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">
6
+ <title>HuggingGPT</title>
7
+ <style>
8
+ *{margin:0;padding:0;box-sizing:border-box;}
9
+ :root{
10
+ --bg:#111;--sidebar:#0a0a0a;--card:#1a1a1a;--border:#2a2a2a;
11
+ --accent:#10a37f;--accent2:#1a7f5a;--text:#ececec;--muted:#888;
12
+ --input-bg:#1e1e1e;--bubble-user:#10a37f;--bubble-ai:#1e1e1e;
13
+ }
14
+ body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--text);height:100vh;display:flex;overflow:hidden;}
15
+
16
+ /* ONBOARDING */
17
+ #onboarding{position:fixed;inset:0;background:rgba(0,0,0,0.95);display:flex;align-items:center;justify-content:center;z-index:100;}
18
+ #onboarding-card{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:40px;width:440px;max-width:90vw;}
19
+ #onboarding-card h1{font-size:1.6rem;margin-bottom:6px;}
20
+ #onboarding-card p{color:var(--muted);font-size:0.9rem;margin-bottom:28px;}
21
+ .field{margin-bottom:16px;}
22
+ .field label{display:block;font-size:0.8rem;color:var(--muted);margin-bottom:6px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;}
23
+ .field input,.field select,.field textarea{width:100%;background:var(--input-bg);border:1px solid var(--border);border-radius:8px;padding:10px 14px;color:var(--text);font-size:0.95rem;outline:none;transition:border 0.2s;}
24
+ .field input:focus,.field select:focus,.field textarea:focus{border-color:var(--accent);}
25
+ .field select option{background:var(--card);}
26
+ .btn{background:var(--accent);color:#fff;border:none;border-radius:8px;padding:12px 24px;font-size:0.95rem;font-weight:600;cursor:pointer;width:100%;transition:background 0.2s;}
27
+ .btn:hover{background:var(--accent2);}
28
+ .btn-ghost{background:transparent;border:1px solid var(--border);color:var(--text);}
29
+ .btn-ghost:hover{background:var(--card);}
30
+
31
+ /* SIDEBAR */
32
+ #sidebar{width:260px;min-width:260px;background:var(--sidebar);border-right:1px solid var(--border);display:flex;flex-direction:column;padding:16px;gap:8px;}
33
+ #sidebar-title{font-weight:700;font-size:1.1rem;color:var(--accent);padding:8px 4px;}
34
+ .new-chat-btn{background:var(--card);border:1px solid var(--border);color:var(--text);border-radius:8px;padding:10px 14px;font-size:0.9rem;cursor:pointer;text-align:left;transition:background 0.2s;}
35
+ .new-chat-btn:hover{background:#222;}
36
+ .section-label{font-size:0.7rem;text-transform:uppercase;letter-spacing:0.08em;color:var(--muted);font-weight:700;padding:12px 4px 4px;}
37
+ .mode-btn{background:transparent;border:1px solid transparent;color:var(--muted);border-radius:6px;padding:8px 12px;font-size:0.85rem;cursor:pointer;text-align:left;transition:all 0.15s;width:100%;}
38
+ .mode-btn:hover{background:var(--card);color:var(--text);}
39
+ .mode-btn.active{background:var(--card);border-color:var(--border);color:var(--text);}
40
+ #custom-model-input{display:none;margin-top:4px;}
41
+ #custom-model-input input{width:100%;background:var(--input-bg);border:1px solid var(--border);border-radius:6px;padding:8px 10px;color:var(--text);font-size:0.82rem;outline:none;}
42
+ .sidebar-spacer{flex:1;}
43
+ .tts-toggle{display:flex;align-items:center;gap:10px;padding:10px 4px;border-top:1px solid var(--border);}
44
+ .tts-toggle label{font-size:0.85rem;color:var(--muted);flex:1;}
45
+ .toggle{position:relative;width:40px;height:22px;}
46
+ .toggle input{opacity:0;width:0;height:0;}
47
+ .slider{position:absolute;inset:0;background:#333;border-radius:22px;cursor:pointer;transition:background 0.2s;}
48
+ .slider:before{content:'';position:absolute;height:16px;width:16px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:transform 0.2s;}
49
+ .toggle input:checked + .slider{background:var(--accent);}
50
+ .toggle input:checked + .slider:before{transform:translateX(18px);}
51
+ #user-profile{display:flex;align-items:center;gap:10px;padding:10px 4px;border-top:1px solid var(--border);}
52
+ .avatar{width:32px;height:32px;border-radius:8px;background:var(--accent);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;flex-shrink:0;}
53
+ .profile-info{flex:1;min-width:0;}
54
+ .profile-name{font-size:0.88rem;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
55
+ .profile-sub{font-size:0.74rem;color:var(--muted);}
56
+
57
+ /* MAIN */
58
+ #main{flex:1;display:flex;flex-direction:column;overflow:hidden;}
59
+ #chat-area{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:20px;scroll-behavior:smooth;}
60
+ #chat-area::-webkit-scrollbar{width:6px;}
61
+ #chat-area::-webkit-scrollbar-track{background:transparent;}
62
+ #chat-area::-webkit-scrollbar-thumb{background:#333;border-radius:3px;}
63
+
64
+ /* GREETING */
65
+ #greeting{text-align:center;margin:auto;padding:40px 20px;}
66
+ #greeting h1{font-size:2.4rem;font-weight:700;margin-bottom:8px;}
67
+ #greeting p{color:var(--muted);font-size:1rem;}
68
+
69
+ /* MESSAGES */
70
+ .msg{display:flex;gap:12px;max-width:780px;width:100%;margin:0 auto;}
71
+ .msg.user{flex-direction:row-reverse;}
72
+ .msg-avatar{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;flex-shrink:0;}
73
+ .msg.user .msg-avatar{background:var(--accent);}
74
+ .msg.ai .msg-avatar{background:#333;font-size:16px;}
75
+ .msg-content{background:var(--bubble-ai);border:1px solid var(--border);border-radius:12px;padding:14px 18px;font-size:0.92rem;line-height:1.6;max-width:680px;word-break:break-word;}
76
+ .msg.user .msg-content{background:#1a3a2f;border-color:#2a5a44;}
77
+ .msg-content code{background:#0d0d0d;border:1px solid var(--border);border-radius:4px;padding:1px 6px;font-size:0.85em;font-family:monospace;}
78
+ .msg-content pre{background:#0d0d0d;border:1px solid var(--border);border-radius:8px;padding:14px;overflow-x:auto;margin:10px 0;}
79
+ .msg-content pre code{background:none;border:none;padding:0;}
80
+ .msg-content img{max-width:100%;border-radius:8px;margin:8px 0;display:block;}
81
+ .msg-content video{max-width:100%;border-radius:8px;margin:8px 0;display:block;}
82
+ .gen-status{color:var(--muted);font-size:0.82rem;font-style:italic;margin-top:6px;}
83
+ .tts-btn{background:transparent;border:none;color:var(--muted);cursor:pointer;font-size:14px;padding:4px 6px;border-radius:4px;margin-top:6px;display:inline-flex;align-items:center;gap:4px;transition:color 0.2s;}
84
+ .tts-btn:hover{color:var(--text);}
85
+
86
+ /* INPUT */
87
+ #input-area{padding:16px 24px 24px;border-top:1px solid var(--border);}
88
+ #input-box{background:var(--input-bg);border:1px solid var(--border);border-radius:16px;padding:12px 16px;display:flex;align-items:flex-end;gap:10px;max-width:780px;margin:0 auto;transition:border 0.2s;}
89
+ #input-box:focus-within{border-color:#444;}
90
+ #msg-input{flex:1;background:transparent;border:none;outline:none;color:var(--text);font-size:0.95rem;resize:none;max-height:160px;line-height:1.5;font-family:inherit;}
91
+ #msg-input::placeholder{color:#555;}
92
+ .input-actions{display:flex;align-items:center;gap:6px;}
93
+ .icon-btn{background:transparent;border:none;color:var(--muted);cursor:pointer;padding:6px;border-radius:6px;font-size:16px;transition:color 0.2s,background 0.2s;}
94
+ .icon-btn:hover{color:var(--text);background:#252525;}
95
+ #send-btn{background:var(--accent);border:none;color:#fff;width:34px;height:34px;border-radius:8px;cursor:pointer;font-size:16px;display:flex;align-items:center;justify-content:center;transition:background 0.2s;}
96
+ #send-btn:hover{background:var(--accent2);}
97
+ #send-btn:disabled{background:#333;cursor:not-allowed;}
98
+ #attach-preview{max-width:780px;margin:0 auto 8px;display:none;gap:8px;}
99
+ #attach-preview img,#attach-preview video{max-height:80px;border-radius:6px;border:1px solid var(--border);}
100
+ .typing{display:flex;gap:4px;padding:6px 0;}
101
+ .typing span{width:8px;height:8px;background:var(--muted);border-radius:50%;animation:bounce 1.2s infinite;}
102
+ .typing span:nth-child(2){animation-delay:0.2s;}
103
+ .typing span:nth-child(3){animation-delay:0.4s;}
104
+ @keyframes bounce{0%,60%,100%{transform:translateY(0);}30%{transform:translateY(-6px);}}
105
+ #file-input{display:none;}
106
+ </style>
107
+ </head>
108
+ <body>
109
+
110
+ <!-- ONBOARDING -->
111
+ <div id="onboarding">
112
+ <div id="onboarding-card">
113
+ <h1>Welcome to HuggingGPT</h1>
114
+ <p>Let's personalize your experience before we start.</p>
115
+ <div class="field">
116
+ <label>What should I call you?</label>
117
+ <input type="text" id="ob-name" placeholder="e.g. Alex">
118
+ </div>
119
+ <div class="field">
120
+ <label>AI Personality</label>
121
+ <select id="ob-personality">
122
+ <option>Professional & Concise</option>
123
+ <option>Friendly & Creative</option>
124
+ <option>Sarcastic & Witty</option>
125
+ <option>Expert Coder</option>
126
+ <option>No restrictions (research mode)</option>
127
+ </select>
128
+ </div>
129
+ <button class="btn" onclick="finishOnboarding()">Get Started →</button>
130
+ </div>
131
+ </div>
132
+
133
+ <!-- SIDEBAR -->
134
+ <div id="sidebar">
135
+ <div id="sidebar-title">🤗 HuggingGPT</div>
136
+ <button class="new-chat-btn" onclick="newChat()">+ New chat</button>
137
+
138
+ <div class="section-label">Model</div>
139
+ <button class="mode-btn active" onclick="setMode(this,'qwen')" data-mode="qwen">⚡ Qwen 2.5 7B</button>
140
+ <button class="mode-btn" onclick="setMode(this,'kimi')" data-mode="kimi">🌙 Kimi K2</button>
141
+ <button class="mode-btn" onclick="setMode(this,'small')" data-mode="small">🪶 Qwen 0.5B (fast)</button>
142
+ <button class="mode-btn" onclick="setMode(this,'custom')" data-mode="custom">🔧 Custom</button>
143
+ <div id="custom-model-input"><input type="text" id="custom-url" placeholder="user/model-repo"></div>
144
+
145
+ <div class="section-label">Image Gen</div>
146
+ <button class="mode-btn active" onclick="setImgMode(this,'flash')" data-img="flash">⚡ FLUX Schnell (fast)</button>
147
+ <button class="mode-btn" onclick="setImgMode(this,'full')" data-img="full">🖼 FLUX Dev (quality)</button>
148
+
149
+ <div class="sidebar-spacer"></div>
150
+
151
+ <div class="tts-toggle">
152
+ <label>🔊 TTS (Kokoro)</label>
153
+ <label class="toggle">
154
+ <input type="checkbox" id="tts-toggle" onchange="toggleTTS(this)">
155
+ <span class="slider"></span>
156
+ </label>
157
+ </div>
158
+
159
+ <div id="user-profile">
160
+ <div class="avatar" id="user-avatar">?</div>
161
+ <div class="profile-info">
162
+ <div class="profile-name" id="user-name-display">Guest</div>
163
+ <div class="profile-sub" id="user-personality-display">Personal account</div>
164
+ </div>
165
+ </div>
166
+ </div>
167
+
168
+ <!-- MAIN CHAT -->
169
+ <div id="main">
170
+ <div id="chat-area">
171
+ <div id="greeting">
172
+ <h1 id="greeting-text">Good to see you.</h1>
173
+ <p>Ask me anything — I can generate images, videos, and speak to you.</p>
174
+ </div>
175
+ </div>
176
+
177
+ <div id="attach-preview"></div>
178
+
179
+ <div id="input-area">
180
+ <div id="input-box">
181
+ <textarea id="msg-input" rows="1" placeholder="Ask anything..." onkeydown="handleKey(event)" oninput="autoResize(this)"></textarea>
182
+ <div class="input-actions">
183
+ <button class="icon-btn" onclick="document.getElementById('file-input').click()" title="Attach image/video">📎</button>
184
+ <button id="send-btn" onclick="sendMessage()">↑</button>
185
+ </div>
186
+ </div>
187
+ <input type="file" id="file-input" accept="image/*,video/*" onchange="handleAttach(this)">
188
+ </div>
189
+ </div>
190
+
191
+ <script>
192
+ // --- STATE ---
193
+ let state = {
194
+ name: 'Guest', personality: 'Professional & Concise',
195
+ mode: 'qwen', imgMode: 'flash',
196
+ tts: false, history: [],
197
+ attachment: null, attachType: null
198
+ };
199
+
200
+ const MODELS = {
201
+ qwen: 'Qwen/Qwen2.5-7B-Instruct',
202
+ kimi: 'moonshotai/Kimi-K2-Instruct',
203
+ small: 'Qwen/Qwen2.5-0.5B-Instruct',
204
+ custom: null
205
+ };
206
+
207
+ const IMG_MODELS = {
208
+ flash: 'black-forest-labs/FLUX.1-schnell',
209
+ full: 'black-forest-labs/FLUX.1-dev'
210
+ };
211
+
212
+ const VIDEO_MODEL = 'ByteDance/AnimateDiff-Lightning';
213
+ const TTS_MODEL = 'hexgrad/Kokoro-82M';
214
+ const HF_API = 'https://api-inference.huggingface.co/models/';
215
+
216
+ // --- ONBOARDING ---
217
+ function finishOnboarding(){
218
+ const name = document.getElementById('ob-name').value.trim() || 'Guest';
219
+ const pers = document.getElementById('ob-personality').value;
220
+ state.name = name; state.personality = pers;
221
+ document.getElementById('user-name-display').textContent = name;
222
+ document.getElementById('user-avatar').textContent = name[0].toUpperCase();
223
+ document.getElementById('user-personality-display').textContent = pers;
224
+ document.getElementById('greeting-text').textContent = `Good to see you, ${name}.`;
225
+ document.getElementById('onboarding').style.display = 'none';
226
+ }
227
+
228
+ // --- MODE BUTTONS ---
229
+ function setMode(btn, mode){
230
+ document.querySelectorAll('[data-mode]').forEach(b=>b.classList.remove('active'));
231
+ btn.classList.add('active');
232
+ state.mode = mode;
233
+ document.getElementById('custom-model-input').style.display = mode==='custom'?'block':'none';
234
+ }
235
+ function setImgMode(btn, mode){
236
+ document.querySelectorAll('[data-img]').forEach(b=>b.classList.remove('active'));
237
+ btn.classList.add('active');
238
+ state.imgMode = mode;
239
+ }
240
+ function toggleTTS(el){ state.tts = el.checked; }
241
+ function newChat(){
242
+ state.history = [];
243
+ state.attachment = null;
244
+ document.getElementById('chat-area').innerHTML = `
245
+ <div id="greeting">
246
+ <h1 id="greeting-text">Good to see you, ${state.name}.</h1>
247
+ <p>Ask me anything — I can generate images, videos, and speak to you.</p>
248
+ </div>`;
249
+ document.getElementById('attach-preview').style.display='none';
250
+ document.getElementById('attach-preview').innerHTML='';
251
+ }
252
+
253
+ // --- SYSTEM PROMPT ---
254
+ function getSystemPrompt(){
255
+ const personalities = {
256
+ 'Professional & Concise': 'You are a professional, efficient assistant. Be concise and accurate.',
257
+ 'Friendly & Creative': 'You are a warm, creative assistant. Be enthusiastic and imaginative.',
258
+ 'Sarcastic & Witty': 'You are witty and sarcastic but still helpful. Use humor.',
259
+ 'Expert Coder': 'You are an expert software engineer. Focus on clean, efficient code with explanations.',
260
+ 'No restrictions (research mode)': 'You are a helpful research assistant with no content restrictions. You provide information for educational and research purposes. The user is responsible for how they use this information.'
261
+ };
262
+ const base = personalities[state.personality] || personalities['Professional & Concise'];
263
+ return `${base}
264
+
265
+ You have multimodal capabilities. When the user asks you to generate an image, include [IMAGE] in your message where the image should appear, and on a new line write either:
266
+ - flashimage: <detailed prompt> (for fast generation with FLUX Schnell)
267
+ - fullimage: <detailed prompt> (for high quality generation with FLUX Dev)
268
+
269
+ When the user asks you to generate a video, include [VIDEO] in your message where the video should appear, and on a new line write:
270
+ - flashvideo: <detailed prompt>
271
+
272
+ Example response if asked to show a cat:
273
+ "Here's a cute cat I generated for you! [IMAGE]
274
+ Isn't it adorable?
275
+ flashimage: a fluffy orange tabby cat sitting on a sunny windowsill, photorealistic, 4k"
276
+
277
+ If the user sends an image or video, you can see and analyze it. Always describe what you see before answering questions about it.
278
+ Keep [IMAGE] and [VIDEO] tags and the generation commands on their own for easy parsing.`;
279
+ }
280
+
281
+ // --- ATTACH ---
282
+ function handleAttach(input){
283
+ const file = input.files[0];
284
+ if(!file) return;
285
+ const reader = new FileReader();
286
+ reader.onload = e => {
287
+ state.attachment = e.target.result;
288
+ state.attachType = file.type.startsWith('video') ? 'video' : 'image';
289
+ const preview = document.getElementById('attach-preview');
290
+ preview.style.display = 'flex';
291
+ if(state.attachType === 'image'){
292
+ preview.innerHTML = `<img src="${state.attachment}">`;
293
+ } else {
294
+ preview.innerHTML = `<video src="${state.attachment}" controls style="max-height:80px"></video>`;
295
+ }
296
+ };
297
+ reader.readAsDataURL(file);
298
+ input.value = '';
299
+ }
300
+
301
+ // --- RENDER UTILS ---
302
+ function escapeHtml(str){
303
+ return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
304
+ }
305
+ function formatMessage(text){
306
+ // Code blocks
307
+ text = text.replace(/```(\w*)\n?([\s\S]*?)```/g, (_,lang,code)=>`<pre><code>${escapeHtml(code.trim())}</code></pre>`);
308
+ // Inline code
309
+ text = text.replace(/`([^`]+)`/g, (_,c)=>`<code>${escapeHtml(c)}</code>`);
310
+ // Bold
311
+ text = text.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>');
312
+ // Newlines
313
+ text = text.replace(/\n/g,'<br>');
314
+ return text;
315
+ }
316
+
317
+ // --- PARSE AI RESPONSE FOR GENERATION COMMANDS ---
318
+ async function processAIResponse(rawText, msgDiv){
319
+ // Remove generation commands from display text
320
+ let displayText = rawText;
321
+ const imgCommands = [];
322
+ const vidCommands = [];
323
+
324
+ displayText = displayText.replace(/^(flashimage|fullimage): (.+)$/gm, (match, type, prompt)=>{
325
+ imgCommands.push({type, prompt: prompt.trim()});
326
+ return '';
327
+ });
328
+ displayText = displayText.replace(/^flashvideo: (.+)$/gm, (_, prompt)=>{
329
+ vidCommands.push(prompt.trim());
330
+ return '';
331
+ });
332
+ displayText = displayText.trim();
333
+
334
+ // Render text with [IMAGE] and [VIDEO] placeholders
335
+ let rendered = formatMessage(displayText);
336
+ let imgIdx = 0, vidIdx = 0;
337
+ const imgPlaceholders = rendered.match(/\[IMAGE\]/g) || [];
338
+ const vidPlaceholders = rendered.match(/\[VIDEO\]/g) || [];
339
+
340
+ // Replace [IMAGE] with spinner placeholder divs
341
+ rendered = rendered.replace(/\[IMAGE\]/g, ()=>{
342
+ const id = `gen-img-${Date.now()}-${imgIdx++}`;
343
+ return `<div id="${id}"><span class="gen-status">🎨 Generating image...</span></div>`;
344
+ });
345
+ rendered = rendered.replace(/\[VIDEO\]/g, ()=>{
346
+ const id = `gen-vid-${Date.now()}-${vidIdx++}`;
347
+ return `<div id="${id}"><span class="gen-status">🎬 Generating video...</span></div>`;
348
+ });
349
+
350
+ msgDiv.innerHTML = rendered;
351
+
352
+ // Add TTS button
353
+ if(rawText.trim()){
354
+ const ttsBtn = document.createElement('button');
355
+ ttsBtn.className = 'tts-btn';
356
+ ttsBtn.innerHTML = '🔊 Listen';
357
+ ttsBtn.onclick = () => speakText(displayText.replace(/\[IMAGE\]|\[VIDEO\]/g,''));
358
+ msgDiv.appendChild(ttsBtn);
359
+ if(state.tts) speakText(displayText.replace(/\[IMAGE\]|\[VIDEO\]/g,''));
360
+ }
361
+
362
+ // Generate images
363
+ const imgEls = msgDiv.querySelectorAll('[id^="gen-img-"]');
364
+ for(let i=0;i<imgCommands.length && i<imgEls.length;i++){
365
+ const {type, prompt} = imgCommands[i];
366
+ const el = imgEls[i];
367
+ try {
368
+ const modelId = type==='flashimage' ? IMG_MODELS.flash : IMG_MODELS.full;
369
+ const blob = await generateImage(modelId, prompt);
370
+ const url = URL.createObjectURL(blob);
371
+ el.innerHTML = `<img src="${url}" alt="${escapeHtml(prompt)}">`;
372
+ } catch(e){
373
+ el.innerHTML = `<span class="gen-status">❌ Image gen failed: ${e.message}</span>`;
374
+ }
375
+ }
376
+
377
+ // Generate videos
378
+ const vidEls = msgDiv.querySelectorAll('[id^="gen-vid-"]');
379
+ for(let i=0;i<vidCommands.length && i<vidEls.length;i++){
380
+ const prompt = vidCommands[i];
381
+ const el = vidEls[i];
382
+ try {
383
+ const blob = await generateVideo(prompt);
384
+ const url = URL.createObjectURL(blob);
385
+ el.innerHTML = `<video src="${url}" controls></video>`;
386
+ } catch(e){
387
+ el.innerHTML = `<span class="gen-status">❌ Video gen failed: ${e.message}</span>`;
388
+ }
389
+ }
390
+ }
391
+
392
+ // --- HF API CALLS ---
393
+ async function callLLM(prompt, imageData){
394
+ const modelId = state.mode==='custom'
395
+ ? (document.getElementById('custom-url').value.trim() || MODELS.qwen)
396
+ : MODELS[state.mode] || MODELS.qwen;
397
+
398
+ const messages = [...state.history];
399
+
400
+ if(imageData){
401
+ messages.push({role:'user', content:[
402
+ {type:'image_url', image_url:{url: imageData}},
403
+ {type:'text', text: prompt}
404
+ ]});
405
+ } else {
406
+ messages.push({role:'user', content: prompt});
407
+ }
408
+
409
+ const sysPrompt = getSystemPrompt();
410
+
411
+ const body = {
412
+ model: modelId,
413
+ messages: [{role:'system', content: sysPrompt}, ...messages],
414
+ max_tokens: 1024,
415
+ temperature: 0.7
416
+ };
417
+
418
+ const res = await fetch(`https://api-inference.huggingface.co/v1/chat/completions`, {
419
+ method:'POST',
420
+ headers:{'Content-Type':'application/json'},
421
+ body: JSON.stringify(body)
422
+ });
423
+
424
+ if(!res.ok){
425
+ // Fallback to text generation endpoint
426
+ const res2 = await fetch(`${HF_API}${modelId}`, {
427
+ method:'POST',
428
+ headers:{'Content-Type':'application/json'},
429
+ body: JSON.stringify({
430
+ inputs: `System: ${sysPrompt}\n\n${state.history.map(m=>`${m.role==='user'?'User':'Assistant'}: ${m.content}`).join('\n')}\nUser: ${prompt}\nAssistant:`,
431
+ parameters:{max_new_tokens:512, temperature:0.7, return_full_text:false}
432
+ })
433
+ });
434
+ if(!res2.ok) throw new Error(`API error: ${res2.status}`);
435
+ const data2 = await res2.json();
436
+ return data2[0]?.generated_text || 'No response';
437
+ }
438
+
439
+ const data = await res.json();
440
+ return data.choices?.[0]?.message?.content || 'No response';
441
+ }
442
+
443
+ async function generateImage(modelId, prompt){
444
+ const res = await fetch(`${HF_API}${modelId}`, {
445
+ method:'POST',
446
+ headers:{'Content-Type':'application/json'},
447
+ body: JSON.stringify({inputs: prompt})
448
+ });
449
+ if(!res.ok) throw new Error(`${res.status}`);
450
+ return res.blob();
451
+ }
452
+
453
+ async function generateVideo(prompt){
454
+ const res = await fetch(`${HF_API}${VIDEO_MODEL}`, {
455
+ method:'POST',
456
+ headers:{'Content-Type':'application/json'},
457
+ body: JSON.stringify({inputs: prompt})
458
+ });
459
+ if(!res.ok) throw new Error(`${res.status}`);
460
+ return res.blob();
461
+ }
462
+
463
+ async function speakText(text){
464
+ try {
465
+ const clean = text.replace(/<[^>]+>/g,'').substring(0, 500);
466
+ const res = await fetch(`${HF_API}${TTS_MODEL}`, {
467
+ method:'POST',
468
+ headers:{'Content-Type':'application/json'},
469
+ body: JSON.stringify({inputs: clean})
470
+ });
471
+ if(!res.ok) return;
472
+ const blob = await res.blob();
473
+ const url = URL.createObjectURL(blob);
474
+ const audio = new Audio(url);
475
+ audio.play();
476
+ } catch(e){ console.error('TTS failed', e); }
477
+ }
478
+
479
+ // --- SEND MESSAGE ---
480
+ async function sendMessage(){
481
+ const input = document.getElementById('msg-input');
482
+ const text = input.value.trim();
483
+ if(!text && !state.attachment) return;
484
+
485
+ // Hide greeting
486
+ const greeting = document.getElementById('greeting');
487
+ if(greeting) greeting.remove();
488
+
489
+ const chatArea = document.getElementById('chat-area');
490
+ const sendBtn = document.getElementById('send-btn');
491
+ sendBtn.disabled = true;
492
+
493
+ // Add user message
494
+ const userDiv = document.createElement('div');
495
+ userDiv.className = 'msg user';
496
+ let userContent = text;
497
+ if(state.attachment){
498
+ if(state.attachType==='image'){
499
+ userContent = `<img src="${state.attachment}" style="max-height:120px;border-radius:6px;display:block;margin-bottom:6px;">${text?'<br>'+escapeHtml(text):''}`;
500
+ } else {
501
+ userContent = `<video src="${state.attachment}" controls style="max-height:120px;border-radius:6px;display:block;margin-bottom:6px;"></video>${text?'<br>'+escapeHtml(text):''}`;
502
+ }
503
+ } else {
504
+ userContent = formatMessage(text);
505
+ }
506
+ userDiv.innerHTML = `
507
+ <div class="msg-avatar">${state.name[0]?.toUpperCase()||'U'}</div>
508
+ <div class="msg-content">${userContent}</div>`;
509
+ chatArea.appendChild(userDiv);
510
+
511
+ // Add typing indicator
512
+ const typingDiv = document.createElement('div');
513
+ typingDiv.className = 'msg ai';
514
+ typingDiv.innerHTML = `
515
+ <div class="msg-avatar">🤗</div>
516
+ <div class="msg-content"><div class="typing"><span></span><span></span><span></span></div></div>`;
517
+ chatArea.appendChild(typingDiv);
518
+ chatArea.scrollTop = chatArea.scrollHeight;
519
+
520
+ const userText = text;
521
+ const attachData = state.attachment;
522
+
523
+ // Clear input
524
+ input.value = '';
525
+ autoResize(input);
526
+ state.attachment = null;
527
+ document.getElementById('attach-preview').style.display='none';
528
+ document.getElementById('attach-preview').innerHTML='';
529
+
530
+ try {
531
+ const response = await callLLM(userText, attachData);
532
+
533
+ // Update history
534
+ state.history.push({role:'user', content: userText});
535
+ state.history.push({role:'assistant', content: response});
536
+ if(state.history.length > 20) state.history = state.history.slice(-20);
537
+
538
+ // Replace typing with real response
539
+ const aiDiv = document.createElement('div');
540
+ aiDiv.className = 'msg ai';
541
+ const msgContent = document.createElement('div');
542
+ msgContent.className = 'msg-content';
543
+ aiDiv.innerHTML = `<div class="msg-avatar">🤗</div>`;
544
+ aiDiv.appendChild(msgContent);
545
+ typingDiv.replaceWith(aiDiv);
546
+
547
+ await processAIResponse(response, msgContent);
548
+
549
+ } catch(e){
550
+ typingDiv.querySelector('.msg-content').innerHTML = `<span style="color:#e55">Error: ${e.message}</span>`;
551
+ }
552
+
553
+ chatArea.scrollTop = chatArea.scrollHeight;
554
+ sendBtn.disabled = false;
555
+ input.focus();
556
+ }
557
+
558
+ // --- UI HELPERS ---
559
+ function handleKey(e){
560
+ if(e.key==='Enter' && !e.shiftKey){ e.preventDefault(); sendMessage(); }
561
+ }
562
+ function autoResize(el){
563
+ el.style.height='auto';
564
+ el.style.height=Math.min(el.scrollHeight, 160)+'px';
565
+ }
566
+ </script>
567
+ </body>
568
+ </html>
app.py CHANGED
@@ -1,207 +1,9 @@
1
- import gradio as gr
2
- import requests
3
- import os
4
-
5
- # --- Constants & API Config ---
6
- HF_API_URL = "https://api-inference.huggingface.co/models/"
7
-
8
- def query_hf_model(model_id, prompt, hf_token, system_prompt):
9
- if "huggingface.co/" in model_id:
10
- model_id = model_id.split("huggingface.co/")[-1]
11
-
12
- api_url = f"{HF_API_URL}{model_id}"
13
-
14
- # Try without token first, then with token if provided
15
- headers = {}
16
- if hf_token:
17
- headers = {"Authorization": f"Bearer {hf_token}"}
18
-
19
- # Inject personality into the prompt
20
- full_prompt = f"System: {system_prompt}\n\nUser: {prompt}\n\nAssistant:"
21
-
22
- try:
23
- response = requests.post(
24
- api_url,
25
- headers=headers,
26
- json={"inputs": full_prompt, "parameters": {"max_new_tokens": 512, "temperature": 0.7}},
27
- timeout=20
28
- )
29
- if response.status_code == 200:
30
- result = response.json()
31
- if isinstance(result, list) and len(result) > 0:
32
- text = result[0].get("generated_text", "")
33
- return text.split("Assistant:")[-1].strip()
34
- return str(result)
35
- else:
36
- return f"API Error: {response.text}"
37
- except Exception as e:
38
- return f"Request failed: {str(e)}"
39
-
40
- # --- UI Logic ---
41
-
42
- def finish_onboarding(name, personality):
43
- if not name.strip():
44
- name = "Guest"
45
- # Returns: Update profile state, Update Greeting, Hide Onboarding, Show Chat UI
46
- return {"name": name, "personality": personality}, f"Good to see you, {name}.", gr.update(visible=False), gr.update(visible=True)
47
-
48
- def chat_response(message, history, profile, routing_mode, custom_link, hf_token):
49
- if not message.strip():
50
- return history, ""
51
-
52
- # Base System Prompt + User Personality
53
- base_prompt = f"You are a helpful assistant. Your personality is: {profile['personality']}."
54
- model_id = "Qwen/Qwen2.5-7B-Instruct"
55
-
56
- if routing_mode == "Custom" and custom_link:
57
- model_id = custom_link
58
- elif routing_mode == "Kimi":
59
- model_id = "moonshotai/Kimi-K2.6"
60
- elif routing_mode == "Smallest qwen":
61
- model_id = "Qwen/Qwen2.5-0.5B-Instruct"
62
- elif routing_mode == "NSFWlowcomplexity":
63
- model_id = "mradermacher/JPHackX-QwenUnfiltered-Full-GGUF"
64
- elif routing_mode == "Qwen autoroute":
65
- model_id = "Qwen/Qwen2.5-7B-Instruct" # Defaulting for stability
66
-
67
- history.append({"role": "user", "content": message})
68
- bot_message = query_hf_model(model_id, message, hf_token, base_prompt)
69
- history.append({"role": "assistant", "content": bot_message})
70
-
71
- return history, "", gr.update(visible=False)
72
-
73
- # --- Custom CSS ---
74
- css = """
75
- .gradio-container { background-color: #212121 !important; color: white !important; }
76
- #side-bar {
77
- background-color: #171717 !important;
78
- border-right: 1px solid #333;
79
- padding: 15px;
80
- height: 100vh;
81
- display: flex;
82
- flex-direction: column;
83
- }
84
- #side-bar .gap { flex-grow: 1; }
85
- #main-container { padding: 0 !important; }
86
-
87
- /* Onboarding Overlay */
88
- #onboarding-card {
89
- background-color: #2f2f2f;
90
- padding: 30px;
91
- border-radius: 15px;
92
- border: 1px solid #444;
93
- max-width: 500px;
94
- margin: 100px auto;
95
- }
96
-
97
- /* Chat Styling */
98
- #chat-window { border: none !important; background: transparent !important; }
99
- #greeting-area { margin-top: 15vh; text-align: center; }
100
- #greeting-area h1 { font-size: 2.2rem; font-weight: 600; color: #ececec; }
101
-
102
- /* Input Bar (Pill) */
103
- #input-row {
104
- background-color: #2f2f2f !important;
105
- border-radius: 28px !important;
106
- border: 1px solid #424242 !important;
107
- padding: 4px 16px !important;
108
- margin: 0 auto 20px auto;
109
- max-width: 800px;
110
- }
111
- #input-row textarea { background: transparent !important; border: none !important; color: white !important; box-shadow: none !important; }
112
-
113
- /* Sidebar Profile Section */
114
- #user-profile-bottom {
115
- margin-top: auto;
116
- padding-top: 15px;
117
- border-top: 1px solid #333;
118
- }
119
- .user-avatar {
120
- background: #00a67e;
121
- color: white;
122
- width: 32px;
123
- height: 32px;
124
- border-radius: 6px;
125
- display: flex;
126
- align-items: center;
127
- justify-content: center;
128
- font-weight: bold;
129
- font-size: 14px;
130
- flex-shrink: 0;
131
- }
132
- """
133
-
134
- with gr.Blocks() as demo:
135
- # --- State Management ---
136
- user_profile = gr.State({"name": "", "personality": "", "onboarded": False})
137
- history_state = gr.State([])
138
-
139
- # --- Onboarding UI (Visible by default) ---
140
- with gr.Column(elem_id="onboarding-card", visible=True) as onboarding_view:
141
- gr.Markdown("# Welcome to HuggingGPT\nLet's set up your experience.")
142
- user_name = gr.Textbox(label="What should I call you?", placeholder="e.g. Bentley")
143
- user_pref = gr.Dropdown(
144
- label="AI Personality Preference",
145
- choices=["Professional & Concise", "Friendly & Creative", "Sarcastic & Witty", "Expert Coder"],
146
- value="Professional & Concise"
147
- )
148
- start_btn = gr.Button("Get Started", variant="primary")
149
-
150
- # --- Main Application UI (Hidden by default) ---
151
- with gr.Row(visible=False, elem_id="main-container") as main_view:
152
- # SIDEBAR
153
- with gr.Column(scale=1, elem_id="side-bar"):
154
- gr.Button("HuggingGPT", variant="primary")
155
- gr.Button("New chat", variant="secondary")
156
-
157
- gr.Markdown("### Settings")
158
- routing_mode = gr.Radio(
159
- choices=["Qwen autoroute", "Custom", "Kimi", "Smallest qwen", "NSFWlowcomplexity"],
160
- label="Mode", value="Qwen autoroute"
161
- )
162
- custom_link = gr.Textbox(label="Model URL", placeholder="user/repo", visible=False)
163
- hf_token = gr.Textbox(label="HF Token", type="password", placeholder="hf_...")
164
-
165
- # Spacer to push profile to bottom
166
- gr.HTML("<div style='flex-grow:1;'></div>")
167
-
168
- # User Info at Bottom
169
- with gr.Row(elem_id="user-profile-bottom"):
170
- gr.HTML("<div class='user-avatar'>U</div>")
171
- with gr.Column(scale=3):
172
- profile_display_name = gr.Markdown("**Guest**")
173
- gr.Markdown("<small style='color:#888;'>Personal account</small>")
174
-
175
- # CHAT AREA
176
- with gr.Column(scale=5):
177
- with gr.Column(elem_id="greeting-area") as greeting_box:
178
- greeting_text = gr.Markdown("# Good to see you.")
179
-
180
- chatbot = gr.Chatbot(elem_id="chat-window", height=600, show_label=False)
181
-
182
- with gr.Row(elem_id="input-row"):
183
- msg = gr.Textbox(placeholder="Ask anything", container=False, scale=10)
184
- submit_btn = gr.Button("↑", variant="primary", scale=1)
185
-
186
- # --- Event Interactivity ---
187
-
188
- # Onboarding Completion
189
- start_btn.click(
190
- finish_onboarding,
191
- [user_name, user_pref],
192
- [user_profile, greeting_text, onboarding_view, main_view]
193
- ).then(
194
- lambda p: gr.update(value=f"**{p['name']}**"),
195
- user_profile, profile_display_name
196
- )
197
-
198
- # Routing Logic
199
- routing_mode.change(lambda x: gr.update(visible=(x == "Custom")), routing_mode, custom_link)
200
-
201
- # Chat submission
202
- input_params = [msg, history_state, user_profile, routing_mode, custom_link, hf_token]
203
- msg.submit(chat_response, input_params, [chatbot, msg, greeting_box]).then(lambda x: x, history_state, history_state)
204
- submit_btn.click(chat_response, input_params, [chatbot, msg, greeting_box]).then(lambda x: x, history_state, history_state)
205
-
206
- if __name__ == "__main__":
207
- demo.launch(css=css, theme=gr.themes.Soft())
 
1
+ import gradio as gr
2
+
3
+ with open("app.html", "r") as f:
4
+ html_content = f.read()
5
+
6
+ with gr.Blocks() as demo:
7
+ gr.HTML(html_content)
8
+
9
+ demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,4 +1 @@
1
- gradio
2
- transformers
3
- torch
4
- requests
 
1
+ gradio==4.44.0