@hirohsu/user-web-feedback 2.8.1 → 2.8.8
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.
- package/dist/cli.cjs +393 -13
- package/dist/index.cjs +393 -13
- package/dist/static/logs.html +3 -3
- package/dist/static/settings.html +46 -11
- package/dist/static/settings.js +242 -18
- package/package.json +6 -3
package/dist/static/settings.js
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
const normalizedUrl = apiUrl.toLowerCase();
|
|
22
22
|
if (normalizedUrl.includes("generativelanguage.googleapis.com")) return "google";
|
|
23
23
|
if (normalizedUrl.includes("api.anthropic.com")) return "anthropic";
|
|
24
|
-
if (normalizedUrl.includes("
|
|
24
|
+
if (normalizedUrl.includes("nvidia.com")) return "nvidia";
|
|
25
|
+
if (normalizedUrl.includes("bigmodel.cn") || normalizedUrl.includes("z.ai")) return "zai";
|
|
25
26
|
if (normalizedUrl.includes("api.openai.com")) return "openai";
|
|
26
27
|
return "openai"; // 預設
|
|
27
28
|
}
|
|
@@ -34,11 +35,11 @@
|
|
|
34
35
|
const elements = {
|
|
35
36
|
// AI Settings
|
|
36
37
|
aiProvider: document.getElementById("aiProvider"),
|
|
38
|
+
apiUrl: document.getElementById("apiUrl"),
|
|
39
|
+
openaiCompatible: document.getElementById("openaiCompatible"),
|
|
37
40
|
apiKey: document.getElementById("apiKey"),
|
|
38
41
|
toggleApiKey: document.getElementById("toggleApiKey"),
|
|
39
42
|
aiModel: document.getElementById("aiModel"),
|
|
40
|
-
systemPrompt: document.getElementById("systemPrompt"),
|
|
41
|
-
mcpToolsPrompt: document.getElementById("mcpToolsPrompt"),
|
|
42
43
|
temperature: document.getElementById("temperature"),
|
|
43
44
|
maxTokens: document.getElementById("maxTokens"),
|
|
44
45
|
autoReplyTimerSeconds: document.getElementById("autoReplyTimerSeconds"),
|
|
@@ -72,6 +73,13 @@
|
|
|
72
73
|
selfProbeCount: document.getElementById("selfProbeCount"),
|
|
73
74
|
selfProbeLastTime: document.getElementById("selfProbeLastTime"),
|
|
74
75
|
saveSelfProbeBtn: document.getElementById("saveSelfProbeBtn"),
|
|
76
|
+
// Prompt Config Settings
|
|
77
|
+
promptConfigList: document.getElementById("promptConfigList"),
|
|
78
|
+
resetPromptsBtn: document.getElementById("resetPromptsBtn"),
|
|
79
|
+
savePromptsBtn: document.getElementById("savePromptsBtn"),
|
|
80
|
+
// Extended Provider Settings (integrated into AI settings)
|
|
81
|
+
zaiExtSettings: document.getElementById("zaiExtSettings"),
|
|
82
|
+
zaiRegion: document.getElementById("zaiRegion"),
|
|
75
83
|
toastContainer: document.getElementById("toastContainer"),
|
|
76
84
|
};
|
|
77
85
|
|
|
@@ -86,6 +94,7 @@
|
|
|
86
94
|
loadCLISettings();
|
|
87
95
|
loadPreferences();
|
|
88
96
|
loadSelfProbeSettings();
|
|
97
|
+
loadPromptConfigs();
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
function setupEventListeners() {
|
|
@@ -93,6 +102,12 @@
|
|
|
93
102
|
elements.toggleApiKey.addEventListener("click", toggleApiKeyVisibility);
|
|
94
103
|
elements.testAiBtn.addEventListener("click", testAIConnection);
|
|
95
104
|
elements.saveAiBtn.addEventListener("click", saveAISettings);
|
|
105
|
+
if (elements.aiProvider) {
|
|
106
|
+
elements.aiProvider.addEventListener("change", handleAIProviderChange);
|
|
107
|
+
}
|
|
108
|
+
if (elements.zaiRegion) {
|
|
109
|
+
elements.zaiRegion.addEventListener("change", handleZaiRegionChange);
|
|
110
|
+
}
|
|
96
111
|
|
|
97
112
|
// CLI Settings
|
|
98
113
|
elements.aiModeApi.addEventListener("change", handleAIModeChange);
|
|
@@ -110,6 +125,54 @@
|
|
|
110
125
|
if (elements.saveSelfProbeBtn) {
|
|
111
126
|
elements.saveSelfProbeBtn.addEventListener("click", saveSelfProbeSettings);
|
|
112
127
|
}
|
|
128
|
+
|
|
129
|
+
// Prompt Config Settings
|
|
130
|
+
if (elements.resetPromptsBtn) {
|
|
131
|
+
elements.resetPromptsBtn.addEventListener("click", resetPromptConfigs);
|
|
132
|
+
}
|
|
133
|
+
if (elements.savePromptsBtn) {
|
|
134
|
+
elements.savePromptsBtn.addEventListener("click", savePromptConfigs);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const DEFAULT_API_URLS = {
|
|
139
|
+
openai: 'https://api.openai.com/v1',
|
|
140
|
+
anthropic: 'https://api.anthropic.com/v1',
|
|
141
|
+
google: 'https://generativelanguage.googleapis.com/v1beta',
|
|
142
|
+
nvidia: 'https://integrate.api.nvidia.com/v1',
|
|
143
|
+
zai: 'https://api.z.ai/api/coding/paas/v4',
|
|
144
|
+
'zai-china': 'https://open.bigmodel.cn/api/paas/v4'
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
function handleAIProviderChange(updateUrl = true) {
|
|
148
|
+
const provider = elements.aiProvider?.value || 'google';
|
|
149
|
+
|
|
150
|
+
// Z.AI 專用設定
|
|
151
|
+
if (elements.zaiExtSettings) {
|
|
152
|
+
elements.zaiExtSettings.style.display = provider === 'zai' ? 'block' : 'none';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 更新預設 API URL(僅當 updateUrl 為 true 時)
|
|
156
|
+
if (updateUrl && elements.apiUrl) {
|
|
157
|
+
let defaultUrl;
|
|
158
|
+
if (provider === 'zai') {
|
|
159
|
+
const region = elements.zaiRegion?.value || 'international';
|
|
160
|
+
defaultUrl = region === 'china' ? DEFAULT_API_URLS['zai-china'] : DEFAULT_API_URLS.zai;
|
|
161
|
+
} else {
|
|
162
|
+
defaultUrl = DEFAULT_API_URLS[provider] || '';
|
|
163
|
+
}
|
|
164
|
+
elements.apiUrl.value = defaultUrl;
|
|
165
|
+
elements.apiUrl.placeholder = defaultUrl || 'API 端點 URL';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function handleZaiRegionChange() {
|
|
170
|
+
const region = elements.zaiRegion?.value || 'international';
|
|
171
|
+
if (elements.apiUrl) {
|
|
172
|
+
const defaultUrl = region === 'china' ? DEFAULT_API_URLS['zai-china'] : DEFAULT_API_URLS.zai;
|
|
173
|
+
elements.apiUrl.value = defaultUrl;
|
|
174
|
+
elements.apiUrl.placeholder = defaultUrl || 'API 端點 URL';
|
|
175
|
+
}
|
|
113
176
|
}
|
|
114
177
|
|
|
115
178
|
function handleSelfProbeToggle() {
|
|
@@ -145,18 +208,26 @@
|
|
|
145
208
|
// 從 apiUrl 反向推斷 provider
|
|
146
209
|
const provider = getProviderFromApiUrl(data.settings.apiUrl);
|
|
147
210
|
elements.aiProvider.value = provider;
|
|
211
|
+
// 設置 API URL
|
|
212
|
+
if (elements.apiUrl) {
|
|
213
|
+
elements.apiUrl.value = data.settings.apiUrl || DEFAULT_API_URLS[provider] || '';
|
|
214
|
+
}
|
|
215
|
+
// OpenAI 相容模式
|
|
216
|
+
if (elements.openaiCompatible) {
|
|
217
|
+
elements.openaiCompatible.checked = data.settings.openaiCompatible || false;
|
|
218
|
+
}
|
|
148
219
|
// API 返回的是 apiKeyMasked(遮罩後的 key),顯示給用戶看
|
|
149
220
|
originalApiKeyMasked = data.settings.apiKeyMasked || "";
|
|
150
221
|
elements.apiKey.value = originalApiKeyMasked;
|
|
151
222
|
elements.apiKey.placeholder = originalApiKeyMasked ? "輸入新的 API Key 以更換" : "請輸入 API Key";
|
|
152
223
|
elements.aiModel.value = data.settings.model || "";
|
|
153
|
-
elements.systemPrompt.value = data.settings.systemPrompt || "";
|
|
154
|
-
elements.mcpToolsPrompt.value = data.settings.mcpToolsPrompt || "";
|
|
155
224
|
elements.temperature.value = data.settings.temperature ?? 0.7;
|
|
156
225
|
elements.maxTokens.value = data.settings.maxTokens ?? 1000;
|
|
157
226
|
elements.autoReplyTimerSeconds.value = data.settings.autoReplyTimerSeconds ?? 300;
|
|
158
227
|
elements.maxToolRounds.value = data.settings.maxToolRounds ?? 5;
|
|
159
228
|
elements.debugMode.checked = data.settings.debugMode || false;
|
|
229
|
+
// 更新 UI(不更新 URL,因為已經從資料庫載入)
|
|
230
|
+
handleAIProviderChange(false);
|
|
160
231
|
}
|
|
161
232
|
} catch (error) {
|
|
162
233
|
console.error("Failed to load AI settings:", error);
|
|
@@ -291,6 +362,8 @@
|
|
|
291
362
|
async function testAIConnection() {
|
|
292
363
|
const apiKey = elements.apiKey.value;
|
|
293
364
|
const model = elements.aiModel.value;
|
|
365
|
+
const provider = elements.aiProvider.value;
|
|
366
|
+
const apiUrl = elements.apiUrl?.value || DEFAULT_API_URLS[provider] || '';
|
|
294
367
|
|
|
295
368
|
// 如果 API key 是遮罩值,表示用戶沒有修改,將使用資料庫中的 key
|
|
296
369
|
const apiKeyChanged = apiKey !== originalApiKeyMasked;
|
|
@@ -309,8 +382,12 @@
|
|
|
309
382
|
elements.testAiBtn.textContent = "測試中...";
|
|
310
383
|
|
|
311
384
|
try {
|
|
312
|
-
//
|
|
313
|
-
const payload = {
|
|
385
|
+
// 傳送當前表單的設定值進行測試
|
|
386
|
+
const payload = {
|
|
387
|
+
model,
|
|
388
|
+
apiUrl,
|
|
389
|
+
openaiCompatible: elements.openaiCompatible?.checked || false
|
|
390
|
+
};
|
|
314
391
|
if (apiKeyChanged) {
|
|
315
392
|
payload.apiKey = apiKey;
|
|
316
393
|
}
|
|
@@ -340,20 +417,22 @@
|
|
|
340
417
|
async function saveAISettings() {
|
|
341
418
|
const provider = elements.aiProvider.value;
|
|
342
419
|
const currentApiKey = elements.apiKey.value;
|
|
343
|
-
|
|
420
|
+
|
|
344
421
|
// 只有當用戶真的修改了 API key 才傳送(不是遮罩值)
|
|
345
422
|
const apiKeyChanged = currentApiKey !== originalApiKeyMasked;
|
|
346
|
-
|
|
423
|
+
|
|
424
|
+
// 使用表單中的 API URL,若為空則使用預設值
|
|
425
|
+
const apiUrl = elements.apiUrl?.value || DEFAULT_API_URLS[provider] || '';
|
|
426
|
+
|
|
347
427
|
const settings = {
|
|
348
|
-
apiUrl:
|
|
428
|
+
apiUrl: apiUrl,
|
|
349
429
|
model: elements.aiModel.value,
|
|
350
|
-
systemPrompt: elements.systemPrompt.value,
|
|
351
|
-
mcpToolsPrompt: elements.mcpToolsPrompt.value,
|
|
352
430
|
temperature: parseFloat(elements.temperature.value) || 0.7,
|
|
353
431
|
maxTokens: parseInt(elements.maxTokens.value) || 1000,
|
|
354
432
|
autoReplyTimerSeconds: parseInt(elements.autoReplyTimerSeconds.value) || 300,
|
|
355
433
|
maxToolRounds: parseInt(elements.maxToolRounds.value) || 5,
|
|
356
434
|
debugMode: elements.debugMode.checked,
|
|
435
|
+
openaiCompatible: elements.openaiCompatible?.checked || false,
|
|
357
436
|
};
|
|
358
437
|
|
|
359
438
|
// 只有修改了 API key 才加入
|
|
@@ -515,10 +594,152 @@
|
|
|
515
594
|
}
|
|
516
595
|
}
|
|
517
596
|
|
|
597
|
+
// ============ Prompt Config Functions ============
|
|
598
|
+
|
|
599
|
+
let promptConfigs = [];
|
|
600
|
+
|
|
601
|
+
async function loadPromptConfigs() {
|
|
602
|
+
if (!elements.promptConfigList) return;
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
const response = await fetch(`${API_BASE}/api/settings/prompts`);
|
|
606
|
+
const data = await response.json();
|
|
607
|
+
|
|
608
|
+
if (data.success && data.prompts) {
|
|
609
|
+
promptConfigs = data.prompts;
|
|
610
|
+
renderPromptConfigs();
|
|
611
|
+
} else {
|
|
612
|
+
elements.promptConfigList.innerHTML = '<div style="text-align: center; padding: 20px; color: var(--text-muted);">無法載入配置</div>';
|
|
613
|
+
}
|
|
614
|
+
} catch (error) {
|
|
615
|
+
console.error("Load prompt configs failed:", error);
|
|
616
|
+
elements.promptConfigList.innerHTML = '<div style="text-align: center; padding: 20px; color: var(--text-muted);">載入失敗</div>';
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function renderPromptConfigs() {
|
|
621
|
+
if (!elements.promptConfigList || !promptConfigs.length) return;
|
|
622
|
+
|
|
623
|
+
const showEditor = (id) => id !== 'user_context' && id !== 'tool_results' && id !== 'mcp_tools_detailed';
|
|
624
|
+
|
|
625
|
+
elements.promptConfigList.innerHTML = promptConfigs.map(config => `
|
|
626
|
+
<div class="prompt-config-item" data-id="${config.id}" style="background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: var(--radius-sm); padding: 16px;">
|
|
627
|
+
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-bottom: ${showEditor(config.id) ? '12px' : '0'};">
|
|
628
|
+
<span style="font-weight: 600; color: var(--text-primary); font-size: 14px;">${config.displayName}</span>
|
|
629
|
+
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap;">
|
|
630
|
+
<label style="display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary);">
|
|
631
|
+
第一次:
|
|
632
|
+
<input type="number" class="first-order form-input" value="${config.firstOrder}" min="0" max="1000" step="10" style="width: 60px; padding: 4px 8px;">
|
|
633
|
+
</label>
|
|
634
|
+
<label style="display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary);">
|
|
635
|
+
第二次:
|
|
636
|
+
<input type="number" class="second-order form-input" value="${config.secondOrder}" min="0" max="1000" step="10" style="width: 60px; padding: 4px 8px;">
|
|
637
|
+
</label>
|
|
638
|
+
<label style="display: flex; align-items: center; gap: 6px; font-size: 13px;">
|
|
639
|
+
<input type="checkbox" class="prompt-enabled" ${config.enabled ? 'checked' : ''}>
|
|
640
|
+
啟用
|
|
641
|
+
</label>
|
|
642
|
+
</div>
|
|
643
|
+
</div>
|
|
644
|
+
${showEditor(config.id) ? `
|
|
645
|
+
<textarea class="prompt-content form-textarea" style="min-height: 100px;">${config.content || ''}</textarea>
|
|
646
|
+
` : ''}
|
|
647
|
+
</div>
|
|
648
|
+
`).join('');
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
async function savePromptConfigs() {
|
|
652
|
+
if (!elements.savePromptsBtn) return;
|
|
653
|
+
|
|
654
|
+
elements.savePromptsBtn.disabled = true;
|
|
655
|
+
elements.savePromptsBtn.textContent = "儲存中...";
|
|
656
|
+
|
|
657
|
+
try {
|
|
658
|
+
const items = document.querySelectorAll('.prompt-config-item');
|
|
659
|
+
const updates = [];
|
|
660
|
+
|
|
661
|
+
items.forEach(item => {
|
|
662
|
+
const id = item.dataset.id;
|
|
663
|
+
const firstOrder = parseInt(item.querySelector('.first-order').value) || 0;
|
|
664
|
+
const secondOrder = parseInt(item.querySelector('.second-order').value) || 0;
|
|
665
|
+
const enabled = item.querySelector('.prompt-enabled').checked;
|
|
666
|
+
const contentEl = item.querySelector('.prompt-content');
|
|
667
|
+
const content = contentEl ? contentEl.value || null : null;
|
|
668
|
+
|
|
669
|
+
updates.push({ id, firstOrder, secondOrder, enabled, content });
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
const response = await fetch(`${API_BASE}/api/settings/prompts`, {
|
|
673
|
+
method: 'PUT',
|
|
674
|
+
headers: { 'Content-Type': 'application/json' },
|
|
675
|
+
body: JSON.stringify({ prompts: updates })
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
const data = await response.json();
|
|
679
|
+
|
|
680
|
+
if (data.success) {
|
|
681
|
+
showToast("提示詞配置已儲存", "success");
|
|
682
|
+
if (data.prompts) {
|
|
683
|
+
promptConfigs = data.prompts;
|
|
684
|
+
}
|
|
685
|
+
} else {
|
|
686
|
+
showToast(`儲存失敗: ${data.error || "未知錯誤"}`, "error");
|
|
687
|
+
}
|
|
688
|
+
} catch (error) {
|
|
689
|
+
console.error("Save prompt configs failed:", error);
|
|
690
|
+
showToast("儲存失敗", "error");
|
|
691
|
+
} finally {
|
|
692
|
+
elements.savePromptsBtn.disabled = false;
|
|
693
|
+
elements.savePromptsBtn.textContent = "儲存提示詞設定";
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
async function resetPromptConfigs() {
|
|
698
|
+
if (!confirm("確定要重置為預設配置?")) return;
|
|
699
|
+
if (!elements.resetPromptsBtn) return;
|
|
700
|
+
|
|
701
|
+
elements.resetPromptsBtn.disabled = true;
|
|
702
|
+
elements.resetPromptsBtn.textContent = "重置中...";
|
|
703
|
+
|
|
704
|
+
try {
|
|
705
|
+
const response = await fetch(`${API_BASE}/api/settings/prompts/reset`, { method: 'POST' });
|
|
706
|
+
const data = await response.json();
|
|
707
|
+
|
|
708
|
+
if (data.success) {
|
|
709
|
+
showToast("已重置為預設配置", "success");
|
|
710
|
+
if (data.prompts) {
|
|
711
|
+
promptConfigs = data.prompts;
|
|
712
|
+
renderPromptConfigs();
|
|
713
|
+
}
|
|
714
|
+
} else {
|
|
715
|
+
showToast(`重置失敗: ${data.error || "未知錯誤"}`, "error");
|
|
716
|
+
}
|
|
717
|
+
} catch (error) {
|
|
718
|
+
console.error("Reset prompt configs failed:", error);
|
|
719
|
+
showToast("重置失敗", "error");
|
|
720
|
+
} finally {
|
|
721
|
+
elements.resetPromptsBtn.disabled = false;
|
|
722
|
+
elements.resetPromptsBtn.textContent = "恢復預設";
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
518
726
|
function showToast(message, type = "info") {
|
|
519
727
|
const toast = document.createElement("div");
|
|
520
728
|
toast.className = `toast toast-${type}`;
|
|
521
|
-
|
|
729
|
+
|
|
730
|
+
const messageSpan = document.createElement("span");
|
|
731
|
+
messageSpan.textContent = message;
|
|
732
|
+
toast.appendChild(messageSpan);
|
|
733
|
+
|
|
734
|
+
// 添加關閉按鈕
|
|
735
|
+
const closeBtn = document.createElement("button");
|
|
736
|
+
closeBtn.textContent = "×";
|
|
737
|
+
closeBtn.style.cssText = "margin-left: 12px; background: none; border: none; color: inherit; font-size: 18px; cursor: pointer; padding: 0 4px;";
|
|
738
|
+
closeBtn.onclick = () => {
|
|
739
|
+
toast.classList.remove("show");
|
|
740
|
+
setTimeout(() => toast.remove(), 300);
|
|
741
|
+
};
|
|
742
|
+
toast.appendChild(closeBtn);
|
|
522
743
|
|
|
523
744
|
elements.toastContainer.appendChild(toast);
|
|
524
745
|
|
|
@@ -526,12 +747,15 @@
|
|
|
526
747
|
toast.classList.add("show");
|
|
527
748
|
}, 10);
|
|
528
749
|
|
|
529
|
-
|
|
530
|
-
|
|
750
|
+
// 錯誤訊息不自動關閉,其他類型 3 秒後關閉
|
|
751
|
+
if (type !== "error") {
|
|
531
752
|
setTimeout(() => {
|
|
532
|
-
toast.remove();
|
|
533
|
-
|
|
534
|
-
|
|
753
|
+
toast.classList.remove("show");
|
|
754
|
+
setTimeout(() => {
|
|
755
|
+
toast.remove();
|
|
756
|
+
}, 300);
|
|
757
|
+
}, 3000);
|
|
758
|
+
}
|
|
535
759
|
}
|
|
536
760
|
|
|
537
761
|
if (document.readyState === "loading") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hirohsu/user-web-feedback",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.8",
|
|
4
4
|
"description": "基於Node.js的MCP回饋收集器 - 支持AI工作彙報和用戶回饋收集",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"bin": {
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"build": "tsup",
|
|
35
35
|
"dev": "tsx watch --clear-screen=false src/cli.ts",
|
|
36
36
|
"start": "node dist/cli.js",
|
|
37
|
-
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
37
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --maxWorkers=50%",
|
|
38
|
+
"test:ci": "node --experimental-vm-modules node_modules/jest/bin/jest.js --maxWorkers=1 --forceExit",
|
|
38
39
|
"test:integration": "node --experimental-vm-modules node_modules/jest/bin/jest.js src/__tests__/integration.test.ts --testPathIgnorePatterns=[] --forceExit",
|
|
39
40
|
"test:all": "npm run test && npm run test:integration",
|
|
40
41
|
"test:watch": "jest --watch",
|
|
@@ -45,7 +46,9 @@
|
|
|
45
46
|
"prepublishOnly": "npm run clean && npm run build && node scripts/remove-sourcemaps.cjs"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
48
|
-
"
|
|
49
|
+
"@hirohsu/user-web-feedback": "^2.8.2",
|
|
50
|
+
"better-sqlite3": "^12.4.1",
|
|
51
|
+
"openai": "^6.16.0"
|
|
49
52
|
},
|
|
50
53
|
"optionalDependencies": {
|
|
51
54
|
"@google/generative-ai": "^0.24.1",
|