Spaces:
Running
Running
Download webui/components/projects/projects-store.js from Leon4gr45/openoperator: direct link, hf CLI and curl.
- Browser
- Download file 19 kB
-
https://huggingface.co/spaces/Leon4gr45/openoperator/resolve/main/webui/components/projects/projects-store.js
- Command line
-
hf download hf://spaces/Leon4gr45/openoperator/webui/components/projects/projects-store.js
-
curl -L -o projects-store.js https://huggingface.co/spaces/Leon4gr45/openoperator/resolve/main/webui/components/projects/projects-store.js
19 kB
| import { createStore } from "/js/AlpineStore.js"; | |
| import * as api from "/js/api.js"; | |
| import * as modals from "/js/modals.js"; | |
| import * as notifications from "/components/notifications/notification-store.js"; | |
| import { store as chatsStore } from "/components/sidebar/chats/chats-store.js"; | |
| import { store as browserStore } from "/components/modals/file-browser/file-browser-store.js"; | |
| import { store as skillsImportStore } from "/components/settings/skills/skills-import-store.js"; | |
| import { store as modelConfigStore } from "/plugins/_model_config/webui/model-config-store.js"; | |
| import * as shortcuts from "/js/shortcuts.js"; | |
| import { showConfirmDialog } from "/js/confirmDialog.js"; | |
| const listModal = "projects/project-list.html"; | |
| const createModal = "projects/project-create.html"; | |
| const editModal = "projects/project-edit.html"; | |
| // define the model object holding data and functions | |
| const model = { | |
| projectList: [], | |
| selectedProject: null, | |
| editData: null, | |
| colors: [ | |
| "#7b2cbf", // Deep Purple | |
| "#8338ec", // Blue Violet | |
| "#9b5de5", // Amethyst | |
| "#d0bfff", // Lavender | |
| "#002975ff", // Prussian Blue | |
| "#3a86ff", // Azure | |
| "#0077b6", // Star Command Blue | |
| "#4cc9f0", // Bright Blue | |
| "#00bbf9", // Deep Sky Blue | |
| "#a5d8ff", // Baby Blue | |
| "#00f5d4", // Electric Blue | |
| "#06d6a0", // Teal | |
| "#1a7431", // Dartmouth Green | |
| "#2a9d8f", // Jungle Green | |
| "#b2f2bb", // Light Mint | |
| "#9ef01a", // Lime Green | |
| "#e9c46a", // Saffron | |
| "#fee440", // Lemon Yellow | |
| "#ffec99", // Pale Yellow | |
| "#ff9f43", // Bright Orange | |
| "#fb5607", // Orange Peel | |
| "#ffddb5", // Peach | |
| "#f95738", // Coral | |
| "#e76f51", // Burnt Sienna | |
| "#ff6b6b", // Vibrant Red | |
| "#ffc9c9", // Light Coral | |
| "#f15bb5", // Hot Pink | |
| "#ff006e", // Magenta | |
| "#ffafcc", // Carnation Pink | |
| "#adb5bd", // Cool Gray | |
| "#6c757d", // Slate Gray | |
| ], | |
| _toFolderName(str) { | |
| if (!str) return ""; | |
| // a helper function to convert title to a folder safe name | |
| const s = str | |
| .normalize("NFD") // remove all diacritics and replace it with the latin character | |
| .replace(/[\u0300-\u036f]/g, "") | |
| .toLowerCase() | |
| .replace(/[^a-z0-9\s-]/g, "_") // replace all special symbols with _ | |
| .replace(/\s+/g, "_") // replace spaces with _ | |
| .replace(/_{2,}/g, "_") // condense multiple underscores into 1 | |
| .replace(/^-+|-+$/g, "") // remove any leading and trailing underscores | |
| .replace(/^_+|_+$/g, ""); | |
| return s; | |
| }, | |
| getSelectedProjectSkillsPath() { | |
| const projectName = this.selectedProject?.name; | |
| if (!projectName) return ""; | |
| return `usr/projects/${projectName}/.a0proj/skills/`; | |
| }, | |
| async openSelectedProjectSkillsImport() { | |
| const projectName = this.selectedProject?.name; | |
| if (!projectName) return; | |
| skillsImportStore.projectKey = projectName; | |
| skillsImportStore.agentProfileKey = ""; | |
| await modals.openModal("settings/skills/import.html"); | |
| }, | |
| async openSelectedProjectSkillsFolder() { | |
| const path = this.getSelectedProjectSkillsPath(); | |
| if (!path) return; | |
| await browserStore.open(path); | |
| }, | |
| async openProjectsModal() { | |
| await this.loadProjectsList(); | |
| await modals.openModal(listModal); | |
| }, | |
| async openCreateModal() { | |
| this.selectedProject = await this._createNewProjectData(); | |
| await modals.openModal(createModal); | |
| this.selectedProject = null; | |
| }, | |
| async openEditModal(name) { | |
| this.selectedProject = await this._createEditProjectData(name); | |
| await modals.openModal(editModal); | |
| this.selectedProject = null; | |
| }, | |
| async cancelCreate() { | |
| await modals.closeModal(createModal); | |
| }, | |
| async cancelEdit() { | |
| await modals.closeModal(editModal); | |
| }, | |
| async confirmCreate() { | |
| // If git_url is provided, use clone flow | |
| if (this.selectedProject.git_url && this.selectedProject.git_url.trim()) { | |
| await this.cloneProject(); | |
| return; | |
| } | |
| // create folder name based on title | |
| this.selectedProject.name = this._toFolderName(this.selectedProject.title); | |
| const project = await this.saveSelectedProject(true); | |
| await this.loadProjectsList(); | |
| await modals.closeModal(createModal); | |
| await this.openEditModal(project.name); | |
| }, | |
| async cloneProject() { | |
| // Security warning with custom dialog | |
| const confirmed = await showConfirmDialog({ | |
| title: "Security Warning", | |
| message: ` | |
| <p><strong>Cloning repositories from untrusted sources may pose security risks:</strong></p> | |
| <ul style="margin: 0.75em 0; padding-left: 1.5em;"> | |
| <li>Malicious code execution</li> | |
| <li>Exposure of sensitive data</li> | |
| <li>System compromise</li> | |
| </ul> | |
| <p style="margin-top: 0.75em;">Only clone from sources you trust.</p> | |
| `, | |
| type: "warning", | |
| confirmText: "Clone Anyway", | |
| cancelText: "Cancel" | |
| }); | |
| if (!confirmed) return; | |
| // Save reference before async operations | |
| const project = this.selectedProject; | |
| if (!project) return; | |
| // Disable button state handled by UI | |
| project._cloning = true; | |
| project.name = this._toFolderName(project.title); | |
| try { | |
| const response = await api.callJsonApi("projects", { | |
| action: "clone", | |
| project: { | |
| name: project.name, | |
| title: project.title, | |
| color: project.color, | |
| git_url: project.git_url, | |
| include_agents_md: project.include_agents_md !== false, | |
| git_token: project.git_token || "", | |
| llm: project.llm || null, | |
| }, | |
| }); | |
| if (response?.ok) { | |
| await this.loadProjectsList(); | |
| await modals.closeModal(createModal); | |
| await this.openEditModal(response.data.name); | |
| } else { | |
| notifications.toastFrontendError( | |
| response?.error || "Clone failed", | |
| "Git Clone", | |
| 5, | |
| "git_clone", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| } | |
| } catch (error) { | |
| console.error("Error cloning project:", error); | |
| notifications.toastFrontendError( | |
| "Error cloning project: " + error, | |
| "Git Clone", | |
| 5, | |
| "git_clone", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| } finally { | |
| // Use the saved reference instead of this.selectedProject | |
| if (project) { | |
| project._cloning = false; | |
| } | |
| } | |
| }, | |
| async confirmEdit() { | |
| const project = await this.saveSelectedProject(false); | |
| await this.loadProjectsList(); | |
| await modals.closeModal(editModal); | |
| }, | |
| async activateProject(name) { | |
| try { | |
| const response = await api.callJsonApi("projects", { | |
| action: "activate", | |
| context_id: chatsStore.getSelectedChatId(), | |
| name: name, | |
| }); | |
| if (response?.ok) { | |
| notifications.toastFrontendSuccess( | |
| "Project activated successfully", | |
| "Project activated", | |
| 3, | |
| "projects", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| } else { | |
| notifications.toastFrontendWarning( | |
| response?.error || "Project activation reported issues", | |
| "Project activation", | |
| 5, | |
| "projects", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| } | |
| } catch (error) { | |
| console.error("Error activating project:", error); | |
| notifications.toastFrontendError( | |
| "Error activating project: " + error, | |
| "Error activating project", | |
| 5, | |
| "projects", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| } | |
| await this.loadProjectsList(); | |
| }, | |
| async deactivateProject() { | |
| try { | |
| const response = await api.callJsonApi("projects", { | |
| action: "deactivate", | |
| context_id: chatsStore.getSelectedChatId(), | |
| }); | |
| if (response?.ok) { | |
| notifications.toastFrontendSuccess( | |
| "Project deactivated successfully", | |
| "Project deactivated", | |
| 3, | |
| "projects", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| } else { | |
| notifications.toastFrontendWarning( | |
| response?.error || "Project deactivation reported issues", | |
| "Project deactivated", | |
| 5, | |
| "projects", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| } | |
| } catch (error) { | |
| console.error("Error deactivating project:", error); | |
| notifications.toastFrontendError( | |
| "Error deactivating project: " + error, | |
| "Error deactivating project", | |
| 5, | |
| "projects", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| } | |
| await this.loadProjectsList(); | |
| }, | |
| async deleteProjectAndCloseModal() { | |
| await this.deleteProject(this.selectedProject.name); | |
| await modals.closeModal(editModal); | |
| }, | |
| async deleteProject(name) { | |
| // show confirmation dialog before proceeding | |
| const confirmed = window.confirm( | |
| "Are you sure you want to permanently delete this project? This action is irreversible and ALL FILES will be deleted." | |
| ); | |
| if (!confirmed) return; | |
| try { | |
| const response = await api.callJsonApi("projects", { | |
| action: "delete", | |
| name: name, | |
| }); | |
| if (response.ok) { | |
| notifications.toastFrontendSuccess( | |
| "Project deleted successfully", | |
| "Project deleted", | |
| 3, | |
| "projects", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| await this.loadProjectsList(); | |
| } else { | |
| notifications.toastFrontendWarning( | |
| response.error || "Project deletion blocked", | |
| "Project delete", | |
| 5, | |
| "projects", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| } | |
| } catch (error) { | |
| console.error("Error deleting project:", error); | |
| notifications.toastFrontendError( | |
| "Error deleting project: " + error, | |
| "Error deleting project", | |
| 5, | |
| "projects", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| } | |
| }, | |
| async loadProjectsList() { | |
| this.loading = true; | |
| try { | |
| const response = await api.callJsonApi("projects", { | |
| action: "list", | |
| }); | |
| this.projectList = response.data || []; | |
| } catch (error) { | |
| console.error("Error loading projects list:", error); | |
| } finally { | |
| this.loading = false; | |
| } | |
| }, | |
| async saveSelectedProject(creating) { | |
| try { | |
| // prepare data | |
| const data = { | |
| ...this.selectedProject, | |
| }; | |
| // remove internal fields | |
| for (const kvp of Object.entries(data)) | |
| if (kvp[0].startsWith("_")) delete data[kvp[0]]; | |
| // call backend | |
| const response = await api.callJsonApi("projects", { | |
| action: creating ? "create" : "update", | |
| project: data, | |
| }); | |
| // notifications | |
| if (response.ok) { | |
| notifications.toastFrontendSuccess( | |
| "Project saved successfully", | |
| "Project saved", | |
| 3, | |
| "projects", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| const contextId = chatsStore.getSelectedChatId(); | |
| if (contextId) await modelConfigStore.refreshSwitcher(contextId); | |
| return response.data; | |
| } else { | |
| notifications.toastFrontendError( | |
| response.error || "Error saving project", | |
| "Error saving project", | |
| 5, | |
| "projects", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| return null; | |
| } | |
| } catch (error) { | |
| console.error("Error saving project:", error); | |
| notifications.toastFrontendError( | |
| "Error saving project: " + error, | |
| "Error saving project", | |
| 5, | |
| "projects", | |
| notifications.NotificationPriority.NORMAL, | |
| true | |
| ); | |
| return null; | |
| } | |
| }, | |
| async _createNewProjectData() { | |
| return { | |
| _meta: { | |
| creating: true, | |
| }, | |
| _cloning: false, | |
| name: ``, | |
| title: `Project #${this.projectList.length + 1}`, | |
| description: "", | |
| instructions: "", | |
| include_agents_md: true, | |
| color: "", | |
| git_url: "", | |
| git_token: "", | |
| }; | |
| }, | |
| async _createEditProjectData(name) { | |
| const projectData = ( | |
| await api.callJsonApi("projects", { | |
| action: "load", | |
| name: name, | |
| }) | |
| ).data; | |
| return { | |
| _meta: { | |
| creating: false, | |
| }, | |
| ...projectData, | |
| include_agents_md: projectData.include_agents_md !== false, | |
| llm: this._normalizeProjectLlmData(projectData.llm, name), | |
| }; | |
| }, | |
| async _createProjectLlmData(projectName) { | |
| await modelConfigStore.ensureLoaded(); | |
| const configResult = await api.callJsonApi("/plugins/_model_config/model_config_get", { | |
| project_name: projectName || "", | |
| }); | |
| const presetsResult = await api.callJsonApi("/plugins/_model_config/model_presets", { | |
| action: "get", | |
| project_name: projectName || "", | |
| }); | |
| return this._normalizeProjectLlmData({ | |
| selected_preset: { | |
| scope: "global", | |
| project_name: "", | |
| name: configResult.selected_preset || configResult.configured_preset || "Default", | |
| }, | |
| global_presets: presetsResult.global_presets || presetsResult.presets || [], | |
| presets: presetsResult.presets || [], | |
| }, projectName); | |
| }, | |
| _normalizeProjectLlmData(raw, projectName) { | |
| const data = raw || {}; | |
| const globalPresets = this._normalizePresetsWithScope( | |
| data.global_presets || [], | |
| "global", | |
| "" | |
| ); | |
| const presets = [...globalPresets]; | |
| const selected = data.selected_preset || { | |
| scope: "global", | |
| project_name: "", | |
| name: "Default", | |
| }; | |
| return { | |
| selected_preset: selected, | |
| preset_key: this.getLlmPresetKey(selected), | |
| global_presets: globalPresets, | |
| project_presets: [], | |
| presets, | |
| }; | |
| }, | |
| _normalizePresetsWithScope(presets, defaultScope, projectName) { | |
| return modelConfigStore._normalizePresets(presets || []).map((preset, index) => { | |
| const raw = presets[index] || {}; | |
| return { | |
| ...preset, | |
| scope: raw.scope || defaultScope, | |
| project_name: raw.project_name || (defaultScope === "project" ? projectName : ""), | |
| }; | |
| }); | |
| }, | |
| getLlmPresetKey(preset) { | |
| if (!preset) return "global||Default"; | |
| return `${preset.scope || "global"}|${preset.project_name || ""}|${preset.name || ""}`; | |
| }, | |
| _findLlmPresetByKey(key) { | |
| const llm = this.selectedProject?.llm; | |
| if (!llm) return null; | |
| return (llm.presets || []).find((preset) => this.getLlmPresetKey(preset) === key) || null; | |
| }, | |
| applySelectedLlmPreset() { | |
| const llm = this.selectedProject?.llm; | |
| if (!llm) return; | |
| const preset = this._findLlmPresetByKey(llm.preset_key); | |
| if (!preset) return; | |
| llm.selected_preset = { | |
| scope: preset.scope || "global", | |
| project_name: preset.project_name || "", | |
| name: preset.name || "", | |
| }; | |
| }, | |
| async editSelectedProjectPresets() { | |
| const llm = this.selectedProject?.llm; | |
| if (!llm) return; | |
| const selectedName = llm.selected_preset?.name || "Default"; | |
| await modelConfigStore.openPresetEditor(selectedName); | |
| await modelConfigStore.loadGlobalPresets(); | |
| llm.global_presets = this._normalizePresetsWithScope( | |
| modelConfigStore.globalPresets, | |
| "global", | |
| "" | |
| ); | |
| llm.presets = [...llm.global_presets]; | |
| const remappedName = modelConfigStore.remapPresetName(selectedName); | |
| llm.selected_preset = { | |
| scope: "global", | |
| project_name: "", | |
| name: remappedName, | |
| }; | |
| llm.preset_key = this.getLlmPresetKey(llm.selected_preset); | |
| }, | |
| async browseSelected(...relPath) { | |
| const path = this.getSelectedAbsPath(...relPath); | |
| return await browserStore.open(path); | |
| }, | |
| async browseInstructionFiles() { | |
| await this.browseSelected(".a0proj", "instructions"); | |
| try { | |
| const newData = await this._createEditProjectData( | |
| this.selectedProject.name | |
| ); | |
| this.selectedProject.instruction_files_count = | |
| newData.instruction_files_count; | |
| } catch (error) { | |
| //pass | |
| } | |
| }, | |
| async browseKnowledgeFiles() { | |
| await this.browseSelected(".a0proj", "knowledge"); | |
| // refresh and reindex project | |
| try { | |
| // progress notification | |
| shortcuts.frontendNotification({ | |
| type: shortcuts.NotificationType.PROGRESS, | |
| message: "Loading knowledge...", | |
| priority: shortcuts.NotificationPriority.NORMAL, | |
| displayTime: 999, | |
| group: "knowledge_load", | |
| frontendOnly: true, | |
| }); | |
| // call reindex knowledge | |
| const reindexCall = api.callJsonApi("/plugins/_memory/knowledge_reindex", { | |
| ctxid: shortcuts.getCurrentContextId(), | |
| }); | |
| const newData = await this._createEditProjectData( | |
| this.selectedProject.name | |
| ); | |
| this.selectedProject.knowledge_files_count = | |
| newData.knowledge_files_count; | |
| // wait for reindex to finish | |
| await reindexCall; | |
| // finished notification | |
| shortcuts.frontendNotification({ | |
| type: shortcuts.NotificationType.SUCCESS, | |
| message: "Knowledge loaded successfully", | |
| priority: shortcuts.NotificationPriority.NORMAL, | |
| displayTime: 2, | |
| group: "knowledge_load", | |
| frontendOnly: true, | |
| }); | |
| } catch (error) { | |
| // error notification | |
| shortcuts.frontendNotification({ | |
| type: shortcuts.NotificationType.ERROR, | |
| message: "Error loading knowledge", | |
| priority: shortcuts.NotificationPriority.NORMAL, | |
| displayTime: 5, | |
| group: "knowledge_load", | |
| frontendOnly: true, | |
| }); | |
| } | |
| }, | |
| getSelectedAbsPath(...relPath) { | |
| return ["/a0/usr/projects", this.selectedProject.name, ...relPath] | |
| .join("/") | |
| .replace(/\/+/g, "/"); | |
| }, | |
| async editActiveProject() { | |
| const ctx = shortcuts.getCurrentContext(); | |
| if(!ctx) return; | |
| this.openEditModal(ctx.project.name); | |
| }, | |
| async testFileStructure() { | |
| try { | |
| const response = await api.callJsonApi("projects", { | |
| action: "file_structure", | |
| name: this.selectedProject.name, | |
| settings: this.selectedProject.file_structure, | |
| }); | |
| this.fileStructureTestOutput = response.data; | |
| shortcuts.openModal("projects/project-file-structure-test.html"); | |
| } catch (error) { | |
| console.error("Error testing file structure:", error); | |
| shortcuts.frontendNotification({ | |
| type: shortcuts.NotificationType.ERROR, | |
| message: "Error testing file structure", | |
| priority: shortcuts.NotificationPriority.NORMAL, | |
| displayTime: 3, | |
| frontendOnly: true, | |
| }); | |
| } | |
| }, | |
| }; | |
| // convert it to alpine store | |
| const store = createStore("projects", model); | |
| // export for use in other files | |
| export { store }; | |