akhaliq HF Staff commited on
Commit
ee98620
·
1 Parent(s): 802afb1

refactor: update UI styling and layout with new color palette and modern scrollable chat interface

Browse files
Files changed (1) hide show
  1. index.html +121 -97
index.html CHANGED
@@ -3,19 +3,19 @@
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
- <title>MiniCPM-V 4.6 | Minimalist Multimodal AI</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
9
  <script src="https://unpkg.com/lucide@latest"></script>
10
  <style>
11
  :root {
12
- --bg: #191919;
13
- --accent: #D4A27F;
14
- --accent-soft: rgba(212, 162, 127, 0.1);
15
  --text: #FFFFFF;
16
- --text-muted: #A0A0A0;
17
  --glass: rgba(255, 255, 255, 0.03);
18
- --glass-border: rgba(255, 255, 255, 0.08);
19
  }
20
 
21
  body {
@@ -23,33 +23,47 @@
23
  background-color: var(--bg);
24
  color: var(--text);
25
  height: 100vh;
26
- overflow: hidden;
 
 
 
27
  }
28
 
29
  h1, h2, h3 { font-family: 'Outfit', sans-serif; }
30
 
31
- .glass {
32
- background: var(--glass);
33
- backdrop-filter: blur(20px);
34
- -webkit-backdrop-filter: blur(20px);
35
- border: 1px solid var(--glass-border);
36
  }
37
 
38
- .chat-container {
39
- scrollbar-width: none;
40
- -ms-overflow-style: none;
 
 
 
 
 
 
 
41
  }
42
- .chat-container::-webkit-scrollbar { display: none; }
43
 
44
  .message-bubble {
45
- max-width: 80%;
46
- transition: transform 0.2s ease;
 
 
 
 
 
47
  }
48
 
49
  .user-message {
50
- background-color: var(--accent);
51
- color: #191919;
52
- box-shadow: 0 4px 20px rgba(212, 162, 127, 0.15);
53
  }
54
 
55
  .bot-message {
@@ -57,19 +71,10 @@
57
  border: 1px solid var(--glass-border);
58
  }
59
 
60
- .animate-in {
61
- animation: slideIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
62
- }
63
-
64
- @keyframes slideIn {
65
- from { opacity: 0; transform: translateY(15px); }
66
- to { opacity: 1; transform: translateY(0); }
67
- }
68
-
69
  .typing-dot {
70
- width: 5px;
71
- height: 5px;
72
- background: var(--accent);
73
  border-radius: 50%;
74
  animation: bounce 1.4s infinite ease-in-out;
75
  }
@@ -81,88 +86,100 @@
81
  40% { transform: scale(1); opacity: 1; }
82
  }
83
 
84
- .input-wrapper {
85
- background: rgba(255, 255, 255, 0.04);
 
 
86
  border: 1px solid var(--glass-border);
87
  transition: all 0.3s ease;
88
  }
89
 
90
- .input-wrapper:focus-within {
91
- border-color: var(--accent);
92
- background: rgba(255, 255, 255, 0.06);
93
- box-shadow: 0 0 30px rgba(212, 162, 127, 0.05);
94
  }
95
 
96
- #user-input::placeholder { color: #666; }
 
 
97
 
98
- .accent-text { color: var(--accent); }
99
- .accent-bg { background-color: var(--accent); }
100
-
101
- @media (max-width: 768px) {
102
- .message-bubble { max-width: 90%; }
103
  }
 
 
 
104
  </style>
105
  </head>
106
- <body class="flex flex-col items-center">
107
 
108
  <!-- Minimalist Header -->
109
- <header class="w-full h-20 flex items-center justify-between px-6 md:px-12 z-50">
110
- <div class="flex items-center gap-3">
111
- <div class="w-2 h-2 rounded-full accent-bg"></div>
112
- <h1 class="text-xl font-medium tracking-tight">MiniCPM-V</h1>
 
 
 
 
113
  </div>
114
- <div class="flex items-center gap-4 text-xs font-medium text-muted uppercase tracking-widest">
115
- <span>Vision Understanding</span>
 
116
  </div>
117
  </header>
118
 
119
- <!-- Main Chat Area -->
120
- <main class="w-full max-w-4xl flex-1 flex flex-col relative px-4 md:px-0">
121
- <div id="chat-messages" class="flex-1 overflow-y-auto pt-4 pb-32 space-y-8 chat-container">
122
  <!-- Bot Greeting -->
123
- <div class="flex gap-4 items-start animate-in">
124
- <div class="bot-message p-6 rounded-3xl rounded-tl-none message-bubble">
125
  <p class="text-white/90 leading-relaxed text-[15px]">
126
- Welcome. I am <span class="accent-text font-medium">MiniCPM-V 4.6</span>.
127
- Upload an image or video to explore its details with me.
 
 
128
  </p>
129
  </div>
130
  </div>
131
  </div>
 
132
 
133
- <!-- Floating Input -->
134
- <div class="fixed bottom-0 left-0 right-0 p-6 md:p-10 pointer-events-none">
135
- <div class="max-w-3xl mx-auto pointer-events-auto">
136
- <!-- Preview -->
137
- <div id="preview-container" class="hidden mb-6 animate-in">
138
- <div class="relative inline-block">
139
- <img id="image-preview" src="" class="h-40 w-auto rounded-3xl border border-white/10 shadow-2xl hidden object-cover" />
140
- <video id="video-preview" class="h-40 w-auto rounded-3xl border border-white/10 shadow-2xl hidden object-cover" muted loop></video>
141
- <button id="cancel-file" class="absolute -top-3 -right-3 bg-white text-black rounded-full p-2 shadow-xl hover:bg-neutral-200 transition-colors">
142
- <i data-lucide="x" class="w-4 h-4"></i>
143
- </button>
144
- </div>
145
  </div>
 
146
 
147
- <!-- Input Box -->
148
- <div class="input-wrapper rounded-[2rem] p-2 flex items-end gap-2 pr-2">
149
- <div class="flex items-center">
150
- <input type="file" id="file-input" class="hidden" accept="image/*,video/*">
151
- <button id="upload-trigger" class="p-4 text-white/40 hover:text-white transition-all">
152
- <i data-lucide="paperclip" class="w-6 h-6"></i>
153
- </button>
154
- </div>
155
-
156
- <textarea id="user-input" rows="1" placeholder="Type a message..." class="flex-1 bg-transparent border-none focus:ring-0 text-white py-4 px-2 resize-none max-h-40 scrollbar-none text-[16px] leading-relaxed" oninput="this.style.height = ''; this.style.height = this.scrollHeight + 'px'"></textarea>
157
-
158
- <button id="send-btn" class="w-12 h-12 accent-bg text-neutral-900 rounded-full flex items-center justify-center transition-all disabled:opacity-20 disabled:grayscale group shrink-0 mb-1">
159
- <i data-lucide="arrow-up" class="w-5 h-5 group-hover:scale-110 transition-transform" id="send-icon"></i>
160
- <i data-lucide="loader-2" class="w-5 h-5 animate-spin hidden" id="loading-icon"></i>
161
  </button>
162
  </div>
 
 
 
 
 
 
 
 
163
  </div>
164
  </div>
165
- </main>
166
 
167
  <script type="module">
168
  import { Client, handle_file } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
@@ -187,10 +204,11 @@
187
  async function init() {
188
  try {
189
  client = await Client.connect(window.location.origin);
190
- } catch (err) { console.error("Connection failed", err); }
191
  }
192
  init();
193
 
 
194
  uploadTrigger.onclick = () => fileInput.click();
195
  fileInput.onchange = (e) => {
196
  const file = e.target.files[0];
@@ -221,26 +239,31 @@
221
 
222
  function appendMessage(role, text, mediaUrl = null, mediaType = null) {
223
  const div = document.createElement('div');
224
- div.className = `flex gap-4 items-start animate-in ${role === 'user' ? 'flex-row-reverse' : ''}`;
225
 
226
  let mediaHtml = '';
227
  if (mediaUrl) {
228
  if (mediaType.startsWith('image')) {
229
- mediaHtml = `<img src="${mediaUrl}" class="max-w-xs md:max-w-sm rounded-3xl mb-4 border border-white/10" />`;
230
  } else {
231
- mediaHtml = `<video src="${mediaUrl}" controls class="max-w-xs md:max-w-sm rounded-3xl mb-4 border border-white/10"></video>`;
232
  }
233
  }
234
 
235
  const bubbleClass = role === 'user' ? 'user-message' : 'bot-message';
236
 
237
  div.innerHTML = `
238
- <div class="${bubbleClass} p-6 rounded-[2rem] ${role === 'user' ? 'rounded-tr-none' : 'rounded-tl-none'} message-bubble">
239
  ${mediaHtml}
240
- <p class="leading-relaxed text-[15px] whitespace-pre-wrap">${text}</p>
241
  </div>
242
  `;
243
- chatMessages.appendChild(div);
 
 
 
 
 
244
  chatMessages.scrollTo({ top: chatMessages.scrollHeight, behavior: 'smooth' });
245
  }
246
 
@@ -266,7 +289,7 @@
266
  const thinkingId = 'think-' + Date.now();
267
  const thinkingDiv = document.createElement('div');
268
  thinkingDiv.id = thinkingId;
269
- thinkingDiv.className = 'flex gap-4 items-start animate-in';
270
  thinkingDiv.innerHTML = `
271
  <div class="bot-message p-6 rounded-[2rem] rounded-tl-none message-bubble flex items-center gap-4">
272
  <div class="flex gap-1.5">
@@ -274,7 +297,8 @@
274
  </div>
275
  </div>
276
  `;
277
- chatMessages.appendChild(thinkingDiv);
 
278
  chatMessages.scrollTo({ top: chatMessages.scrollHeight, behavior: 'smooth' });
279
 
280
  try {
@@ -282,14 +306,14 @@
282
  const result = await client.predict("/predict", {
283
  message: content,
284
  file: fileData,
285
- downsample_mode: "16x" // Hidden default for minimalist UI
286
  });
287
 
288
  document.getElementById(thinkingId).remove();
289
  appendMessage('bot', result.data);
290
  } catch (err) {
291
  document.getElementById(thinkingId).remove();
292
- appendMessage('bot', "An error occurred. Please try again.");
293
  } finally {
294
  sendIcon.classList.remove('hidden');
295
  loadingIcon.classList.add('hidden');
 
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
+ <title>MiniCPM-V | OpenBMB</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
9
  <script src="https://unpkg.com/lucide@latest"></script>
10
  <style>
11
  :root {
12
+ --bg: #0A0C10;
13
+ --blue: #3B5BFF;
14
+ --cyan: #27D4EA;
15
  --text: #FFFFFF;
16
+ --text-muted: #6E7681;
17
  --glass: rgba(255, 255, 255, 0.03);
18
+ --glass-border: rgba(255, 255, 255, 0.1);
19
  }
20
 
21
  body {
 
23
  background-color: var(--bg);
24
  color: var(--text);
25
  height: 100vh;
26
+ margin: 0;
27
+ display: flex;
28
+ flex-direction: column;
29
+ overflow: hidden; /* Prevent body scroll */
30
  }
31
 
32
  h1, h2, h3 { font-family: 'Outfit', sans-serif; }
33
 
34
+ .chat-scroll-area {
35
+ flex: 1;
36
+ overflow-y: auto;
37
+ padding-bottom: 120px; /* Space for floating input */
38
+ -webkit-overflow-scrolling: touch;
39
  }
40
 
41
+ /* Modern Scrollbar */
42
+ .chat-scroll-area::-webkit-scrollbar {
43
+ width: 5px;
44
+ }
45
+ .chat-scroll-area::-webkit-scrollbar-track {
46
+ background: transparent;
47
+ }
48
+ .chat-scroll-area::-webkit-scrollbar-thumb {
49
+ background: rgba(255, 255, 255, 0.1);
50
+ border-radius: 10px;
51
  }
 
52
 
53
  .message-bubble {
54
+ max-width: 85%;
55
+ animation: fadeIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
56
+ }
57
+
58
+ @keyframes fadeIn {
59
+ from { opacity: 0; transform: translateY(10px); }
60
+ to { opacity: 1; transform: translateY(0); }
61
  }
62
 
63
  .user-message {
64
+ background: linear-gradient(135deg, var(--blue), var(--cyan));
65
+ color: #FFFFFF;
66
+ box-shadow: 0 10px 30px rgba(59, 91, 255, 0.2);
67
  }
68
 
69
  .bot-message {
 
71
  border: 1px solid var(--glass-border);
72
  }
73
 
 
 
 
 
 
 
 
 
 
74
  .typing-dot {
75
+ width: 4px;
76
+ height: 4px;
77
+ background: var(--cyan);
78
  border-radius: 50%;
79
  animation: bounce 1.4s infinite ease-in-out;
80
  }
 
86
  40% { transform: scale(1); opacity: 1; }
87
  }
88
 
89
+ .input-pill {
90
+ background: rgba(255, 255, 255, 0.05);
91
+ backdrop-filter: blur(20px);
92
+ -webkit-backdrop-filter: blur(20px);
93
  border: 1px solid var(--glass-border);
94
  transition: all 0.3s ease;
95
  }
96
 
97
+ .input-pill:focus-within {
98
+ border-color: var(--blue);
99
+ box-shadow: 0 0 30px rgba(59, 91, 255, 0.1);
 
100
  }
101
 
102
+ .logo-glow {
103
+ filter: drop-shadow(0 0 10px rgba(39, 212, 234, 0.3));
104
+ }
105
 
106
+ .send-btn {
107
+ background: linear-gradient(135deg, var(--blue), var(--cyan));
108
+ transition: transform 0.2s ease, opacity 0.2s ease;
 
 
109
  }
110
+ .send-btn:active { transform: scale(0.95); }
111
+
112
+ #user-input::placeholder { color: #555; }
113
  </style>
114
  </head>
115
+ <body>
116
 
117
  <!-- Minimalist Header -->
118
+ <header class="h-20 flex items-center justify-between px-6 md:px-12 shrink-0 z-50">
119
+ <div class="flex items-center gap-4">
120
+ <img src="https://cdn-avatars.huggingface.co/v1/production/uploads/1670387859384-633fe7784b362488336bbfad.png"
121
+ alt="OpenBMB" class="w-10 h-10 logo-glow">
122
+ <div>
123
+ <h1 class="text-xl font-bold tracking-tight">MiniCPM-V</h1>
124
+ <p class="text-[10px] text-muted uppercase tracking-[0.2em] font-medium">By OpenBMB</p>
125
+ </div>
126
  </div>
127
+ <div class="hidden md:flex items-center gap-2 text-[10px] font-bold text-muted uppercase tracking-widest">
128
+ <span class="w-1.5 h-1.5 rounded-full bg-[#27D4EA] animate-pulse"></span>
129
+ Vision System Online
130
  </div>
131
  </header>
132
 
133
+ <!-- Chat Messages Scroll Area -->
134
+ <main id="chat-messages" class="chat-scroll-area px-4 md:px-0">
135
+ <div class="max-w-3xl mx-auto space-y-8 pt-4">
136
  <!-- Bot Greeting -->
137
+ <div class="flex gap-4 items-start">
138
+ <div class="bot-message p-6 rounded-3xl rounded-tl-none message-bubble shadow-2xl">
139
  <p class="text-white/90 leading-relaxed text-[15px]">
140
+ Welcome to <span class="font-bold text-[#27D4EA]">MiniCPM-V 4.6</span>.
141
+ I can analyze images and videos with high efficiency.
142
+ <br><br>
143
+ Drop a file below to begin.
144
  </p>
145
  </div>
146
  </div>
147
  </div>
148
+ </main>
149
 
150
+ <!-- Floating Input Bar -->
151
+ <div class="fixed bottom-0 left-0 right-0 p-6 md:p-10 pointer-events-none">
152
+ <div class="max-w-3xl mx-auto pointer-events-auto">
153
+ <!-- Media Preview -->
154
+ <div id="preview-container" class="hidden mb-6 animate-in">
155
+ <div class="relative inline-block group">
156
+ <img id="image-preview" src="" class="h-36 w-auto rounded-3xl border border-white/20 shadow-2xl hidden object-cover" />
157
+ <video id="video-preview" class="h-36 w-auto rounded-3xl border border-white/20 shadow-2xl hidden object-cover" muted loop></video>
158
+ <button id="cancel-file" class="absolute -top-3 -right-3 bg-white text-black rounded-full p-2 shadow-xl hover:bg-neutral-200 transition-all">
159
+ <i data-lucide="x" class="w-4 h-4"></i>
160
+ </button>
 
161
  </div>
162
+ </div>
163
 
164
+ <!-- Pill Input -->
165
+ <div class="input-pill rounded-[2.5rem] p-2 flex items-end gap-2 pr-3 shadow-2xl">
166
+ <div class="flex items-center">
167
+ <input type="file" id="file-input" class="hidden" accept="image/*,video/*">
168
+ <button id="upload-trigger" class="p-4 text-white/30 hover:text-[#27D4EA] transition-colors">
169
+ <i data-lucide="paperclip" class="w-6 h-6"></i>
 
 
 
 
 
 
 
 
170
  </button>
171
  </div>
172
+
173
+ <textarea id="user-input" rows="1" placeholder="Type your message..."
174
+ class="flex-1 bg-transparent border-none focus:ring-0 text-white py-4 px-1 resize-none max-h-40 scrollbar-none text-[16px] leading-relaxed"></textarea>
175
+
176
+ <button id="send-btn" class="send-btn w-12 h-12 text-white rounded-full flex items-center justify-center disabled:opacity-20 disabled:grayscale group shrink-0 mb-1">
177
+ <i data-lucide="arrow-up" class="w-5 h-5 group-hover:scale-110 transition-transform" id="send-icon"></i>
178
+ <i data-lucide="loader-2" class="w-5 h-5 animate-spin hidden" id="loading-icon"></i>
179
+ </button>
180
  </div>
181
  </div>
182
+ </div>
183
 
184
  <script type="module">
185
  import { Client, handle_file } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
 
204
  async function init() {
205
  try {
206
  client = await Client.connect(window.location.origin);
207
+ } catch (err) { console.error("Gradio Connection Error", err); }
208
  }
209
  init();
210
 
211
+ // UI Interactions
212
  uploadTrigger.onclick = () => fileInput.click();
213
  fileInput.onchange = (e) => {
214
  const file = e.target.files[0];
 
239
 
240
  function appendMessage(role, text, mediaUrl = null, mediaType = null) {
241
  const div = document.createElement('div');
242
+ div.className = `flex gap-4 items-start ${role === 'user' ? 'flex-row-reverse' : ''}`;
243
 
244
  let mediaHtml = '';
245
  if (mediaUrl) {
246
  if (mediaType.startsWith('image')) {
247
+ mediaHtml = `<img src="${mediaUrl}" class="max-w-xs md:max-w-md rounded-3xl mb-4 border border-white/10" />`;
248
  } else {
249
+ mediaHtml = `<video src="${mediaUrl}" controls class="max-w-xs md:max-w-md rounded-3xl mb-4 border border-white/10"></video>`;
250
  }
251
  }
252
 
253
  const bubbleClass = role === 'user' ? 'user-message' : 'bot-message';
254
 
255
  div.innerHTML = `
256
+ <div class="${bubbleClass} p-6 rounded-[2rem] ${role === 'user' ? 'rounded-tr-none' : 'rounded-tl-none'} message-bubble shadow-xl">
257
  ${mediaHtml}
258
+ <p class="leading-relaxed text-[15px] whitespace-pre-wrap font-medium">${text}</p>
259
  </div>
260
  `;
261
+
262
+ // Get the inner container
263
+ const container = chatMessages.querySelector('.max-w-3xl');
264
+ container.appendChild(div);
265
+
266
+ // Smooth scroll to bottom
267
  chatMessages.scrollTo({ top: chatMessages.scrollHeight, behavior: 'smooth' });
268
  }
269
 
 
289
  const thinkingId = 'think-' + Date.now();
290
  const thinkingDiv = document.createElement('div');
291
  thinkingDiv.id = thinkingId;
292
+ thinkingDiv.className = 'flex gap-4 items-start';
293
  thinkingDiv.innerHTML = `
294
  <div class="bot-message p-6 rounded-[2rem] rounded-tl-none message-bubble flex items-center gap-4">
295
  <div class="flex gap-1.5">
 
297
  </div>
298
  </div>
299
  `;
300
+ const container = chatMessages.querySelector('.max-w-3xl');
301
+ container.appendChild(thinkingDiv);
302
  chatMessages.scrollTo({ top: chatMessages.scrollHeight, behavior: 'smooth' });
303
 
304
  try {
 
306
  const result = await client.predict("/predict", {
307
  message: content,
308
  file: fileData,
309
+ downsample_mode: "16x"
310
  });
311
 
312
  document.getElementById(thinkingId).remove();
313
  appendMessage('bot', result.data);
314
  } catch (err) {
315
  document.getElementById(thinkingId).remove();
316
+ appendMessage('bot', "The system encountered an error. Please check your file format and try again.");
317
  } finally {
318
  sendIcon.classList.remove('hidden');
319
  loadingIcon.classList.add('hidden');