|
@@ -13,6 +13,8 @@ document.addEventListener("alpine:init", () => {
|
|
|
home: 0,
|
|
|
generating: false,
|
|
|
endpoint: `${window.location.origin}/v1`,
|
|
|
+
|
|
|
+ // Initialize error message structure
|
|
|
errorMessage: null,
|
|
|
errorExpanded: false,
|
|
|
errorTimeout: null,
|
|
@@ -32,12 +34,81 @@ document.addEventListener("alpine:init", () => {
|
|
|
// Pending message storage
|
|
|
pendingMessage: null,
|
|
|
|
|
|
+ modelPoolInterval: null,
|
|
|
+
|
|
|
+ // Add models state alongside existing state
|
|
|
+ models: {},
|
|
|
+
|
|
|
init() {
|
|
|
// Clean up any pending messages
|
|
|
localStorage.removeItem("pendingMessage");
|
|
|
|
|
|
+ // Get initial model list
|
|
|
+ this.fetchInitialModels();
|
|
|
+
|
|
|
// Start polling for download progress
|
|
|
this.startDownloadProgressPolling();
|
|
|
+
|
|
|
+ // Start model polling with the new pattern
|
|
|
+ this.startModelPolling();
|
|
|
+ },
|
|
|
+
|
|
|
+ async fetchInitialModels() {
|
|
|
+ try {
|
|
|
+ const response = await fetch(`${window.location.origin}/initial_models`);
|
|
|
+ if (response.ok) {
|
|
|
+ const initialModels = await response.json();
|
|
|
+ this.models = initialModels;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error fetching initial models:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async startModelPolling() {
|
|
|
+ while (true) {
|
|
|
+ try {
|
|
|
+ await this.populateSelector();
|
|
|
+ // Wait 5 seconds before next poll
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 5000));
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Model polling error:', error);
|
|
|
+ // If there's an error, wait before retrying
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 5000));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async populateSelector() {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const evtSource = new EventSource(`${window.location.origin}/modelpool`);
|
|
|
+
|
|
|
+ evtSource.onmessage = (event) => {
|
|
|
+ if (event.data === "[DONE]") {
|
|
|
+ evtSource.close();
|
|
|
+ resolve();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const modelData = JSON.parse(event.data);
|
|
|
+ // Update existing model data while preserving other properties
|
|
|
+ Object.entries(modelData).forEach(([modelName, data]) => {
|
|
|
+ if (this.models[modelName]) {
|
|
|
+ this.models[modelName] = {
|
|
|
+ ...this.models[modelName],
|
|
|
+ ...data,
|
|
|
+ loading: false
|
|
|
+ };
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ evtSource.onerror = (error) => {
|
|
|
+ console.error('EventSource failed:', error);
|
|
|
+ evtSource.close();
|
|
|
+ reject(error);
|
|
|
+ };
|
|
|
+ });
|
|
|
},
|
|
|
|
|
|
removeHistory(cstate) {
|
|
@@ -74,56 +145,6 @@ document.addEventListener("alpine:init", () => {
|
|
|
return `${s}s`;
|
|
|
},
|
|
|
|
|
|
- async populateSelector() {
|
|
|
- try {
|
|
|
- const response = await fetch(`${window.location.origin}/modelpool`);
|
|
|
- const responseText = await response.text(); // Get raw response text first
|
|
|
-
|
|
|
- if (!response.ok) {
|
|
|
- throw new Error(`HTTP error! status: ${response.status}`);
|
|
|
- }
|
|
|
-
|
|
|
- // Try to parse the response text
|
|
|
- let responseJson;
|
|
|
- try {
|
|
|
- responseJson = JSON.parse(responseText);
|
|
|
- } catch (parseError) {
|
|
|
- console.error('Failed to parse JSON:', parseError);
|
|
|
- throw new Error(`Invalid JSON response: ${responseText}`);
|
|
|
- }
|
|
|
-
|
|
|
- const sel = document.querySelector(".model-select");
|
|
|
- if (!sel) {
|
|
|
- throw new Error("Could not find model selector element");
|
|
|
- }
|
|
|
-
|
|
|
- // Clear the current options and add new ones
|
|
|
- sel.innerHTML = '';
|
|
|
-
|
|
|
- const modelDict = responseJson["model pool"];
|
|
|
- if (!modelDict) {
|
|
|
- throw new Error("Response missing 'model pool' property");
|
|
|
- }
|
|
|
-
|
|
|
- Object.entries(modelDict).forEach(([key, value]) => {
|
|
|
- const opt = document.createElement("option");
|
|
|
- opt.value = key;
|
|
|
- opt.textContent = value;
|
|
|
- sel.appendChild(opt);
|
|
|
- });
|
|
|
-
|
|
|
- // Set initial value to the first model
|
|
|
- const firstKey = Object.keys(modelDict)[0];
|
|
|
- if (firstKey) {
|
|
|
- sel.value = firstKey;
|
|
|
- this.cstate.selectedModel = firstKey;
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error("Error populating model selector:", error);
|
|
|
- this.errorMessage = `Failed to load models: ${error.message}`;
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
async handleImageUpload(event) {
|
|
|
const file = event.target.files[0];
|
|
|
if (file) {
|
|
@@ -169,29 +190,7 @@ document.addEventListener("alpine:init", () => {
|
|
|
this.processMessage(value);
|
|
|
} catch (error) {
|
|
|
console.error('error', error);
|
|
|
- const errorDetails = {
|
|
|
- message: error.message || 'Unknown error',
|
|
|
- stack: error.stack,
|
|
|
- name: error.name || 'Error'
|
|
|
- };
|
|
|
-
|
|
|
- this.errorMessage = {
|
|
|
- basic: `${errorDetails.name}: ${errorDetails.message}`,
|
|
|
- stack: errorDetails.stack
|
|
|
- };
|
|
|
-
|
|
|
- // Clear any existing timeout
|
|
|
- if (this.errorTimeout) {
|
|
|
- clearTimeout(this.errorTimeout);
|
|
|
- }
|
|
|
-
|
|
|
- // Only set the timeout if the error details aren't expanded
|
|
|
- if (!this.errorExpanded) {
|
|
|
- this.errorTimeout = setTimeout(() => {
|
|
|
- this.errorMessage = null;
|
|
|
- this.errorExpanded = false;
|
|
|
- }, 30 * 1000);
|
|
|
- }
|
|
|
+ this.setError(error);
|
|
|
this.generating = false;
|
|
|
}
|
|
|
},
|
|
@@ -309,29 +308,7 @@ document.addEventListener("alpine:init", () => {
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('error', error);
|
|
|
- const errorDetails = {
|
|
|
- message: error.message || 'Unknown error',
|
|
|
- stack: error.stack,
|
|
|
- name: error.name || 'Error'
|
|
|
- };
|
|
|
-
|
|
|
- this.errorMessage = {
|
|
|
- basic: `${errorDetails.name}: ${errorDetails.message}`,
|
|
|
- stack: errorDetails.stack
|
|
|
- };
|
|
|
-
|
|
|
- // Clear any existing timeout
|
|
|
- if (this.errorTimeout) {
|
|
|
- clearTimeout(this.errorTimeout);
|
|
|
- }
|
|
|
-
|
|
|
- // Only set the timeout if the error details aren't expanded
|
|
|
- if (!this.errorExpanded) {
|
|
|
- this.errorTimeout = setTimeout(() => {
|
|
|
- this.errorMessage = null;
|
|
|
- this.errorExpanded = false;
|
|
|
- }, 30 * 1000);
|
|
|
- }
|
|
|
+ this.setError(error);
|
|
|
} finally {
|
|
|
this.generating = false;
|
|
|
}
|
|
@@ -467,6 +444,106 @@ document.addEventListener("alpine:init", () => {
|
|
|
this.fetchDownloadProgress();
|
|
|
}, 1000); // Poll every second
|
|
|
},
|
|
|
+
|
|
|
+ // Add a helper method to set errors consistently
|
|
|
+ setError(error) {
|
|
|
+ this.errorMessage = {
|
|
|
+ basic: error.message || "An unknown error occurred",
|
|
|
+ stack: error.stack || ""
|
|
|
+ };
|
|
|
+ this.errorExpanded = false;
|
|
|
+
|
|
|
+ if (this.errorTimeout) {
|
|
|
+ clearTimeout(this.errorTimeout);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.errorExpanded) {
|
|
|
+ this.errorTimeout = setTimeout(() => {
|
|
|
+ this.errorMessage = null;
|
|
|
+ this.errorExpanded = false;
|
|
|
+ }, 30 * 1000);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async deleteModel(modelName, model) {
|
|
|
+ const downloadedSize = model.total_downloaded || 0;
|
|
|
+ const sizeMessage = downloadedSize > 0 ?
|
|
|
+ `This will free up ${this.formatBytes(downloadedSize)} of space.` :
|
|
|
+ 'This will remove any partially downloaded files.';
|
|
|
+
|
|
|
+ if (!confirm(`Are you sure you want to delete ${model.name}? ${sizeMessage}`)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(`${window.location.origin}/models/${modelName}`, {
|
|
|
+ method: 'DELETE',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const data = await response.json();
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(data.detail || 'Failed to delete model');
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update the model status in the UI
|
|
|
+ if (this.models[modelName]) {
|
|
|
+ this.models[modelName].downloaded = false;
|
|
|
+ this.models[modelName].download_percentage = 0;
|
|
|
+ this.models[modelName].total_downloaded = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // If this was the selected model, switch to a different one
|
|
|
+ if (this.cstate.selectedModel === modelName) {
|
|
|
+ const availableModel = Object.keys(this.models).find(key => this.models[key].downloaded);
|
|
|
+ this.cstate.selectedModel = availableModel || 'llama-3.2-1b';
|
|
|
+ }
|
|
|
+
|
|
|
+ // Show success message
|
|
|
+ console.log(`Model deleted successfully from: ${data.path}`);
|
|
|
+
|
|
|
+ // Refresh the model list
|
|
|
+ await this.populateSelector();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error deleting model:', error);
|
|
|
+ this.setError(error.message || 'Failed to delete model');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async handleDownload(modelName) {
|
|
|
+ try {
|
|
|
+ const response = await fetch(`${window.location.origin}/download`, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
+ },
|
|
|
+ body: JSON.stringify({
|
|
|
+ model: modelName
|
|
|
+ })
|
|
|
+ });
|
|
|
+
|
|
|
+ const data = await response.json();
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(data.error || 'Failed to start download');
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update the model's status immediately when download starts
|
|
|
+ if (this.models[modelName]) {
|
|
|
+ this.models[modelName] = {
|
|
|
+ ...this.models[modelName],
|
|
|
+ loading: true
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error starting download:', error);
|
|
|
+ this.setError(error);
|
|
|
+ }
|
|
|
+ }
|
|
|
}));
|
|
|
});
|
|
|
|