kimhyunwoo commited on
Commit
6b44370
ยท
verified ยท
1 Parent(s): d5b0fae

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +41 -74
index.html CHANGED
@@ -3,9 +3,9 @@
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>AI Assistant (Gemma 3 1B - Exact Doc Example)</title>
7
  <style>
8
- /* CSS๋Š” ์ด์ „๊ณผ ๋™์ผ (๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด ์ผ๋ถ€๋งŒ ํ‘œ์‹œ) */
9
  @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
10
  :root { /* Using the neutral blue theme */
11
  --primary-color: #007bff; --secondary-color: #6c757d; --text-color: #212529;
@@ -73,8 +73,8 @@
73
  <div id="control-panel">
74
  <h2>Model Loader</h2>
75
  <!-- Button to explicitly trigger model loading -->
76
- <button id="loadModelButton">Load Gemma 3 1B Model (Q4)</button>
77
- <div id="model-status" class="info">Click button to load Gemma 3 1B using the exact method from the documentation. **Warning:** Loading is still expected to fail due to library incompatibility.</div>
78
  </div>
79
 
80
  <div id="chat-container">
@@ -95,13 +95,13 @@
95
  <script type="module">
96
  import { pipeline, env } from '@xenova/transformers';
97
 
98
- // Configuration EXACTLY as per the model card example
99
- const MODEL_NAME = 'onnx-community/gemma-3-1b-it-ONNX-GQA';
100
  const TASK = 'text-generation';
101
- const QUANTIZATION = 'q4';
102
 
103
  // Environment setup
104
- env.allowRemoteModels = true; // Usually default, but set explicitly
105
  env.useBrowserCache = true;
106
  env.backends.onnx.executionProviders = ['webgpu', 'wasm'];
107
  console.log('Using Execution Providers:', env.backends.onnx.executionProviders);
@@ -120,11 +120,10 @@
120
  // State
121
  let generator = null;
122
  let isLoadingModel = false;
123
- // Store history as { role: 'user' | 'assistant' | 'system', content: '...' }
124
  let conversationHistory = [];
125
  let botState = { botName: "AI Assistant", userName: "User", botSettings: { useSpeechOutput: true } };
126
- const stateKey = 'gemma3_1b_exact_doc_state_v1';
127
- const historyKey = 'gemma3_1b_exact_doc_history_v1';
128
 
129
  // Speech API
130
  let recognition = null;
@@ -139,12 +138,12 @@
139
  updateSpeakerButtonUI();
140
  initializeSpeechAPI();
141
  setupInputAutosize();
142
- updateChatUIState(false); // Initial state: disabled
143
  displayHistory();
144
  setTimeout(loadVoices, 500);
145
  loadModelButton.addEventListener('click', handleLoadModelClick);
146
  console.log("Attempting to use Transformers.js (latest) loaded via import map.");
147
- displayMessage('system', `Using latest Transformers.js. Ready to load ${MODEL_NAME} exactly as per doc example.`, false);
148
  });
149
 
150
  // --- State Persistence ---
@@ -157,15 +156,9 @@
157
  localStorage.setItem(historyKey, JSON.stringify(conversationHistory));
158
  }
159
  function displayHistory() {
160
- chatbox.innerHTML = '';
161
- conversationHistory.forEach(msg => {
162
- if (msg.role === 'user' || msg.role === 'assistant') {
163
- displayMessage(msg.role === 'user' ? 'user' : 'bot', msg.content, false);
164
- }
165
- });
166
  }
167
 
168
-
169
  // --- UI Update Functions ---
170
  function displayMessage(sender, text, animate = true, isError = false) {
171
  const messageDiv = document.createElement('div'); let messageClass = sender === 'user' ? 'user-message' : sender === 'bot' ? 'bot-message' : 'system-message'; if (sender === 'system' && isError) messageClass = 'error-message'; messageDiv.classList.add(messageClass); if (!animate) messageDiv.style.animation = 'none'; text = text.replace(/</g, "<").replace(/>/g, ">"); text = text.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'); text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\*(.*?)\*/g, '<em>$1</em>'); text = text.replace(/\n/g, '<br>'); messageDiv.innerHTML = text; chatbox.appendChild(messageDiv); chatbox.scrollTo({ top: chatbox.scrollHeight, behavior: animate ? 'smooth' : 'auto' });
@@ -187,20 +180,21 @@
187
  if (isLoadingModel || generator) return;
188
  isLoadingModel = true; generator = null;
189
  updateChatUIState(false);
 
190
  await initializeModel(MODEL_NAME);
191
  isLoadingModel = false;
192
  updateChatUIState(generator !== null);
193
  }
194
 
195
- // Initialize model EXACTLY as per the documentation example
196
  async function initializeModel(modelId) {
197
  updateModelStatus(`Loading ${modelId} with { dtype: "${QUANTIZATION}" }... (Strict doc example)`, 'loading');
198
  displayMessage('system', `Attempting to load ${modelId} using documented method (dtype: ${QUANTIZATION})...`, false);
199
 
200
  try {
201
- // Pipeline creation EXACTLY as in the example
202
  generator = await pipeline(TASK, modelId, {
203
- dtype: QUANTIZATION,
204
  progress_callback: (progress) => {
205
  const msg = `[Loading: ${progress.status}] ${progress.file ? progress.file.split('/').pop() : ''} (${Math.round(progress.progress || 0)}%)`;
206
  updateModelStatus(msg, 'loading');
@@ -213,10 +207,11 @@
213
  } catch (error) {
214
  console.error(`Model loading failed for ${modelId} (Strict Attempt):`, error);
215
  let errorMsg = `Failed to load ${modelId}: ${error.message}.`;
216
- if (error.message.includes("Unsupported model type") || error.message.includes("gemma3_text")) {
217
- errorMsg += " As expected, the 'gemma3_text' model type is likely unsupported.";
218
- } else if (error.message.includes("split is not a function")) {
219
- errorMsg += " As expected, a TypeError occurred during config parsing (incompatibility).";
 
220
  } else {
221
  errorMsg += " Unknown error. Check console/network/memory.";
222
  }
@@ -226,85 +221,57 @@
226
  }
227
  }
228
 
229
- // Build messages array EXACTLY as per documentation example
230
  function buildMessages(newUserMessage) {
231
- // Start with system prompt, add history, then user message
232
  let messages = [{ role: "system", content: "You are a helpful assistant." }];
233
- // Append history (already in correct format)
234
  messages = messages.concat(conversationHistory);
235
- // Append new user message
236
  messages.push({ role: "user", content: newUserMessage });
237
  console.log("Input Messages for Pipeline:", messages);
238
  return messages;
239
  }
240
 
241
- // Cleanup response EXACTLY as per documentation example (with safety checks)
242
  function cleanupResponse(output) {
243
  try {
244
- // Check the structure expected when using messages input
245
  if (output && output.length > 0 && output[0].generated_text && Array.isArray(output[0].generated_text)) {
246
- // Use .at(-1) to get the last element, which should be the assistant's response
247
  const lastMessage = output[0].generated_text.at(-1);
248
  if (lastMessage && (lastMessage.role === 'assistant' || lastMessage.role === 'model') && typeof lastMessage.content === 'string') {
249
  let content = lastMessage.content.trim();
250
- // Optional: Remove potential trailing artifacts if needed
251
  content = content.replace(/<end_of_turn>/g, '').trim();
252
  if (content.length > 0) return content;
253
  }
254
  }
255
- } catch (e) { console.error("Error parsing generator output with .at(-1):", e, "Output:", output); }
256
-
257
- // Fallback if the specific structure isn't found
258
- console.warn("Could not extract response using output[0].generated_text.at(-1).content. Output structure might differ or generation failed.", output);
259
- const fallbacks = [ "Sorry, response format was unexpected.", "My response might be garbled.", "Error processing the AI answer." ];
260
- return fallbacks[Math.floor(Math.random() * fallbacks.length)];
261
  }
262
 
263
  // --- Main Interaction Logic ---
264
  async function handleUserMessage() {
265
  const userText = userInput.value.trim();
266
- if (!userText || !generator || isLoadingModel) return; // Check if generator is ready
267
-
268
- userInput.value = ''; userInput.style.height = 'auto';
269
- updateChatUIState(true); // Disable input
270
-
271
- // Add user message to UI and history
272
- displayMessage('user', userText);
273
- conversationHistory.push({ role: 'user', content: userText });
274
-
275
  updateModelStatus("AI thinking...", "loading");
276
-
277
- // Prepare messages array
278
- const messages = buildMessages(userText);
279
 
280
  try {
281
- // Call generator EXACTLY as in the example (with messages array)
282
  const outputs = await generator(messages, {
283
- max_new_tokens: 512, // From example
284
- do_sample: false // From example
285
- // Add other parameters like temperature if needed for sampling
286
  });
287
-
288
- const replyText = cleanupResponse(outputs); // Use the cleanup function based on example
289
-
290
  console.log("Cleaned AI Output:", replyText);
291
-
292
- // Add AI response to UI and history
293
- displayMessage('bot', replyText);
294
- conversationHistory.push({ role: 'assistant', content: replyText }); // Add assistant response
295
-
296
- if (botState.botSettings.useSpeechOutput && synthesis && targetVoice) {
297
- speakText(replyText);
298
- }
299
- saveState(); // Save history
300
-
301
  } catch (error) {
302
- console.error("AI response generation error:", error);
303
- displayMessage('system', `[ERROR] Failed to generate response: ${error.message}`, true, true);
304
  } finally {
305
  if(generator) updateModelStatus(`${MODEL_NAME} ready.`, "success");
306
- updateChatUIState(generator !== null); // Re-enable UI
307
- userInput.focus();
308
  }
309
  }
310
 
 
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>AI Assistant (Gemma 3 1B ONNX Attempt)</title> {/* Updated Title */}
7
  <style>
8
+ /* CSS๋Š” ์ด์ „๊ณผ ๋™์ผ */
9
  @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
10
  :root { /* Using the neutral blue theme */
11
  --primary-color: #007bff; --secondary-color: #6c757d; --text-color: #212529;
 
73
  <div id="control-panel">
74
  <h2>Model Loader</h2>
75
  <!-- Button to explicitly trigger model loading -->
76
+ <button id="loadModelButton">Load Gemma 3 1B ONNX (Q4)</button> {/* Updated Button Text */}
77
+ <div id="model-status" class="info">Click button to load `onnx-community/gemma-3-1b-it-ONNX` (Q4). **Warning:** Loading may still fail due to library incompatibility.</div>
78
  </div>
79
 
80
  <div id="chat-container">
 
95
  <script type="module">
96
  import { pipeline, env } from '@xenova/transformers';
97
 
98
+ // Configuration based on the LATEST user request
99
+ const MODEL_NAME = 'onnx-community/gemma-3-1b-it-ONNX'; // Using the non-GQA version
100
  const TASK = 'text-generation';
101
+ const QUANTIZATION = 'q4'; // Using Q4 as specified in the example structure
102
 
103
  // Environment setup
104
+ env.allowRemoteModels = true;
105
  env.useBrowserCache = true;
106
  env.backends.onnx.executionProviders = ['webgpu', 'wasm'];
107
  console.log('Using Execution Providers:', env.backends.onnx.executionProviders);
 
120
  // State
121
  let generator = null;
122
  let isLoadingModel = false;
 
123
  let conversationHistory = [];
124
  let botState = { botName: "AI Assistant", userName: "User", botSettings: { useSpeechOutput: true } };
125
+ const stateKey = 'gemma3_1b_onnx_state_v1'; // New key for this specific model
126
+ const historyKey = 'gemma3_1b_onnx_history_v1';
127
 
128
  // Speech API
129
  let recognition = null;
 
138
  updateSpeakerButtonUI();
139
  initializeSpeechAPI();
140
  setupInputAutosize();
141
+ updateChatUIState(false);
142
  displayHistory();
143
  setTimeout(loadVoices, 500);
144
  loadModelButton.addEventListener('click', handleLoadModelClick);
145
  console.log("Attempting to use Transformers.js (latest) loaded via import map.");
146
+ displayMessage('system', `Using latest Transformers.js. Ready to load ${MODEL_NAME}.`, false);
147
  });
148
 
149
  // --- State Persistence ---
 
156
  localStorage.setItem(historyKey, JSON.stringify(conversationHistory));
157
  }
158
  function displayHistory() {
159
+ chatbox.innerHTML = ''; conversationHistory.forEach(msg => { if (msg.role === 'user' || msg.role === 'assistant') { displayMessage(msg.role === 'user' ? 'user' : 'bot', msg.content, false); } });
 
 
 
 
 
160
  }
161
 
 
162
  // --- UI Update Functions ---
163
  function displayMessage(sender, text, animate = true, isError = false) {
164
  const messageDiv = document.createElement('div'); let messageClass = sender === 'user' ? 'user-message' : sender === 'bot' ? 'bot-message' : 'system-message'; if (sender === 'system' && isError) messageClass = 'error-message'; messageDiv.classList.add(messageClass); if (!animate) messageDiv.style.animation = 'none'; text = text.replace(/</g, "<").replace(/>/g, ">"); text = text.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'); text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\*(.*?)\*/g, '<em>$1</em>'); text = text.replace(/\n/g, '<br>'); messageDiv.innerHTML = text; chatbox.appendChild(messageDiv); chatbox.scrollTo({ top: chatbox.scrollHeight, behavior: animate ? 'smooth' : 'auto' });
 
180
  if (isLoadingModel || generator) return;
181
  isLoadingModel = true; generator = null;
182
  updateChatUIState(false);
183
+ // Pass the specific model name requested by the user
184
  await initializeModel(MODEL_NAME);
185
  isLoadingModel = false;
186
  updateChatUIState(generator !== null);
187
  }
188
 
189
+ // Initialize model using the exact parameters from the latest example structure
190
  async function initializeModel(modelId) {
191
  updateModelStatus(`Loading ${modelId} with { dtype: "${QUANTIZATION}" }... (Strict doc example)`, 'loading');
192
  displayMessage('system', `Attempting to load ${modelId} using documented method (dtype: ${QUANTIZATION})...`, false);
193
 
194
  try {
195
+ // Pipeline creation EXACTLY as per the example structure provided by user
196
  generator = await pipeline(TASK, modelId, {
197
+ dtype: QUANTIZATION, // Explicitly use q4
198
  progress_callback: (progress) => {
199
  const msg = `[Loading: ${progress.status}] ${progress.file ? progress.file.split('/').pop() : ''} (${Math.round(progress.progress || 0)}%)`;
200
  updateModelStatus(msg, 'loading');
 
207
  } catch (error) {
208
  console.error(`Model loading failed for ${modelId} (Strict Attempt):`, error);
209
  let errorMsg = `Failed to load ${modelId}: ${error.message}.`;
210
+ // Provide specific feedback based on likely errors
211
+ if (error.message.includes("Unsupported model type") || error.message.includes("gemma3_text")) {
212
+ errorMsg += " The 'gemma3_text' model type is likely unsupported by this library version.";
213
+ } else if (error.message.includes("split is not a function")) {
214
+ errorMsg += " A TypeError occurred, likely due to config parsing incompatibility.";
215
  } else {
216
  errorMsg += " Unknown error. Check console/network/memory.";
217
  }
 
221
  }
222
  }
223
 
224
+ // Build messages array as per documentation example
225
  function buildMessages(newUserMessage) {
 
226
  let messages = [{ role: "system", content: "You are a helpful assistant." }];
 
227
  messages = messages.concat(conversationHistory);
 
228
  messages.push({ role: "user", content: newUserMessage });
229
  console.log("Input Messages for Pipeline:", messages);
230
  return messages;
231
  }
232
 
233
+ // Cleanup response as per documentation example structure
234
  function cleanupResponse(output) {
235
  try {
 
236
  if (output && output.length > 0 && output[0].generated_text && Array.isArray(output[0].generated_text)) {
 
237
  const lastMessage = output[0].generated_text.at(-1);
238
  if (lastMessage && (lastMessage.role === 'assistant' || lastMessage.role === 'model') && typeof lastMessage.content === 'string') {
239
  let content = lastMessage.content.trim();
 
240
  content = content.replace(/<end_of_turn>/g, '').trim();
241
  if (content.length > 0) return content;
242
  }
243
  }
244
+ } catch (e) { console.error("Error parsing generator output with .at(-1):", e, "Output:", output); }
245
+ console.warn("Could not extract response using output[0].generated_text.at(-1).content. Output structure might differ or generation failed.", output);
246
+ const fallbacks = [ "Sorry, response format was unexpected.", "My response might be garbled.", "Error processing the AI answer." ];
247
+ return fallbacks[Math.floor(Math.random() * fallbacks.length)];
 
 
248
  }
249
 
250
  // --- Main Interaction Logic ---
251
  async function handleUserMessage() {
252
  const userText = userInput.value.trim();
253
+ if (!userText || !generator || isLoadingModel) return;
254
+ userInput.value = ''; userInput.style.height = 'auto'; updateChatUIState(true);
255
+ displayMessage('user', userText); conversationHistory.push({ role: 'user', content: userText });
 
 
 
 
 
 
256
  updateModelStatus("AI thinking...", "loading");
257
+ const messages = buildMessages(userText); // Use messages format
 
 
258
 
259
  try {
260
+ // Call generator EXACTLY as in the example
261
  const outputs = await generator(messages, {
262
+ max_new_tokens: 512,
263
+ do_sample: false // From example
 
264
  });
265
+ const replyText = cleanupResponse(outputs);
 
 
266
  console.log("Cleaned AI Output:", replyText);
267
+ displayMessage('bot', replyText); conversationHistory.push({ role: 'assistant', content: replyText });
268
+ if (botState.botSettings.useSpeechOutput && synthesis && targetVoice) { speakText(replyText); }
269
+ saveState();
 
 
 
 
 
 
 
270
  } catch (error) {
271
+ console.error("AI response generation error:", error); displayMessage('system', `[ERROR] Failed to generate response: ${error.message}`, true, true);
 
272
  } finally {
273
  if(generator) updateModelStatus(`${MODEL_NAME} ready.`, "success");
274
+ updateChatUIState(generator !== null); userInput.focus();
 
275
  }
276
  }
277