Spaces:
Running
Running
Update index.html
Browse files- 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
|
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
|
77 |
-
<div id="model-status" class="info">Click button to load
|
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
|
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;
|
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 = '
|
127 |
-
const historyKey = '
|
128 |
|
129 |
// Speech API
|
130 |
let recognition = null;
|
@@ -139,12 +138,12 @@
|
|
139 |
updateSpeakerButtonUI();
|
140 |
initializeSpeechAPI();
|
141 |
setupInputAutosize();
|
142 |
-
updateChatUIState(false);
|
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}
|
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
|
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
|
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 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
|
|
220 |
} else {
|
221 |
errorMsg += " Unknown error. Check console/network/memory.";
|
222 |
}
|
@@ -226,85 +221,57 @@
|
|
226 |
}
|
227 |
}
|
228 |
|
229 |
-
// Build messages array
|
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
|
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 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
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;
|
267 |
-
|
268 |
-
|
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
|
282 |
const outputs = await generator(messages, {
|
283 |
-
max_new_tokens: 512,
|
284 |
-
do_sample: false
|
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 |
-
|
293 |
-
|
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);
|
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 |
|