@hirohsu/user-web-feedback 2.8.2 → 2.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +953 -953
  2. package/dist/cli.cjs +35128 -22228
  3. package/dist/index.cjs +39895 -26993
  4. package/dist/static/app.js +385 -385
  5. package/dist/static/components/navbar.css +406 -406
  6. package/dist/static/components/navbar.html +49 -49
  7. package/dist/static/components/navbar.js +211 -211
  8. package/dist/static/dashboard.css +495 -495
  9. package/dist/static/dashboard.html +95 -95
  10. package/dist/static/dashboard.js +540 -540
  11. package/dist/static/favicon.svg +27 -27
  12. package/dist/static/index.html +541 -541
  13. package/dist/static/logs.html +376 -376
  14. package/dist/static/logs.js +442 -442
  15. package/dist/static/mcp-settings.html +797 -797
  16. package/dist/static/mcp-settings.js +884 -884
  17. package/dist/static/modules/app-core.js +124 -124
  18. package/dist/static/modules/conversation-panel.js +247 -247
  19. package/dist/static/modules/feedback-handler.js +1420 -1420
  20. package/dist/static/modules/image-handler.js +155 -155
  21. package/dist/static/modules/log-viewer.js +296 -296
  22. package/dist/static/modules/mcp-manager.js +474 -474
  23. package/dist/static/modules/prompt-manager.js +364 -364
  24. package/dist/static/modules/settings-manager.js +299 -299
  25. package/dist/static/modules/socket-manager.js +170 -170
  26. package/dist/static/modules/state-manager.js +352 -352
  27. package/dist/static/modules/timer-controller.js +243 -243
  28. package/dist/static/modules/ui-helpers.js +246 -246
  29. package/dist/static/settings.html +426 -417
  30. package/dist/static/settings.js +767 -705
  31. package/dist/static/style.css +2156 -2156
  32. package/dist/static/terminals.html +357 -357
  33. package/dist/static/terminals.js +321 -321
  34. package/package.json +95 -93
@@ -1,364 +1,364 @@
1
- /**
2
- * prompt-manager.js
3
- * 提示詞管理模組
4
- * 包含提示詞 CRUD、渲染、搜尋等功能
5
- */
6
-
7
- import {
8
- getPrompts,
9
- setPrompts,
10
- findPromptById,
11
- isEditingPrompt,
12
- getEditingPromptId,
13
- setIsEditingPrompt,
14
- setEditingPromptId,
15
- } from "./state-manager.js";
16
-
17
- import {
18
- showToast,
19
- formatApiError,
20
- escapeHtml,
21
- updateCharCount,
22
- } from "./ui-helpers.js";
23
- import { emitUserActivity } from "./socket-manager.js";
24
-
25
- /**
26
- * 載入提示詞列表
27
- */
28
- export async function loadPrompts() {
29
- try {
30
- const response = await fetch("/api/prompts");
31
- const data = await response.json();
32
-
33
- if (data.success) {
34
- setPrompts(data.prompts);
35
- renderPrompts();
36
- }
37
- } catch (error) {
38
- console.error("載入提示詞失敗:", error);
39
- }
40
- }
41
-
42
- /**
43
- * 自動載入釘選提示詞
44
- */
45
- export async function autoLoadPinnedPrompts() {
46
- try {
47
- const response = await fetch("/api/prompts/pinned");
48
- const data = await response.json();
49
-
50
- if (data.success && data.prompts.length > 0) {
51
- const content = data.prompts.map((p) => p.content).join("\n\n");
52
- document.getElementById("feedbackText").value = content;
53
- updateCharCount();
54
-
55
- showToast(
56
- "info",
57
- "提示詞已載入",
58
- `已自動載入 ${data.prompts.length} 個釘選提示詞`
59
- );
60
- }
61
- } catch (error) {
62
- console.error("自動載入釘選提示詞失敗:", error);
63
- }
64
- }
65
-
66
- /**
67
- * 獲取釘選提示詞內容
68
- * @returns {Promise<string>} - 釘選提示詞內容
69
- */
70
- export async function getPinnedPromptsContent() {
71
- try {
72
- const response = await fetch("/api/prompts/pinned");
73
- const data = await response.json();
74
-
75
- if (data.success && data.prompts.length > 0) {
76
- return data.prompts.map((p) => p.content).join("\n\n");
77
- }
78
- return "";
79
- } catch (error) {
80
- console.error("獲取釘選提示詞失敗:", error);
81
- return "";
82
- }
83
- }
84
-
85
- /**
86
- * 渲染提示詞列表
87
- * @param {string} searchTerm - 搜尋關鍵字
88
- */
89
- export function renderPrompts(searchTerm = "") {
90
- const listEl = document.getElementById("promptList");
91
- if (!listEl) return;
92
-
93
- const prompts = getPrompts();
94
- let filteredPrompts = prompts;
95
-
96
- if (searchTerm) {
97
- filteredPrompts = prompts.filter(
98
- (p) =>
99
- p.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
100
- p.content.toLowerCase().includes(searchTerm.toLowerCase()) ||
101
- (p.category &&
102
- p.category.toLowerCase().includes(searchTerm.toLowerCase()))
103
- );
104
- }
105
-
106
- if (filteredPrompts.length === 0) {
107
- listEl.innerHTML = `
108
- <div class="placeholder">
109
- <span class="icon">📋</span>
110
- <p>${searchTerm ? "找不到符合的提示詞" : "尚無提示詞"}</p>
111
- <button id="addPromptBtn" class="btn btn-secondary btn-sm" onclick="openPromptModal()">新增提示詞</button>
112
- </div>
113
- `;
114
- return;
115
- }
116
-
117
- listEl.innerHTML = filteredPrompts
118
- .map(
119
- (prompt) => `
120
- <div class="prompt-item ${
121
- prompt.isPinned ? "pinned" : ""
122
- }" onclick="usePrompt(${prompt.id})">
123
- <div class="prompt-item-header">
124
- <div class="prompt-item-title">${escapeHtml(prompt.title)}</div>
125
- <div class="prompt-item-actions">
126
- <button class="btn btn-ghost btn-sm" onclick="event.stopPropagation(); togglePinPrompt(${
127
- prompt.id
128
- })" title="${prompt.isPinned ? "取消釘選" : "釘選"}">
129
- <span class="icon">${prompt.isPinned ? "📍" : "📌"}</span>
130
- </button>
131
- <button class="btn btn-ghost btn-sm" onclick="event.stopPropagation(); editPrompt(${
132
- prompt.id
133
- })" title="編輯">
134
- <span class="icon">✏️</span>
135
- </button>
136
- <button class="btn btn-ghost btn-sm" onclick="event.stopPropagation(); deletePrompt(${
137
- prompt.id
138
- })" title="刪除">
139
- <span class="icon">🗑️</span>
140
- </button>
141
- </div>
142
- </div>
143
- <div class="prompt-item-content">${escapeHtml(prompt.content)}</div>
144
- ${
145
- prompt.category
146
- ? `
147
- <div class="prompt-item-footer">
148
- <span class="prompt-item-category">${escapeHtml(
149
- prompt.category
150
- )}</span>
151
- </div>
152
- `
153
- : ""
154
- }
155
- </div>
156
- `
157
- )
158
- .join("");
159
- }
160
-
161
- /**
162
- * 過濾提示詞
163
- */
164
- export function filterPrompts() {
165
- const searchTerm = document.getElementById("promptSearch").value;
166
- renderPrompts(searchTerm);
167
- }
168
-
169
- /**
170
- * 使用提示詞
171
- * @param {number} id - 提示詞 ID
172
- */
173
- export function usePrompt(id) {
174
- const prompt = findPromptById(id);
175
- if (!prompt) return;
176
-
177
- const feedbackText = document.getElementById("feedbackText");
178
- const currentText = feedbackText.value;
179
-
180
- if (currentText.trim()) {
181
- feedbackText.value = currentText + "\n\n" + prompt.content;
182
- } else {
183
- feedbackText.value = prompt.content;
184
- }
185
-
186
- updateCharCount();
187
- emitUserActivity();
188
-
189
- showToast("success", "提示詞已使用", `已插入「${prompt.title}」`);
190
- }
191
-
192
- /**
193
- * 切換提示詞釘選狀態
194
- * @param {number} id - 提示詞 ID
195
- */
196
- export async function togglePinPrompt(id) {
197
- try {
198
- const response = await fetch(`/api/prompts/${id}/pin`, {
199
- method: "PUT",
200
- });
201
-
202
- const data = await response.json();
203
-
204
- if (data.success) {
205
- await loadPrompts();
206
- showToast(
207
- "success",
208
- "成功",
209
- data.prompt.isPinned ? "已釘選提示詞" : "已取消釘選"
210
- );
211
- } else {
212
- showToast("error", "錯誤", formatApiError(data));
213
- }
214
- } catch (error) {
215
- console.error("切換釘選狀態失敗:", error);
216
- showToast("error", "錯誤", "操作失敗");
217
- }
218
- }
219
-
220
- /**
221
- * 編輯提示詞
222
- * @param {number} id - 提示詞 ID
223
- */
224
- export function editPrompt(id) {
225
- const prompt = findPromptById(id);
226
- if (!prompt) return;
227
-
228
- setIsEditingPrompt(true);
229
- setEditingPromptId(id);
230
-
231
- document.getElementById("promptModalTitle").textContent = "編輯提示詞";
232
- document.getElementById("promptId").value = id;
233
- document.getElementById("promptTitle").value = prompt.title;
234
- document.getElementById("promptContent").value = prompt.content;
235
- document.getElementById("promptCategory").value = prompt.category || "";
236
- document.getElementById("promptIsPinned").checked = prompt.isPinned;
237
-
238
- openPromptModal();
239
- }
240
-
241
- /**
242
- * 刪除提示詞
243
- * @param {number} id - 提示詞 ID
244
- */
245
- export async function deletePrompt(id) {
246
- if (!confirm("確定要刪除此提示詞嗎?")) return;
247
-
248
- try {
249
- const response = await fetch(`/api/prompts/${id}`, {
250
- method: "DELETE",
251
- });
252
-
253
- const data = await response.json();
254
-
255
- if (data.success) {
256
- await loadPrompts();
257
- showToast("success", "成功", "提示詞已刪除");
258
- } else {
259
- showToast("error", "錯誤", formatApiError(data));
260
- }
261
- } catch (error) {
262
- console.error("刪除提示詞失敗:", error);
263
- showToast("error", "錯誤", "刪除失敗");
264
- }
265
- }
266
-
267
- /**
268
- * 開啟提示詞彈窗
269
- */
270
- export function openPromptModal() {
271
- if (!isEditingPrompt()) {
272
- document.getElementById("promptModalTitle").textContent = "新增提示詞";
273
- document.getElementById("promptForm").reset();
274
- document.getElementById("promptId").value = "";
275
- }
276
-
277
- document.getElementById("promptModal").classList.add("show");
278
- }
279
-
280
- /**
281
- * 關閉提示詞彈窗
282
- */
283
- export function closePromptModal() {
284
- document.getElementById("promptModal").classList.remove("show");
285
- setIsEditingPrompt(false);
286
- setEditingPromptId(null);
287
- }
288
-
289
- /**
290
- * 儲存提示詞
291
- */
292
- export async function savePrompt() {
293
- const title = document.getElementById("promptTitle").value.trim();
294
- const content = document.getElementById("promptContent").value.trim();
295
- const category = document.getElementById("promptCategory").value.trim();
296
- const isPinned = document.getElementById("promptIsPinned").checked;
297
-
298
- if (!title || !content) {
299
- showToast("error", "錯誤", "標題和內容為必填欄位");
300
- return;
301
- }
302
-
303
- const promptData = {
304
- title,
305
- content,
306
- category: category || undefined,
307
- isPinned,
308
- };
309
-
310
- try {
311
- let response;
312
- const editing = isEditingPrompt();
313
- const editingId = getEditingPromptId();
314
-
315
- if (editing && editingId) {
316
- response = await fetch(`/api/prompts/${editingId}`, {
317
- method: "PUT",
318
- headers: { "Content-Type": "application/json" },
319
- body: JSON.stringify(promptData),
320
- });
321
- } else {
322
- response = await fetch("/api/prompts", {
323
- method: "POST",
324
- headers: { "Content-Type": "application/json" },
325
- body: JSON.stringify(promptData),
326
- });
327
- }
328
-
329
- const data = await response.json();
330
-
331
- if (data.success) {
332
- await loadPrompts();
333
- closePromptModal();
334
- showToast("success", "成功", editing ? "提示詞已更新" : "提示詞已創建");
335
- } else {
336
- showToast("error", "錯誤", formatApiError(data));
337
- }
338
- } catch (error) {
339
- console.error("保存提示詞失敗:", error);
340
- showToast("error", "錯誤", "保存失敗");
341
- }
342
- }
343
-
344
- // 暴露到 window 供 HTML onclick 使用
345
- window.usePrompt = usePrompt;
346
- window.togglePinPrompt = togglePinPrompt;
347
- window.editPrompt = editPrompt;
348
- window.deletePrompt = deletePrompt;
349
- window.openPromptModal = openPromptModal;
350
-
351
- export default {
352
- loadPrompts,
353
- autoLoadPinnedPrompts,
354
- getPinnedPromptsContent,
355
- renderPrompts,
356
- filterPrompts,
357
- usePrompt,
358
- togglePinPrompt,
359
- editPrompt,
360
- deletePrompt,
361
- openPromptModal,
362
- closePromptModal,
363
- savePrompt,
364
- };
1
+ /**
2
+ * prompt-manager.js
3
+ * 提示詞管理模組
4
+ * 包含提示詞 CRUD、渲染、搜尋等功能
5
+ */
6
+
7
+ import {
8
+ getPrompts,
9
+ setPrompts,
10
+ findPromptById,
11
+ isEditingPrompt,
12
+ getEditingPromptId,
13
+ setIsEditingPrompt,
14
+ setEditingPromptId,
15
+ } from "./state-manager.js";
16
+
17
+ import {
18
+ showToast,
19
+ formatApiError,
20
+ escapeHtml,
21
+ updateCharCount,
22
+ } from "./ui-helpers.js";
23
+ import { emitUserActivity } from "./socket-manager.js";
24
+
25
+ /**
26
+ * 載入提示詞列表
27
+ */
28
+ export async function loadPrompts() {
29
+ try {
30
+ const response = await fetch("/api/prompts");
31
+ const data = await response.json();
32
+
33
+ if (data.success) {
34
+ setPrompts(data.prompts);
35
+ renderPrompts();
36
+ }
37
+ } catch (error) {
38
+ console.error("載入提示詞失敗:", error);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * 自動載入釘選提示詞
44
+ */
45
+ export async function autoLoadPinnedPrompts() {
46
+ try {
47
+ const response = await fetch("/api/prompts/pinned");
48
+ const data = await response.json();
49
+
50
+ if (data.success && data.prompts.length > 0) {
51
+ const content = data.prompts.map((p) => p.content).join("\n\n");
52
+ document.getElementById("feedbackText").value = content;
53
+ updateCharCount();
54
+
55
+ showToast(
56
+ "info",
57
+ "提示詞已載入",
58
+ `已自動載入 ${data.prompts.length} 個釘選提示詞`
59
+ );
60
+ }
61
+ } catch (error) {
62
+ console.error("自動載入釘選提示詞失敗:", error);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * 獲取釘選提示詞內容
68
+ * @returns {Promise<string>} - 釘選提示詞內容
69
+ */
70
+ export async function getPinnedPromptsContent() {
71
+ try {
72
+ const response = await fetch("/api/prompts/pinned");
73
+ const data = await response.json();
74
+
75
+ if (data.success && data.prompts.length > 0) {
76
+ return data.prompts.map((p) => p.content).join("\n\n");
77
+ }
78
+ return "";
79
+ } catch (error) {
80
+ console.error("獲取釘選提示詞失敗:", error);
81
+ return "";
82
+ }
83
+ }
84
+
85
+ /**
86
+ * 渲染提示詞列表
87
+ * @param {string} searchTerm - 搜尋關鍵字
88
+ */
89
+ export function renderPrompts(searchTerm = "") {
90
+ const listEl = document.getElementById("promptList");
91
+ if (!listEl) return;
92
+
93
+ const prompts = getPrompts();
94
+ let filteredPrompts = prompts;
95
+
96
+ if (searchTerm) {
97
+ filteredPrompts = prompts.filter(
98
+ (p) =>
99
+ p.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
100
+ p.content.toLowerCase().includes(searchTerm.toLowerCase()) ||
101
+ (p.category &&
102
+ p.category.toLowerCase().includes(searchTerm.toLowerCase()))
103
+ );
104
+ }
105
+
106
+ if (filteredPrompts.length === 0) {
107
+ listEl.innerHTML = `
108
+ <div class="placeholder">
109
+ <span class="icon">📋</span>
110
+ <p>${searchTerm ? "找不到符合的提示詞" : "尚無提示詞"}</p>
111
+ <button id="addPromptBtn" class="btn btn-secondary btn-sm" onclick="openPromptModal()">新增提示詞</button>
112
+ </div>
113
+ `;
114
+ return;
115
+ }
116
+
117
+ listEl.innerHTML = filteredPrompts
118
+ .map(
119
+ (prompt) => `
120
+ <div class="prompt-item ${
121
+ prompt.isPinned ? "pinned" : ""
122
+ }" onclick="usePrompt(${prompt.id})">
123
+ <div class="prompt-item-header">
124
+ <div class="prompt-item-title">${escapeHtml(prompt.title)}</div>
125
+ <div class="prompt-item-actions">
126
+ <button class="btn btn-ghost btn-sm" onclick="event.stopPropagation(); togglePinPrompt(${
127
+ prompt.id
128
+ })" title="${prompt.isPinned ? "取消釘選" : "釘選"}">
129
+ <span class="icon">${prompt.isPinned ? "📍" : "📌"}</span>
130
+ </button>
131
+ <button class="btn btn-ghost btn-sm" onclick="event.stopPropagation(); editPrompt(${
132
+ prompt.id
133
+ })" title="編輯">
134
+ <span class="icon">✏️</span>
135
+ </button>
136
+ <button class="btn btn-ghost btn-sm" onclick="event.stopPropagation(); deletePrompt(${
137
+ prompt.id
138
+ })" title="刪除">
139
+ <span class="icon">🗑️</span>
140
+ </button>
141
+ </div>
142
+ </div>
143
+ <div class="prompt-item-content">${escapeHtml(prompt.content)}</div>
144
+ ${
145
+ prompt.category
146
+ ? `
147
+ <div class="prompt-item-footer">
148
+ <span class="prompt-item-category">${escapeHtml(
149
+ prompt.category
150
+ )}</span>
151
+ </div>
152
+ `
153
+ : ""
154
+ }
155
+ </div>
156
+ `
157
+ )
158
+ .join("");
159
+ }
160
+
161
+ /**
162
+ * 過濾提示詞
163
+ */
164
+ export function filterPrompts() {
165
+ const searchTerm = document.getElementById("promptSearch").value;
166
+ renderPrompts(searchTerm);
167
+ }
168
+
169
+ /**
170
+ * 使用提示詞
171
+ * @param {number} id - 提示詞 ID
172
+ */
173
+ export function usePrompt(id) {
174
+ const prompt = findPromptById(id);
175
+ if (!prompt) return;
176
+
177
+ const feedbackText = document.getElementById("feedbackText");
178
+ const currentText = feedbackText.value;
179
+
180
+ if (currentText.trim()) {
181
+ feedbackText.value = currentText + "\n\n" + prompt.content;
182
+ } else {
183
+ feedbackText.value = prompt.content;
184
+ }
185
+
186
+ updateCharCount();
187
+ emitUserActivity();
188
+
189
+ showToast("success", "提示詞已使用", `已插入「${prompt.title}」`);
190
+ }
191
+
192
+ /**
193
+ * 切換提示詞釘選狀態
194
+ * @param {number} id - 提示詞 ID
195
+ */
196
+ export async function togglePinPrompt(id) {
197
+ try {
198
+ const response = await fetch(`/api/prompts/${id}/pin`, {
199
+ method: "PUT",
200
+ });
201
+
202
+ const data = await response.json();
203
+
204
+ if (data.success) {
205
+ await loadPrompts();
206
+ showToast(
207
+ "success",
208
+ "成功",
209
+ data.prompt.isPinned ? "已釘選提示詞" : "已取消釘選"
210
+ );
211
+ } else {
212
+ showToast("error", "錯誤", formatApiError(data));
213
+ }
214
+ } catch (error) {
215
+ console.error("切換釘選狀態失敗:", error);
216
+ showToast("error", "錯誤", "操作失敗");
217
+ }
218
+ }
219
+
220
+ /**
221
+ * 編輯提示詞
222
+ * @param {number} id - 提示詞 ID
223
+ */
224
+ export function editPrompt(id) {
225
+ const prompt = findPromptById(id);
226
+ if (!prompt) return;
227
+
228
+ setIsEditingPrompt(true);
229
+ setEditingPromptId(id);
230
+
231
+ document.getElementById("promptModalTitle").textContent = "編輯提示詞";
232
+ document.getElementById("promptId").value = id;
233
+ document.getElementById("promptTitle").value = prompt.title;
234
+ document.getElementById("promptContent").value = prompt.content;
235
+ document.getElementById("promptCategory").value = prompt.category || "";
236
+ document.getElementById("promptIsPinned").checked = prompt.isPinned;
237
+
238
+ openPromptModal();
239
+ }
240
+
241
+ /**
242
+ * 刪除提示詞
243
+ * @param {number} id - 提示詞 ID
244
+ */
245
+ export async function deletePrompt(id) {
246
+ if (!confirm("確定要刪除此提示詞嗎?")) return;
247
+
248
+ try {
249
+ const response = await fetch(`/api/prompts/${id}`, {
250
+ method: "DELETE",
251
+ });
252
+
253
+ const data = await response.json();
254
+
255
+ if (data.success) {
256
+ await loadPrompts();
257
+ showToast("success", "成功", "提示詞已刪除");
258
+ } else {
259
+ showToast("error", "錯誤", formatApiError(data));
260
+ }
261
+ } catch (error) {
262
+ console.error("刪除提示詞失敗:", error);
263
+ showToast("error", "錯誤", "刪除失敗");
264
+ }
265
+ }
266
+
267
+ /**
268
+ * 開啟提示詞彈窗
269
+ */
270
+ export function openPromptModal() {
271
+ if (!isEditingPrompt()) {
272
+ document.getElementById("promptModalTitle").textContent = "新增提示詞";
273
+ document.getElementById("promptForm").reset();
274
+ document.getElementById("promptId").value = "";
275
+ }
276
+
277
+ document.getElementById("promptModal").classList.add("show");
278
+ }
279
+
280
+ /**
281
+ * 關閉提示詞彈窗
282
+ */
283
+ export function closePromptModal() {
284
+ document.getElementById("promptModal").classList.remove("show");
285
+ setIsEditingPrompt(false);
286
+ setEditingPromptId(null);
287
+ }
288
+
289
+ /**
290
+ * 儲存提示詞
291
+ */
292
+ export async function savePrompt() {
293
+ const title = document.getElementById("promptTitle").value.trim();
294
+ const content = document.getElementById("promptContent").value.trim();
295
+ const category = document.getElementById("promptCategory").value.trim();
296
+ const isPinned = document.getElementById("promptIsPinned").checked;
297
+
298
+ if (!title || !content) {
299
+ showToast("error", "錯誤", "標題和內容為必填欄位");
300
+ return;
301
+ }
302
+
303
+ const promptData = {
304
+ title,
305
+ content,
306
+ category: category || undefined,
307
+ isPinned,
308
+ };
309
+
310
+ try {
311
+ let response;
312
+ const editing = isEditingPrompt();
313
+ const editingId = getEditingPromptId();
314
+
315
+ if (editing && editingId) {
316
+ response = await fetch(`/api/prompts/${editingId}`, {
317
+ method: "PUT",
318
+ headers: { "Content-Type": "application/json" },
319
+ body: JSON.stringify(promptData),
320
+ });
321
+ } else {
322
+ response = await fetch("/api/prompts", {
323
+ method: "POST",
324
+ headers: { "Content-Type": "application/json" },
325
+ body: JSON.stringify(promptData),
326
+ });
327
+ }
328
+
329
+ const data = await response.json();
330
+
331
+ if (data.success) {
332
+ await loadPrompts();
333
+ closePromptModal();
334
+ showToast("success", "成功", editing ? "提示詞已更新" : "提示詞已創建");
335
+ } else {
336
+ showToast("error", "錯誤", formatApiError(data));
337
+ }
338
+ } catch (error) {
339
+ console.error("保存提示詞失敗:", error);
340
+ showToast("error", "錯誤", "保存失敗");
341
+ }
342
+ }
343
+
344
+ // 暴露到 window 供 HTML onclick 使用
345
+ window.usePrompt = usePrompt;
346
+ window.togglePinPrompt = togglePinPrompt;
347
+ window.editPrompt = editPrompt;
348
+ window.deletePrompt = deletePrompt;
349
+ window.openPromptModal = openPromptModal;
350
+
351
+ export default {
352
+ loadPrompts,
353
+ autoLoadPinnedPrompts,
354
+ getPinnedPromptsContent,
355
+ renderPrompts,
356
+ filterPrompts,
357
+ usePrompt,
358
+ togglePinPrompt,
359
+ editPrompt,
360
+ deletePrompt,
361
+ openPromptModal,
362
+ closePromptModal,
363
+ savePrompt,
364
+ };