main
js 178 lines 4.31 KB
Raw
1 import { createStore } from "/js/AlpineStore.js";
2 import * as api from "/js/api.js";
3 import { store as skillsScanStore } from "/components/settings/skills/skills-scan-store.js";
4
5 function sanitizeNamespace(text) {
6 if (!text) return "";
7 return String(text)
8 .trim()
9 .replace(/[^a-zA-Z0-9._-]+/g, "_")
10 .replace(/^_+|_+$/g, "");
11 }
12
13 const model = {
14 loading: false,
15 loadingMessage: "",
16 error: "",
17
18 skillsFile: null,
19 namespace: "",
20 conflict: "skip", // skip|overwrite|rename
21 projectKey: "", // selected project key, empty means All
22 projects: [], // available projects options [{key,label}]
23
24 preview: null,
25 result: null,
26
27 init() {
28 this.resetState();
29 this.loadProjects();
30 },
31
32 resetState() {
33 this.loading = false;
34 this.loadingMessage = "";
35 this.error = "";
36 this.preview = null;
37 this.result = null;
38 },
39
40 onClose() {
41 this.resetState();
42 this.skillsFile = null;
43 this.namespace = "";
44 this.conflict = "skip";
45 this.projectKey = "";
46 },
47
48 async loadProjects() {
49 try {
50 const data = await api.callJsonApi("/projects", { action: "list_options" });
51 this.projects = data.ok ? (data.data || []) : [];
52 } catch (e) {
53 console.error("Failed to load projects:", e);
54 this.projects = [];
55 }
56 },
57
58 async handleFileUpload(event) {
59 const file = event.target.files[0];
60 if (!file) return;
61
62 this.skillsFile = file;
63 this.error = "";
64 this.result = null;
65 this.preview = null;
66
67 // default namespace from file name (minus .zip)
68 const base = file.name.replace(/\.zip$/i, "");
69 if (!this.namespace) {
70 this.namespace = sanitizeNamespace(base);
71 } else {
72 this.namespace = sanitizeNamespace(this.namespace);
73 }
74
75 await this.previewImport();
76 },
77
78 buildFormData() {
79 const formData = new FormData();
80 formData.append("skills_file", this.skillsFile);
81 formData.append("ctxid", globalThis.getContext ? globalThis.getContext() : "");
82 formData.append("namespace", sanitizeNamespace(this.namespace));
83 formData.append("conflict", this.conflict);
84
85 if (this.projectKey) {
86 formData.append("project_name", this.projectKey);
87 }
88
89 return formData;
90 },
91
92 async previewImport() {
93 if (!this.skillsFile) {
94 this.error = "Please select a skills .zip file first";
95 return;
96 }
97
98 try {
99 this.loading = true;
100 this.loadingMessage = "Previewing skills import...";
101 this.error = "";
102 this.preview = null;
103
104 const response = await api.fetchApi("/skills_import_preview", {
105 method: "POST",
106 body: this.buildFormData(),
107 });
108
109 const result = await response.json();
110 if (!result.success) {
111 this.error = result.error || "Preview failed";
112 return;
113 }
114
115 this.preview = result;
116 // normalize namespace (server may sanitize)
117 if (result.namespace) this.namespace = result.namespace;
118 } catch (e) {
119 this.error = `Preview error: ${e.message}`;
120 } finally {
121 this.loading = false;
122 this.loadingMessage = "";
123 }
124 },
125
126 async scanSelectedFile() {
127 if (!this.skillsFile) {
128 this.error = "Please select a skills .zip file first";
129 return;
130 }
131
132 await skillsScanStore.openForUploadedFile(this.skillsFile, {
133 namespace: sanitizeNamespace(this.namespace),
134 });
135 },
136
137 async performImport() {
138 if (!this.skillsFile) {
139 this.error = "Please select a skills .zip file first";
140 return;
141 }
142
143 try {
144 this.loading = true;
145 this.loadingMessage = "Importing skills...";
146 this.error = "";
147 this.result = null;
148
149 const response = await api.fetchApi("/skills_import", {
150 method: "POST",
151 body: this.buildFormData(),
152 });
153
154 const result = await response.json();
155 if (!result.success) {
156 this.error = result.error || "Import failed";
157 return;
158 }
159
160 this.result = result;
161 this.preview = result; // keep last info visible
162 if (window.toastFrontendInfo) {
163 window.toastFrontendInfo(
164 `Imported ${result.imported_count} skill folder(s)`,
165 "Skills Import"
166 );
167 }
168 } catch (e) {
169 this.error = `Import error: ${e.message}`;
170 } finally {
171 this.loading = false;
172 this.loadingMessage = "";
173 }
174 },
175 };
176
177 const store = createStore("skillsImportStore", model);
178 export { store };