@shawnstack/quickforge 1.6.2 → 1.6.3

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 (37) hide show
  1. package/README.md +452 -450
  2. package/dist/assets/AgentProfilesPage-BTWTENpJ.js +1 -0
  3. package/dist/assets/{ChatPanelHost-CnQoAfxW.js → ChatPanelHost-DBCQpcp1.js} +1 -1
  4. package/dist/assets/PluginsPage-DRbQLXc6.js +1 -0
  5. package/dist/assets/ScheduledTasksPage-_Te96TTL.js +2 -0
  6. package/dist/assets/SettingsWorkspacePage-B3KByRv9.js +1412 -0
  7. package/dist/assets/{SharedConversationPage-D97o9Ycp.js → SharedConversationPage-CJr-rzR4.js} +1 -1
  8. package/dist/assets/TerminalDock-DSsVoL0f.js +2 -0
  9. package/dist/assets/WorkspaceInspector-HuIqe-Y6.js +3 -0
  10. package/dist/assets/{WorkspaceReaderDialog-B2WEMVuF.js → WorkspaceReaderDialog-au2UL_Ti.js} +1 -1
  11. package/dist/assets/{diff-line-counts-B60LmPZX.js → diff-line-counts-CnZu37tM.js} +1 -1
  12. package/dist/assets/{icons-BlAm_pUr.js → icons-B0ihJZtt.js} +1 -1
  13. package/dist/assets/index-CTo6RkZO.js +63 -0
  14. package/dist/assets/index-hb3vYF5_.css +3 -0
  15. package/dist/assets/mcp-servers-dialog-1Y9X80tB.js +5 -0
  16. package/dist/assets/{monaco-C5Lhy6-h.js → monaco-BKEc9mhB.js} +1 -1
  17. package/dist/assets/{react-vendor--m65aOgw.js → react-vendor-EwqQ8x7m.js} +1 -1
  18. package/dist/assets/skills-dialog-vMwLkH49.js +1 -0
  19. package/dist/assets/{useAppTheme-ChXh3B2c.js → useAppTheme-CG1_MfzA.js} +1 -1
  20. package/dist/index.html +6 -6
  21. package/package.json +1 -1
  22. package/server/agent-manager.mjs +3 -1
  23. package/server/index.mjs +4 -3
  24. package/server/routes/backup.mjs +80 -6
  25. package/server/routes/system.mjs +5 -0
  26. package/server/storage.mjs +6 -5
  27. package/server/utils/package-update.mjs +46 -0
  28. package/dist/assets/AgentProfilesPage-wpt1cJV0.js +0 -1
  29. package/dist/assets/PluginsPage-0wwQydcP.js +0 -1
  30. package/dist/assets/ScheduledTasksPage-qGHyGkjw.js +0 -2
  31. package/dist/assets/SettingsWorkspacePage-CeYg_VJl.js +0 -1320
  32. package/dist/assets/TerminalDock-D4zLoahb.js +0 -2
  33. package/dist/assets/WorkspaceInspector-Bn-am8g6.js +0 -3
  34. package/dist/assets/index-C3bhHGO4.css +0 -3
  35. package/dist/assets/index-C_DXqhW8.js +0 -69
  36. package/dist/assets/mcp-servers-dialog-DEyq3gmP.js +0 -20
  37. package/dist/assets/skills-dialog-CZ3CuVtQ.js +0 -1
@@ -0,0 +1,1412 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/AgentProfilesPage-BTWTENpJ.js","assets/rolldown-runtime-DWdDZTNf.js","assets/index-CTo6RkZO.js","assets/pi-ai-Cx633yhb.js","assets/pi-web-ui-DFNE2m5b.js","assets/lit-vendor-Dr3cpBGF.js","assets/css-utils-rkE68RDy.js","assets/icons-B0ihJZtt.js","assets/react-vendor-EwqQ8x7m.js","assets/skills-dialog-vMwLkH49.js","assets/logger-B65Akg8A.js","assets/index-hb3vYF5_.css","assets/mcp-servers-dialog-1Y9X80tB.js","assets/PluginsPage-DRbQLXc6.js","assets/plugin-api-UKg_cgSG.js","assets/ScheduledTasksPage-_Te96TTL.js"])))=>i.map(i=>d[i]);
2
+ import{i as e}from"./rolldown-runtime-DWdDZTNf.js";import{Dt as t,Et as n,Ot as r,at as i,b as a,f as o,h as s,jt as c,k as l,kt as u,l as d,ot as ee,s as te,ut as ne,wt as re,z as ie}from"./icons-B0ihJZtt.js";import{n as ae,r as oe}from"./react-vendor-EwqQ8x7m.js";import{p as f}from"./lit-vendor-Dr3cpBGF.js";import{a as p,u as m}from"./pi-web-ui-DFNE2m5b.js";import{y as h}from"./pi-ai-Cx633yhb.js";import{t as g}from"./logger-B65Akg8A.js";import{$ as se,A as ce,C as _,D as le,E as ue,G as de,H as fe,J as pe,M as v,Q as me,S as y,T as he,U as ge,V as b,X as _e,Y as x,_ as ve,b as ye,et as S,g as be,it as C,j as w,k as xe,nt as Se,q as Ce,rt as we,tt as Te,v as Ee,w as De,x as Oe,y as ke}from"./index-CTo6RkZO.js";var T=e(c(),1),Ae={openai:{name:`OpenAI`,baseUrl:`https://api.openai.com/v1`,protocol:`openai-completions`,modelId:`gpt-4o`},deepseek:{name:`DeepSeek`,baseUrl:`https://api.deepseek.com/v1`,protocol:`openai-completions`,modelId:`deepseek-chat`},glm:{name:`Zhipu GLM`,baseUrl:`https://open.bigmodel.cn/api/paas/v4`,protocol:`openai-completions`,modelId:`glm-4-plus`},ollama:{name:`Ollama`,baseUrl:`http://localhost:11434/v1`,protocol:`openai-completions`,modelId:`llama3.1`},litellm:{name:b.name,baseUrl:b.baseUrl,protocol:`openai-completions`,modelId:b.modelId}},je=[{key:`openai`,label:C(`presetOpenai`)},{key:`deepseek`,label:C(`presetDeepseek`)},{key:`glm`,label:C(`presetGlm`)},{key:`ollama`,label:C(`presetOllama`)},{key:`litellm`,label:C(`presetLitellm`)},{key:`custom`,label:C(`presetCustom`)}],E=()=>({modelId:``,contextWindow:b.contextWindow,maxTokens:b.maxTokens,reasoning:!0,open:!1}),D=()=>({name:b.name,baseUrl:b.baseUrl,apiKey:``,headerRows:[],protocol:`openai-completions`,models:[E()]}),Me=class extends p{providers=[];form=D();editingProviderId;formOpen=!1;loading=!0;apiKeyVisible=!1;advancedOpen=!1;activePreset=``;testing=!1;testResult=`idle`;testError=``;autoEditProviderName=null;async connectedCallback(){if(super.connectedCallback(),await this.loadProviders(),this.autoEditProviderName){let e=this.providers.find(e=>e.name===this.autoEditProviderName);e&&await this.openEditForm(e),this.autoEditProviderName=null}}getTabName(){return C(`customModels`)}async loadProviders(){this.loading=!0,this.requestUpdate();try{this.providers=await m().customProviders.getAll()}catch(e){g.error(`Failed to load custom providers:`,e),this.providers=[]}finally{this.loading=!1,this.requestUpdate()}}resetTestState(){this.testing=!1,this.testResult=`idle`,this.testError=``}openAddForm(){this.editingProviderId=void 0,this.form=D(),this.apiKeyVisible=!1,this.advancedOpen=!1,this.activePreset=``,this.resetTestState(),this.formOpen=!0,this.requestUpdate()}async openEditForm(e){let t=await m().providerKeys.get(e.name)??e.apiKey??``,n=e.models??[],r=n.length>0?n.map(e=>({modelId:e.id,contextWindow:e.contextWindow??b.contextWindow,maxTokens:e.maxTokens??b.maxTokens,reasoning:e.reasoning===!0,open:!1})):[E()],i=n[0]?.headers??{},a=Object.entries(i).map(([e,t])=>({key:e,value:String(t)}));this.editingProviderId=e.id,this.form={providerId:e.id,id:e.id,name:e.name,baseUrl:e.baseUrl,apiKey:t,headerRows:a,protocol:e.type===`anthropic-messages`?`anthropic-messages`:`openai-completions`,models:r},this.apiKeyVisible=!1,this.advancedOpen=a.length>0||e.type===`anthropic-messages`,this.activePreset=``,this.resetTestState(),this.formOpen=!0,this.requestUpdate()}closeForm(){this.formOpen=!1,this.editingProviderId=void 0,this.form=D(),this.apiKeyVisible=!1,this.advancedOpen=!1,this.activePreset=``,this.resetTestState(),this.requestUpdate()}toggleApiKeyVisibility(){this.apiKeyVisible=!this.apiKeyVisible,this.requestUpdate()}updateForm(e,t){this.form={...this.form,[e]:t},(e===`name`||e===`baseUrl`)&&(this.activePreset=``),this.resetTestState(),this.requestUpdate()}updateModelField(e,t,n){let r=this.form.models.map((r,i)=>i===e?{...r,[t]:n}:r);this.form={...this.form,models:r},t===`modelId`&&(this.activePreset=``,this.resetTestState()),this.requestUpdate()}addModelRow(){this.form={...this.form,models:[...this.form.models,E()]},this.requestUpdate()}removeModelRow(e){let t=this.form.models.filter((t,n)=>n!==e);this.form={...this.form,models:t},this.requestUpdate()}toggleModelExpanded(e){let t=this.form.models.map((t,n)=>n===e?{...t,open:!t.open}:t);this.form={...this.form,models:t},this.requestUpdate()}addHeaderRow(){this.form={...this.form,headerRows:[...this.form.headerRows,{key:``,value:``}]},this.requestUpdate()}updateHeaderRow(e,t,n){let r=this.form.headerRows.map((r,i)=>i===e?{...r,[t]:n}:r);this.form={...this.form,headerRows:r},this.requestUpdate()}removeHeaderRow(e){let t=this.form.headerRows.filter((t,n)=>n!==e);this.form={...this.form,headerRows:t},this.requestUpdate()}applyPreset(e){if(this.activePreset=e,e===`custom`)this.form={...this.form,name:``,baseUrl:``,protocol:`openai-completions`,models:[E()]};else{let t=Ae[e];this.form={...this.form,name:t.name,baseUrl:t.baseUrl,protocol:t.protocol,models:[{...E(),modelId:t.modelId,open:!1}]}}this.resetTestState(),this.requestUpdate()}buildHeadersFromRows(){let e={};for(let t of this.form.headerRows){let n=t.key.trim(),r=t.value.trim();n&&r&&(e[n]=r)}return e}buildModel(e,t){let n=this.form.name.trim(),r=this.form.baseUrl.trim(),i=e.reasoning===!0,a=r.includes(`api.deepseek.com`);return Ce({id:e.modelId,name:`${e.modelId} (${n})`,api:this.form.protocol,provider:n,baseUrl:r.replace(/\/$/,``),reasoning:i,input:[`text`,`image`],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:Number(e.contextWindow)||b.contextWindow,maxTokens:Number(e.maxTokens)||b.maxTokens,headers:Object.keys(t).length>0?t:void 0,thinkingLevelMap:a&&i?{low:`high`,medium:`high`,high:`high`,xhigh:`max`}:void 0,compat:this.form.protocol===`openai-completions`?{supportsStore:!1,supportsDeveloperRole:!1,supportsReasoningEffort:i,supportsUsageInStreaming:!1,supportsStrictMode:!1,maxTokensField:`max_tokens`,...a&&i?{requiresReasoningContentOnAssistantMessages:!0,thinkingFormat:`deepseek`}:{}}:void 0})}async testConnection(){let e=this.form.baseUrl.trim(),t=this.form.models.find(e=>e.modelId.trim());if(!e||!t){this.testing=!1,this.testResult=`fail`,this.testError=C(`testRequiresFields`),this.requestUpdate();return}let n=this.buildHeadersFromRows(),r=this.buildModel(t,n);this.testing=!0,this.testResult=`idle`,this.testError=``,this.requestUpdate();try{let e=await(await fetch(`/api/models/test-connection`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({model:r,apiKey:this.form.apiKey.trim()})})).json().catch(()=>({}));e?.ok?(this.testResult=`ok`,this.testError=``):(this.testResult=`fail`,this.testError=e?.error||C(`connectionFailed`))}catch(e){this.testResult=`fail`,this.testError=e?.message||C(`connectionFailed`)}finally{this.testing=!1,this.requestUpdate()}}async saveModel(){let e=this.form.name.trim(),t=this.form.baseUrl.trim();if(!e||!t){w(C(`fillProviderBaseUrlModel`));return}let n=this.form.models.filter(e=>e.modelId.trim());if(n.length===0){w(C(`atLeastOneModel`));return}let r=n.map(e=>e.modelId.trim());if(new Set(r).size!==r.length){w(C(`duplicateModelId`));return}let i=this.buildHeadersFromRows(),a=n.map(e=>this.buildModel(e,i)),o=this.form.apiKey.trim(),s=this.editingProviderId?this.providers.find(e=>e.id===this.editingProviderId):void 0,c={id:this.editingProviderId??_e(),name:e,type:this.form.protocol,baseUrl:a[0].baseUrl,apiKey:o||void 0,models:a};try{let e=m();await e.customProviders.set(c),s&&s.name!==c.name&&await e.providerKeys.delete(s.name),o?await e.providerKeys.set(c.name,o):await e.providerKeys.delete(c.name),this.closeForm(),await this.loadProviders()}catch(e){g.error(`Failed to save custom model:`,e),w(C(`saveCustomModelFailed`))}}async deleteProvider(e){if(await v({description:C(`confirmDeleteProvider`,{name:e.name}),confirmLabel:C(`confirmDelete`),cancelLabel:C(`cancel`),variant:`destructive`}))try{let t=m();await t.customProviders.delete(e.id),await t.providerKeys.delete(e.name),await this.loadProviders()}catch(e){g.error(`Failed to delete custom provider:`,e),w(C(`deleteFailed`))}}renderProvider(e){let t=e.models??[],n=t.length,r=e.type===`anthropic-messages`?`Anthropic Messages`:`OpenAI Compatible`;return f`
3
+ <div class="quickforge-settings-list-item">
4
+ <div class="quickforge-settings-list-item-main">
5
+ <div class="flex min-w-0 items-start gap-3">
6
+ <span class="quickforge-settings-avatar" aria-hidden="true">${e.name.trim().charAt(0).toUpperCase()||`M`}</span>
7
+ <div class="min-w-0 flex-1">
8
+ <div class="quickforge-settings-row-title">${e.name}</div>
9
+ <div class="quickforge-settings-row-description break-all">${e.baseUrl}</div>
10
+ <div class="quickforge-settings-meta">
11
+ <span class="quickforge-settings-badge quickforge-settings-badge-muted">${C(`providerProtocol`)}: ${r}</span>
12
+ <span class="quickforge-settings-badge quickforge-settings-badge-info">${C(`modelsCount`,{count:n})}</span>
13
+ ${n===0?f`<span class="quickforge-settings-badge quickforge-settings-badge-warning">${C(`noModelAdded`)}</span>`:null}
14
+ </div>
15
+ ${n>0?f`
16
+ <div class="quickforge-settings-meta">
17
+ ${t.slice(0,6).map(e=>f`
18
+ <code class="quickforge-settings-command-name">${e.id}</code>
19
+ `)}
20
+ ${t.length>6?f`<span class="quickforge-settings-badge quickforge-settings-badge-muted">+${t.length-6}</span>`:null}
21
+ </div>
22
+ `:null}
23
+ </div>
24
+ </div>
25
+ </div>
26
+ <div class="quickforge-settings-list-item-actions">
27
+ <button
28
+ class="quickforge-settings-button quickforge-settings-button-secondary quickforge-settings-button-compact"
29
+ type="button"
30
+ @click=${()=>this.openEditForm(e)}
31
+ >
32
+ ${C(`editModel`)}
33
+ </button>
34
+ <button
35
+ class="quickforge-settings-button quickforge-settings-button-danger quickforge-settings-button-compact"
36
+ type="button"
37
+ @click=${()=>this.deleteProvider(e)}
38
+ >
39
+ ${C(`delete`)}
40
+ </button>
41
+ </div>
42
+ </div>
43
+ `}renderPresetChips(){return f`
44
+ <div class="quickforge-settings-form-row">
45
+ <span class="quickforge-settings-form-label">${C(`presets`)}</span>
46
+ <div class="quickforge-settings-segmented quickforge-settings-segmented-wrap">
47
+ ${je.map(e=>f`
48
+ <button
49
+ class="quickforge-settings-segmented-option ${this.activePreset===e.key?`quickforge-settings-segmented-option-active`:``}"
50
+ type="button"
51
+ @click=${()=>this.applyPreset(e.key)}
52
+ >
53
+ ${e.label}
54
+ </button>
55
+ `)}
56
+ </div>
57
+ </div>
58
+ `}renderModelRow(e,t){let n=e.open===!0;return f`
59
+ <div class="quickforge-settings-subrow quickforge-settings-row-align-start">
60
+ <button
61
+ class="quickforge-settings-icon-action"
62
+ type="button"
63
+ title=${C(`expandModel`)}
64
+ aria-expanded=${n?`true`:`false`}
65
+ @click=${()=>this.toggleModelExpanded(t)}
66
+ >
67
+ ${n?`▾`:`▸`}
68
+ </button>
69
+ <div class="quickforge-settings-list-item-main">
70
+ <input
71
+ class="quickforge-settings-input quickforge-settings-mono"
72
+ .value=${e.modelId}
73
+ @input=${e=>this.updateModelField(t,`modelId`,e.target.value)}
74
+ placeholder=${C(`modelIdPlaceholder`)}
75
+ />
76
+ ${n?f`
77
+ <div class="quickforge-settings-model-grid mt-3">
78
+ <label class="quickforge-settings-form-row">
79
+ <span class="quickforge-settings-form-label">${C(`contextWindow`)}</span>
80
+ <input
81
+ class="quickforge-settings-input"
82
+ .value=${String(e.contextWindow)}
83
+ type="number"
84
+ @input=${e=>this.updateModelField(t,`contextWindow`,Number(e.target.value))}
85
+ />
86
+ </label>
87
+ <label class="quickforge-settings-form-row">
88
+ <span class="quickforge-settings-form-label">${C(`maxTokens`)}</span>
89
+ <input
90
+ class="quickforge-settings-input"
91
+ .value=${String(e.maxTokens)}
92
+ type="number"
93
+ @input=${e=>this.updateModelField(t,`maxTokens`,Number(e.target.value))}
94
+ />
95
+ </label>
96
+ </div>
97
+ <label class="mt-3 flex items-center gap-2 text-sm text-muted-foreground">
98
+ <input
99
+ type="checkbox"
100
+ .checked=${e.reasoning}
101
+ @change=${e=>this.updateModelField(t,`reasoning`,e.target.checked)}
102
+ />
103
+ <span>${C(`reasoningModel`)}</span>
104
+ </label>
105
+ `:null}
106
+ </div>
107
+ ${this.form.models.length>1?f`
108
+ <button
109
+ class="quickforge-settings-icon-action quickforge-settings-icon-action-danger"
110
+ type="button"
111
+ title=${C(`delete`)}
112
+ @click=${()=>this.removeModelRow(t)}
113
+ >
114
+
115
+ </button>
116
+ `:null}
117
+ </div>
118
+ `}renderHeadersEditor(){return f`
119
+ <div class="quickforge-settings-form-row">
120
+ <span class="quickforge-settings-form-label">
121
+ ${C(`customHeaders`)}
122
+ <quickforge-info-tip .label=${C(`customHeadersHelp`)}></quickforge-info-tip>
123
+ </span>
124
+ ${this.form.headerRows.length===0?f``:f`
125
+ <div class="quickforge-settings-nested-list">
126
+ ${this.form.headerRows.map((e,t)=>f`
127
+ <div class="quickforge-settings-subrow">
128
+ <input
129
+ class="quickforge-settings-input"
130
+ .value=${e.key}
131
+ @input=${e=>this.updateHeaderRow(t,`key`,e.target.value)}
132
+ placeholder=${C(`headerName`)}
133
+ />
134
+ <input
135
+ class="quickforge-settings-input"
136
+ .value=${e.value}
137
+ @input=${e=>this.updateHeaderRow(t,`value`,e.target.value)}
138
+ placeholder=${C(`headerValue`)}
139
+ />
140
+ <button
141
+ class="quickforge-settings-icon-action quickforge-settings-icon-action-danger"
142
+ type="button"
143
+ title=${C(`removeHeader`)}
144
+ @click=${()=>this.removeHeaderRow(t)}
145
+ >
146
+
147
+ </button>
148
+ </div>
149
+ `)}
150
+ </div>
151
+ `}
152
+ <button
153
+ class="quickforge-settings-button quickforge-settings-button-secondary quickforge-settings-button-compact"
154
+ type="button"
155
+ @click=${()=>this.addHeaderRow()}
156
+ >
157
+ + ${C(`addHeader`)}
158
+ </button>
159
+ </div>
160
+ `}renderTestStatus(){return this.testResult===`idle`&&!this.testing?f``:this.testing?f`<span class="quickforge-settings-status">${C(`testingConnection`)}</span>`:this.testResult===`ok`?f`<span class="quickforge-settings-badge quickforge-settings-badge-success">✓ ${C(`connectionOk`)}</span>`:f`<span class="quickforge-settings-alert">✗ ${this.testError||C(`connectionFailed`)}</span>`}renderForm(){return f`
161
+ <section class="quickforge-settings-section" aria-label=${this.editingProviderId?C(`editCustomModel`):C(`addCustomModel`)}>
162
+ <div class="quickforge-settings-toolbar">
163
+ <button
164
+ class="quickforge-settings-button quickforge-settings-button-secondary"
165
+ type="button"
166
+ @click=${()=>this.closeForm()}
167
+ >
168
+ <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg>
169
+ ${C(`back`)}
170
+ </button>
171
+ <div class="quickforge-settings-row-main">
172
+ <div class="quickforge-settings-row-title">${this.editingProviderId?C(`editCustomModel`):C(`addCustomModel`)}</div>
173
+ <div class="quickforge-settings-row-description">${C(`customModelsDescription`)}</div>
174
+ </div>
175
+ </div>
176
+
177
+ <div class="quickforge-settings-form-grid">
178
+ ${this.renderPresetChips()}
179
+
180
+ <label class="quickforge-settings-form-row">
181
+ <span class="quickforge-settings-form-label">${C(`providerName`)}</span>
182
+ <input
183
+ class="quickforge-settings-input"
184
+ .value=${this.form.name}
185
+ @input=${e=>this.updateForm(`name`,e.target.value)}
186
+ placeholder=${C(`providerNamePlaceholder`)}
187
+ />
188
+ </label>
189
+
190
+ <label class="quickforge-settings-form-row">
191
+ <span class="quickforge-settings-form-label">Base URL</span>
192
+ <input
193
+ class="quickforge-settings-input"
194
+ .value=${this.form.baseUrl}
195
+ @input=${e=>this.updateForm(`baseUrl`,e.target.value)}
196
+ placeholder=${this.form.protocol===`anthropic-messages`?`e.g., https://api.anthropic.com`:`e.g., http://localhost:4000/v1`}
197
+ />
198
+ </label>
199
+
200
+ <label class="quickforge-settings-form-row">
201
+ <span class="quickforge-settings-form-label">${C(`apiKey`)}</span>
202
+ <div class="quickforge-settings-inline-field">
203
+ <input
204
+ class="quickforge-settings-input pr-10"
205
+ .value=${this.form.apiKey}
206
+ type=${this.apiKeyVisible?`text`:`password`}
207
+ @input=${e=>this.updateForm(`apiKey`,e.target.value)}
208
+ placeholder=${C(`apiKeyPlaceholder`)}
209
+ />
210
+ <button
211
+ class="quickforge-settings-inline-field-button"
212
+ type="button"
213
+ title=${this.apiKeyVisible?C(`hideApiKey`):C(`showApiKey`)}
214
+ aria-label=${this.apiKeyVisible?C(`hideApiKey`):C(`showApiKey`)}
215
+ aria-pressed=${this.apiKeyVisible?`true`:`false`}
216
+ @click=${()=>this.toggleApiKeyVisibility()}
217
+ >
218
+ ${this.apiKeyVisible?f`<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.53 13.53 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><line x1="2" y1="2" x2="22" y2="22"/><path d="M8.53 8.53A5 5 0 0 0 12 17a5 5 0 0 0 3.47-8.53"/></svg>`:f`<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>`}
219
+ </button>
220
+ </div>
221
+ </label>
222
+
223
+ <div class="quickforge-settings-form-row">
224
+ <div class="flex items-center justify-between gap-3">
225
+ <span class="quickforge-settings-form-label">${C(`modelsList`)}</span>
226
+ <button
227
+ class="quickforge-settings-button quickforge-settings-button-secondary quickforge-settings-button-compact"
228
+ type="button"
229
+ @click=${()=>this.addModelRow()}
230
+ >
231
+ + ${C(`addModel`)}
232
+ </button>
233
+ </div>
234
+ <div class="quickforge-settings-nested-list">
235
+ ${this.form.models.map((e,t)=>this.renderModelRow(e,t))}
236
+ </div>
237
+ </div>
238
+
239
+ <button
240
+ class="quickforge-settings-details-toggle"
241
+ type="button"
242
+ aria-expanded=${this.advancedOpen?`true`:`false`}
243
+ @click=${()=>{this.advancedOpen=!this.advancedOpen,this.requestUpdate()}}
244
+ >
245
+ <span>${this.advancedOpen?`▾`:`▸`}</span>
246
+ ${C(`providerAdvanced`)}
247
+ </button>
248
+
249
+ ${this.advancedOpen?f`
250
+ <div class="quickforge-settings-form-grid rounded-lg border border-border p-3">
251
+ <label class="quickforge-settings-form-row">
252
+ <span class="quickforge-settings-form-label">
253
+ ${C(`protocolType`)}
254
+ <quickforge-info-tip .label=${C(`protocolHelp`)}></quickforge-info-tip>
255
+ </span>
256
+ <select
257
+ class="quickforge-settings-select"
258
+ .value=${this.form.protocol}
259
+ @change=${e=>this.updateForm(`protocol`,e.target.value)}
260
+ >
261
+ <option value="openai-completions">OpenAI Compatible / Chat Completions</option>
262
+ <option value="anthropic-messages">Anthropic Messages</option>
263
+ </select>
264
+ </label>
265
+ ${this.renderHeadersEditor()}
266
+ </div>
267
+ `:null}
268
+ </div>
269
+
270
+ <div class="quickforge-settings-row">
271
+ <div class="quickforge-settings-row-main">
272
+ ${this.renderTestStatus()}
273
+ </div>
274
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide">
275
+ <button
276
+ class="quickforge-settings-button quickforge-settings-button-secondary"
277
+ type="button"
278
+ @click=${()=>this.closeForm()}
279
+ >
280
+ ${C(`cancel`)}
281
+ </button>
282
+ <button
283
+ class="quickforge-settings-button quickforge-settings-button-secondary"
284
+ type="button"
285
+ ?disabled=${this.testing}
286
+ @click=${()=>this.testConnection()}
287
+ >
288
+ ${this.testing?C(`testingConnection`):C(`testConnection`)}
289
+ </button>
290
+ <button
291
+ class="quickforge-settings-button quickforge-settings-button-primary"
292
+ type="button"
293
+ @click=${()=>this.saveModel()}
294
+ >
295
+ ${C(`save`)}
296
+ </button>
297
+ </div>
298
+ </div>
299
+ </section>
300
+ `}render(){return this.formOpen?f`
301
+ <div class="quickforge-settings-stack">
302
+ <div class="quickforge-settings-heading">
303
+ <h3 class="quickforge-settings-title">
304
+ ${this.editingProviderId?C(`editCustomModel`):C(`addCustomModel`)}
305
+ <quickforge-info-tip .label=${C(`customModelsDescription`)}></quickforge-info-tip>
306
+ </h3>
307
+ </div>
308
+ ${this.renderForm()}
309
+ </div>
310
+ `:f`
311
+ <div class="quickforge-settings-stack">
312
+ <section class="quickforge-settings-section" aria-label=${C(`customModelsTitle`)}>
313
+ <div class="quickforge-settings-toolbar">
314
+ <div>
315
+ <div class="quickforge-settings-row-title">${C(`customModelsTitle`)}</div>
316
+ <div class="quickforge-settings-row-description">${C(`customModelsDescription`)}</div>
317
+ </div>
318
+ <button
319
+ class="quickforge-settings-button quickforge-settings-button-primary"
320
+ type="button"
321
+ @click=${()=>this.openAddForm()}
322
+ >
323
+ ${C(`addModel`)}
324
+ </button>
325
+ </div>
326
+
327
+ ${this.loading?f`<div class="quickforge-settings-empty-row">${C(`loading`)}</div>`:this.providers.length===0?f`<div class="quickforge-settings-empty-row">${C(`noCustomModels`)}</div>`:this.providers.map(e=>this.renderProvider(e))}
328
+ </section>
329
+ </div>
330
+ `}},O=`quickforge-custom-providers-only-tab`;customElements.get(O)||customElements.define(O,Me);function Ne(e){let t=document.createElement(O);return e&&(t.autoEditProviderName=e),t}var Pe=[{value:`light`,label:()=>C(`lightTheme`)},{value:`dark`,label:()=>C(`darkTheme`)}],Fe=class extends p{theme=ke();interfaceFontSizePx=y.interfaceFontSizePx;messageFontSizePx=y.messageFontSizePx;loading=!0;error=``;getTabName(){return C(`appearance`)}async connectedCallback(){super.connectedCallback(),await this.loadSettings()}async loadSettings(){this.loading=!0,this.error=``,this.requestUpdate();try{let e=m(),[t,n]=await Promise.all([ye(e),he(e)]);this.theme=t.theme,this.interfaceFontSizePx=n.interfaceFontSizePx,this.messageFontSizePx=n.messageFontSizePx}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.loading=!1,this.requestUpdate()}}async selectTheme(e){if(this.theme!==e){this.theme=e,this.requestUpdate();try{await Oe(m(),{theme:e}),this.error=``,this.requestUpdate()}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`),this.requestUpdate()}}}currentFontSizeSettings(){return ue({interfaceFontSizePx:this.interfaceFontSizePx,messageFontSizePx:this.messageFontSizePx})}previewFontSize(e){let t=ue(e);this.interfaceFontSizePx=t.interfaceFontSizePx,this.messageFontSizePx=t.messageFontSizePx,De(t),this.requestUpdate()}updateInterfaceFontSize(e){this.previewFontSize({...this.currentFontSizeSettings(),interfaceFontSizePx:Number(e)||y.interfaceFontSizePx})}updateMessageFontSize(e){this.previewFontSize({...this.currentFontSizeSettings(),messageFontSizePx:Number(e)||y.messageFontSizePx})}async saveFontSize(){try{await le(m(),this.currentFontSizeSettings()),this.error=``,this.requestUpdate()}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`),this.requestUpdate()}}renderThemeOption(e){let t=this.theme===e.value;return f`
331
+ <button
332
+ type="button"
333
+ class="quickforge-settings-segmented-option ${t?`quickforge-settings-segmented-option-active`:``}"
334
+ aria-pressed=${t?`true`:`false`}
335
+ @click=${()=>this.selectTheme(e.value)}
336
+ >
337
+ ${e.label()}
338
+ </button>
339
+ `}getFontSizeRangeProgress(e){return(e-_.min)/(_.max-_.min)*100}renderFontSizeSlider(e,t,n,r){return f`
340
+ <div class="quickforge-settings-row">
341
+ <div class="quickforge-settings-row-main">
342
+ <div class="quickforge-settings-row-title">
343
+ ${e}
344
+ ${t?f`<quickforge-info-tip .label=${t}></quickforge-info-tip>`:null}
345
+ </div>
346
+ <div class="quickforge-settings-row-description">${C(`fontSizeRangeDescription`)}</div>
347
+ </div>
348
+ <div class="quickforge-settings-row-control quickforge-settings-slider-control">
349
+ <span class="quickforge-settings-value-badge">${n}px</span>
350
+ <input
351
+ class="quickforge-font-size-slider"
352
+ style=${`--quickforge-font-size-slider-progress: ${this.getFontSizeRangeProgress(n)}%`}
353
+ type="range"
354
+ min=${String(_.min)}
355
+ max=${String(_.max)}
356
+ step="1"
357
+ .value=${String(n)}
358
+ @input=${e=>r(e.target.value)}
359
+ @change=${()=>this.saveFontSize()}
360
+ />
361
+ </div>
362
+ </div>
363
+ `}render(){return this.loading?f`<div class="text-sm text-muted-foreground">${C(`loading`)}</div>`:f`
364
+ <div class="quickforge-settings-stack">
365
+ <section class="quickforge-settings-section" aria-label=${C(`appearance`)}>
366
+ <div class="quickforge-settings-row">
367
+ <div class="quickforge-settings-row-main">
368
+ <div class="quickforge-settings-row-title">
369
+ ${C(`theme`)}
370
+ <quickforge-info-tip .label=${C(`themeDescription`)}></quickforge-info-tip>
371
+ </div>
372
+ <div class="quickforge-settings-row-description">${C(`themeDescription`)}</div>
373
+ </div>
374
+ <div class="quickforge-settings-row-control">
375
+ <div class="quickforge-settings-segmented" role="group" aria-label=${C(`theme`)}>
376
+ ${Pe.map(e=>this.renderThemeOption(e))}
377
+ </div>
378
+ </div>
379
+ </div>
380
+
381
+ ${this.renderFontSizeSlider(C(`interfaceFontSize`),null,this.interfaceFontSizePx,e=>this.updateInterfaceFontSize(e))}
382
+
383
+ ${this.renderFontSizeSlider(C(`messageFontSize`),C(`messageFontSizeNote`),this.messageFontSizePx,e=>this.updateMessageFontSize(e))}
384
+ </section>
385
+
386
+ ${this.error?f`<span class="quickforge-settings-error">${this.error}</span>`:null}
387
+ </div>
388
+ `}},k=`quickforge-appearance-settings-tab`;customElements.get(k)||customElements.define(k,Fe);function Ie(){return document.createElement(k)}var Le=`auto-compact-settings`,A={enabled:!1,thresholdPercent:80,keepRecentTurns:3,minSourceChars:1600,requireConfirmation:!0};function j(e,t,n,r){let i=Number(e);return Number.isFinite(i)?Math.min(r,Math.max(n,Math.round(i))):t}function M(e){if(!e||typeof e!=`object`)return{...A};let t=e;return{enabled:t.enabled===!0,thresholdPercent:j(t.thresholdPercent,A.thresholdPercent,50,95),keepRecentTurns:j(t.keepRecentTurns,A.keepRecentTurns,1,20),minSourceChars:j(t.minSourceChars,A.minSourceChars,0,2e5),requireConfirmation:t.requireConfirmation!==!1}}async function Re(e){return M(await e.settings.get(Le))}async function ze(e,t){await e.settings.set(Le,M(t))}var Be=[{value:`off`,label:()=>C(`thinkingOff`)},{value:`low`,label:()=>C(`thinkingLow`)},{value:`medium`,label:()=>C(`thinkingMedium`)},{value:`high`,label:()=>C(`thinkingHigh`)},{value:`xhigh`,label:()=>C(`thinkingXHigh`)}];function Ve(e){return(e??``).trim().replace(/\/$/,``)}function N(e){return JSON.stringify([e.provider,e.id,e.api,Ve(e.baseUrl)])}function He(e){return`${e.provider} / ${e.id}`}function Ue(){return`custom_${globalThis.crypto?.randomUUID?.().slice(0,8)||Date.now().toString(36)}`}function We(e){let t=e.trim(),n=t.split(/[\\/]/).pop()?.replace(/^"|"$/g,``)||t;return/^bash(\.exe)?$/i.test(n)?`Bash`:/^zsh$/i.test(n)?`Zsh`:/^fish$/i.test(n)?`Fish`:/^cmd(\.exe)?$/i.test(n)?`Command Prompt`:/^powershell(\.exe)?$/i.test(n)?`Windows PowerShell`:/^pwsh(\.exe)?$/i.test(n)?`PowerShell 7+`:n||`Custom Shell`}var Ge=f`
389
+ <svg class="size-3.5" viewBox="0 0 16 16" fill="none" aria-hidden="true">
390
+ <path d="M3.5 8.2 6.6 11.3 12.7 4.7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
391
+ </svg>
392
+ `,Ke=f`
393
+ <svg class="size-3.5" viewBox="0 0 16 16" fill="none" aria-hidden="true">
394
+ <circle cx="8" cy="8" r="4.8" stroke="currentColor" stroke-width="1.5" />
395
+ </svg>
396
+ `,qe=f`
397
+ <svg class="size-3.5" viewBox="0 0 16 16" fill="none" aria-hidden="true">
398
+ <path d="M4.5 4.5 11.5 11.5M11.5 4.5 4.5 11.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" />
399
+ </svg>
400
+ `,Je=class extends p{models=[];selectedModel;thinkingLevel=`off`;showToolDetails=!1;expandToolsByDefault=!1;autoCompactEnabled=!1;autoCompactRequireConfirmation=!0;autoCompactThresholdPercent=80;autoCompactThresholdPercentInput=`80`;autoCompactKeepRecentTurns=3;selectedLanguage=Se();loading=!0;saved=!1;savedMessage=``;error=``;terminalShellConfig={terminalShell:`auto`,defaultProfileId:`auto`,profiles:[]};customShellCommand=``;getTabName(){return C(`defaultOptions`)}async connectedCallback(){super.connectedCallback(),await this.loadSettings()}updated(){this.syncSelectValues()}syncSelectValues(){let e=this.querySelector(`[data-default-model-select]`);e&&this.selectedModel&&(e.value=N(this.selectedModel));let t=this.querySelector(`[data-default-thinking-select]`);t&&(t.value=this.thinkingLevel);let n=this.querySelector(`[data-language-select]`);n&&(n.value=this.selectedLanguage)}async loadSettings(){this.loading=!0,this.error=``,this.requestUpdate();try{let e=m(),[t,n,r,i]=await Promise.all([ge(e),de(e),xe(e),Re(e)]);this.models=t,this.selectedModel=n.model?t.find(e=>N(e)===N(n.model))??n.model:t[0],this.thinkingLevel=n.thinkingLevel??fe(this.selectedModel),this.showToolDetails=r.showToolDetails,this.expandToolsByDefault=r.expandToolsByDefault,this.autoCompactEnabled=i.enabled,this.autoCompactRequireConfirmation=i.requireConfirmation,this.autoCompactThresholdPercent=i.thresholdPercent,this.autoCompactThresholdPercentInput=String(i.thresholdPercent),this.autoCompactKeepRecentTurns=i.keepRecentTurns,await this.loadTerminalShell()}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.loading=!1,this.requestUpdate()}}updateModel(e){let t=this.models.find(t=>N(t)===e);this.selectedModel=t,this.thinkingLevel=fe(t),this.saved=!1,this.requestUpdate(),this.saveDefaultModelOptions()}updateThinkingLevel(e){this.thinkingLevel=Be.some(t=>t.value===e)?e:`off`,this.saved=!1,this.requestUpdate(),this.saveDefaultModelOptions()}updateLanguage(e){this.selectedLanguage=e===`zh`?`zh`:`en`,this.requestUpdate(),this.applyLanguage()}async applyLanguage(){await Te(m(),this.selectedLanguage)}updateShowToolDetails(e){this.showToolDetails=e,this.saved=!1,this.requestUpdate(),this.saveToolDisplayOptions()}updateExpandToolsByDefault(e){this.expandToolsByDefault=e,this.saved=!1,this.requestUpdate(),this.saveToolDisplayOptions()}updateAutoCompactEnabled(e){this.autoCompactEnabled=e,this.saved=!1,this.requestUpdate(),this.saveAutoCompactOptions()}updateAutoCompactRequireConfirmation(e){this.autoCompactRequireConfirmation=e,this.saved=!1,this.requestUpdate(),this.saveAutoCompactOptions()}updateAutoCompactThresholdPercent(e){this.autoCompactThresholdPercentInput=e;let t=Number(e);e!==``&&Number.isFinite(t)&&(this.autoCompactThresholdPercent=t),this.saved=!1,this.requestUpdate()}updateAutoCompactKeepRecentTurns(e){this.autoCompactKeepRecentTurns=Number(e)||3,this.saved=!1,this.requestUpdate()}commitAutoCompactThresholdPercent(){let e=Number(this.autoCompactThresholdPercentInput);if(!Number.isFinite(e)){this.autoCompactThresholdPercentInput=String(this.autoCompactThresholdPercent),this.requestUpdate();return}let t=Math.max(50,Math.min(95,Math.round(e)));this.autoCompactThresholdPercent=t,this.autoCompactThresholdPercentInput=String(t),this.requestUpdate(),this.saveAutoCompactOptions()}commitAutoCompactKeepRecentTurns(){let e=Math.max(1,Math.min(20,Math.round(this.autoCompactKeepRecentTurns||3)));this.autoCompactKeepRecentTurns=e,this.requestUpdate(),this.saveAutoCompactOptions()}markSaved(e=C(`defaultOptionsSaved`)){this.saved=!0,this.savedMessage=e,this.error=``,this.requestUpdate()}customShellProfiles(){return this.terminalShellConfig.profiles.filter(e=>!e.builtin)}async loadTerminalShell(){try{let e=await fetch(`/api/system/terminal-shell`,{cache:`no-store`}),t=await e.json().catch(()=>null);if(!e.ok)throw Error(t?.error||C(`requestFailed`));this.terminalShellConfig={terminalShell:t?.terminalShell||`auto`,defaultProfileId:t?.defaultProfileId||`auto`,profiles:Array.isArray(t?.profiles)?t.profiles:[]}}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}}async saveTerminalShellConfig(e,t=this.customShellProfiles(),n=C(`terminalShellSaved`)){try{let r=await fetch(`/api/system/terminal-shell`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({defaultProfileId:e,profiles:t})}),i=await r.json().catch(()=>null);if(!r.ok)throw Error(i?.error||C(`requestFailed`));this.terminalShellConfig=i,this.markSaved(n)}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`),this.requestUpdate()}}async addCustomTerminalShell(){let e=this.customShellCommand.trim();if(!e){this.error=C(`terminalShellProfileRequired`),this.requestUpdate();return}let t=[...this.customShellProfiles(),{id:Ue(),name:We(e),command:e,builtin:!1}];this.customShellCommand=``,await this.saveTerminalShellConfig(this.terminalShellConfig.defaultProfileId,t,C(`terminalShellProfilesSaved`))}async deleteCustomTerminalShell(e){let t=this.terminalShellConfig.profiles.find(t=>t.id===e);if(!t||t.builtin||!await v({description:C(`terminalShellDeleteConfirm`,{name:t.name}),confirmLabel:C(`confirmDelete`),cancelLabel:C(`cancel`),variant:`destructive`}))return;let n=this.customShellProfiles().filter(t=>t.id!==e),r=this.terminalShellConfig.defaultProfileId===e?`auto`:this.terminalShellConfig.defaultProfileId;await this.saveTerminalShellConfig(r,n,C(`terminalShellProfilesSaved`))}async saveDefaultModelOptions(){try{let e=this.selectedModel?.reasoning?this.thinkingLevel:`off`;await pe(m(),{model:this.selectedModel,thinkingLevel:e}),this.markSaved()}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`),this.requestUpdate()}}async saveToolDisplayOptions(){try{await ce(m(),{showToolDetails:this.showToolDetails,expandToolsByDefault:this.expandToolsByDefault}),this.markSaved()}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`),this.requestUpdate()}}async saveAutoCompactOptions(){try{await ze(m(),{enabled:this.autoCompactEnabled,thresholdPercent:this.autoCompactThresholdPercent,keepRecentTurns:this.autoCompactKeepRecentTurns,minSourceChars:1600,requireConfirmation:this.autoCompactRequireConfirmation}),this.markSaved()}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`),this.requestUpdate()}}modelOptions(){if(!this.selectedModel)return this.models;let e=N(this.selectedModel);return this.models.some(t=>N(t)===e)?this.models:[this.selectedModel,...this.models]}shellProfileRow(e,t=!1){let n=e.id===this.terminalShellConfig.defaultProfileId||this.terminalShellConfig.defaultProfileId===`auto`&&t;return f`
401
+ <div class="quickforge-settings-subrow">
402
+ <div class="quickforge-settings-row-main">
403
+ <div class="quickforge-settings-row-title">${e.name}</div>
404
+ <div class="quickforge-settings-row-description quickforge-settings-mono" title=${e.command}>${e.command}</div>
405
+ </div>
406
+ <div class="quickforge-settings-row-control quickforge-settings-icon-actions">
407
+ ${n?f`
408
+ <span class="quickforge-settings-icon-action quickforge-settings-icon-action-success" title=${C(`terminalShellDefaultBadge`)} aria-label=${C(`terminalShellDefaultBadge`)}>
409
+ ${Ge}
410
+ </span>
411
+ `:f`
412
+ <button
413
+ class="quickforge-settings-icon-action"
414
+ type="button"
415
+ title=${C(`terminalShellSetDefault`)}
416
+ aria-label=${C(`terminalShellSetDefault`)}
417
+ @click=${()=>this.saveTerminalShellConfig(e.id)}
418
+ >
419
+ ${Ke}
420
+ </button>
421
+ `}
422
+ ${e.builtin?null:f`
423
+ <button
424
+ class="quickforge-settings-icon-action quickforge-settings-icon-action-danger"
425
+ type="button"
426
+ title=${C(`delete`)}
427
+ aria-label=${C(`delete`)}
428
+ @click=${()=>this.deleteCustomTerminalShell(e.id)}
429
+ >
430
+ ${qe}
431
+ </button>
432
+ `}
433
+ </div>
434
+ </div>
435
+ `}terminalShellSettings(){let e=this.terminalShellConfig.profiles;return f`
436
+ <section class="quickforge-settings-section" aria-label=${C(`terminalShell`)}>
437
+ <div class="quickforge-settings-row quickforge-settings-row-top">
438
+ <div class="quickforge-settings-row-main">
439
+ <div class="quickforge-settings-row-title">
440
+ ${C(`terminalShell`)}
441
+ <quickforge-info-tip .label=${C(`terminalShellDescription`)}></quickforge-info-tip>
442
+ </div>
443
+ <div class="quickforge-settings-row-description">${C(`terminalShellAutoDetectedHint`)}</div>
444
+ </div>
445
+ </div>
446
+
447
+ <div class="quickforge-settings-nested-list">
448
+ ${e.length>0?e.map((e,t)=>this.shellProfileRow(e,t===0)):f`<div class="quickforge-settings-empty-row">${C(`terminalShellNoDetected`)}</div>`}
449
+ </div>
450
+
451
+ <div class="quickforge-settings-row">
452
+ <div class="quickforge-settings-row-main">
453
+ <div class="quickforge-settings-row-title">${C(`terminalShellCommand`)}</div>
454
+ <div class="quickforge-settings-row-description">${C(`terminalShellCustomDescription`)}</div>
455
+ </div>
456
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide quickforge-terminal-shell-command-control">
457
+ <input
458
+ class="quickforge-settings-input quickforge-settings-mono"
459
+ type="text"
460
+ .value=${this.customShellCommand}
461
+ placeholder=${C(`terminalShellCommandPlaceholder`)}
462
+ @input=${e=>{this.customShellCommand=e.target.value}}
463
+ />
464
+ <button
465
+ class="quickforge-settings-button quickforge-settings-button-primary"
466
+ type="button"
467
+ title=${C(`terminalShellAdd`)}
468
+ aria-label=${C(`terminalShellAdd`)}
469
+ @click=${()=>this.addCustomTerminalShell()}
470
+ >
471
+ ${C(`terminalShellAdd`)}
472
+ </button>
473
+ </div>
474
+ </div>
475
+ </section>
476
+ `}renderSwitch(e,t,n=!1){return f`
477
+ <label class="quickforge-settings-switch" aria-disabled=${n?`true`:`false`}>
478
+ <input
479
+ type="checkbox"
480
+ .checked=${e}
481
+ ?disabled=${n}
482
+ @change=${e=>t(e.target.checked)}
483
+ />
484
+ <span aria-hidden="true"></span>
485
+ </label>
486
+ `}render(){return this.loading?f`<div class="text-sm text-muted-foreground">${C(`loading`)}</div>`:f`
487
+ <div class="quickforge-settings-stack">
488
+ <section class="quickforge-settings-section" aria-label=${C(`defaultOptions`)}>
489
+ <div class="quickforge-settings-row">
490
+ <div class="quickforge-settings-row-main">
491
+ <div class="quickforge-settings-row-title">
492
+ ${C(`language`)}
493
+ <quickforge-info-tip .label=${C(`languageDescription`)}></quickforge-info-tip>
494
+ </div>
495
+ <div class="quickforge-settings-row-description">${C(`displayLanguage`)}</div>
496
+ </div>
497
+ <div class="quickforge-settings-row-control">
498
+ <select
499
+ data-language-select
500
+ class="quickforge-settings-select"
501
+ .value=${this.selectedLanguage}
502
+ @change=${e=>this.updateLanguage(e.target.value)}
503
+ >
504
+ <option value="zh">${C(`simplifiedChinese`)}</option>
505
+ <option value="en">${C(`english`)}</option>
506
+ </select>
507
+ </div>
508
+ </div>
509
+
510
+ <div class="quickforge-settings-row">
511
+ <div class="quickforge-settings-row-main">
512
+ <div class="quickforge-settings-row-title">${C(`defaultModel`)}</div>
513
+ <div class="quickforge-settings-row-description">${C(`defaultModelDescription`)}</div>
514
+ </div>
515
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide">
516
+ <select
517
+ data-default-model-select
518
+ class="quickforge-settings-select"
519
+ .value=${this.selectedModel?N(this.selectedModel):``}
520
+ @change=${e=>this.updateModel(e.target.value)}
521
+ >
522
+ ${this.modelOptions().length===0?f`<option value="">${C(`noModelAvailable`)}</option>`:this.modelOptions().map(e=>f`
523
+ <option .value=${N(e)}>${He(e)}</option>
524
+ `)}
525
+ </select>
526
+ </div>
527
+ </div>
528
+
529
+ <div class="quickforge-settings-row">
530
+ <div class="quickforge-settings-row-main">
531
+ <div class="quickforge-settings-row-title">${C(`defaultThinkingLevel`)}</div>
532
+ <div class="quickforge-settings-row-description">
533
+ ${this.selectedModel?.reasoning?C(`defaultThinkingLevelDescription`):C(`thinkingRequiresReasoningModel`)}
534
+ </div>
535
+ </div>
536
+ <div class="quickforge-settings-row-control">
537
+ <select
538
+ data-default-thinking-select
539
+ class="quickforge-settings-select"
540
+ .value=${this.thinkingLevel}
541
+ ?disabled=${!this.selectedModel?.reasoning}
542
+ @change=${e=>this.updateThinkingLevel(e.target.value)}
543
+ >
544
+ ${Be.map(e=>f`
545
+ <option .value=${e.value}>${e.label()}</option>
546
+ `)}
547
+ </select>
548
+ </div>
549
+ </div>
550
+
551
+ <div class="quickforge-settings-row">
552
+ <div class="quickforge-settings-row-main">
553
+ <div class="quickforge-settings-row-title">
554
+ ${C(`showToolDetails`)}
555
+ <quickforge-info-tip .label=${C(`showToolDetailsDescription`)}></quickforge-info-tip>
556
+ </div>
557
+ <div class="quickforge-settings-row-description">${C(`showToolDetailsDescriptionShort`)}</div>
558
+ </div>
559
+ <div class="quickforge-settings-row-control">
560
+ ${this.renderSwitch(this.showToolDetails,e=>this.updateShowToolDetails(e))}
561
+ </div>
562
+ </div>
563
+
564
+ <div class="quickforge-settings-row">
565
+ <div class="quickforge-settings-row-main">
566
+ <div class="quickforge-settings-row-title">${C(`expandToolsByDefault`)}</div>
567
+ <div class="quickforge-settings-row-description">${C(`expandToolsByDefaultDescription`)}</div>
568
+ </div>
569
+ <div class="quickforge-settings-row-control">
570
+ ${this.renderSwitch(this.expandToolsByDefault,e=>this.updateExpandToolsByDefault(e))}
571
+ </div>
572
+ </div>
573
+
574
+ <div class="quickforge-settings-row">
575
+ <div class="quickforge-settings-row-main">
576
+ <div class="quickforge-settings-row-title">
577
+ ${C(`autoCompactEnabled`)}
578
+ <quickforge-info-tip .label=${C(`autoCompactDescription`)}></quickforge-info-tip>
579
+ </div>
580
+ <div class="quickforge-settings-row-description">${C(`autoCompactTriggerNote`)}</div>
581
+ </div>
582
+ <div class="quickforge-settings-row-control">
583
+ ${this.renderSwitch(this.autoCompactEnabled,e=>this.updateAutoCompactEnabled(e))}
584
+ </div>
585
+ </div>
586
+
587
+ <div class="quickforge-settings-row">
588
+ <div class="quickforge-settings-row-main">
589
+ <div class="quickforge-settings-row-title">${C(`autoCompactRequireConfirmation`)}</div>
590
+ <div class="quickforge-settings-row-description">${C(`autoCompactRequireConfirmationDescription`)}</div>
591
+ </div>
592
+ <div class="quickforge-settings-row-control">
593
+ ${this.renderSwitch(this.autoCompactRequireConfirmation,e=>this.updateAutoCompactRequireConfirmation(e),!this.autoCompactEnabled)}
594
+ </div>
595
+ </div>
596
+
597
+ <div class="quickforge-settings-row">
598
+ <div class="quickforge-settings-row-main">
599
+ <div class="quickforge-settings-row-title">${C(`autoCompactThresholdPercent`)}</div>
600
+ <div class="quickforge-settings-row-description">${C(`autoCompactThresholdDescription`)}</div>
601
+ </div>
602
+ <div class="quickforge-settings-row-control">
603
+ <input
604
+ class="quickforge-settings-input quickforge-settings-number-input"
605
+ type="number"
606
+ min="50"
607
+ max="95"
608
+ step="1"
609
+ .value=${this.autoCompactThresholdPercentInput}
610
+ ?disabled=${!this.autoCompactEnabled}
611
+ @input=${e=>this.updateAutoCompactThresholdPercent(e.target.value)}
612
+ @change=${()=>this.commitAutoCompactThresholdPercent()}
613
+ @blur=${()=>this.commitAutoCompactThresholdPercent()}
614
+ />
615
+ </div>
616
+ </div>
617
+
618
+ <div class="quickforge-settings-row">
619
+ <div class="quickforge-settings-row-main">
620
+ <div class="quickforge-settings-row-title">
621
+ ${C(`autoCompactKeepRecentTurns`)}
622
+ <quickforge-info-tip .label=${C(`autoCompactHistoryPreserved`)}></quickforge-info-tip>
623
+ </div>
624
+ <div class="quickforge-settings-row-description">${C(`autoCompactKeepRecentTurnsDescription`)}</div>
625
+ </div>
626
+ <div class="quickforge-settings-row-control">
627
+ <input
628
+ class="quickforge-settings-input quickforge-settings-number-input"
629
+ type="number"
630
+ min="1"
631
+ max="20"
632
+ step="1"
633
+ .value=${String(this.autoCompactKeepRecentTurns)}
634
+ ?disabled=${!this.autoCompactEnabled}
635
+ @input=${e=>this.updateAutoCompactKeepRecentTurns(e.target.value)}
636
+ @change=${()=>this.commitAutoCompactKeepRecentTurns()}
637
+ @blur=${()=>this.commitAutoCompactKeepRecentTurns()}
638
+ />
639
+ </div>
640
+ </div>
641
+ </section>
642
+
643
+ ${this.terminalShellSettings()}
644
+
645
+ ${this.saved?f`<div class="quickforge-settings-message">${this.savedMessage||C(`defaultOptionsSaved`)}</div>`:null}
646
+ ${this.error?f`<div class="quickforge-settings-alert">${this.error}</div>`:null}
647
+ </div>
648
+ `}},P=`quickforge-default-options-settings-tab`;customElements.get(P)||customElements.define(P,Je);function Ye(){return document.createElement(P)}var Xe=`quickforge-backup`,Ze=[{id:`settings`,countKey:`settings`,label:()=>C(`restoreSettings`),description:()=>C(`restoreSettingsDescription`)},{id:`mcp`,countKey:`mcp`,label:()=>C(`restoreMcp`),description:()=>C(`restoreMcpDescription`)},{id:`providerKeys`,countKey:`providerKeys`,label:()=>C(`restoreProviderKeys`),description:()=>C(`restoreProviderKeysDescription`)},{id:`customProviders`,countKey:`customProviders`,label:()=>C(`restoreCustomProviders`),description:()=>C(`restoreCustomProvidersDescription`)},{id:`projects`,countKey:`projects`,label:()=>C(`restoreProjects`),description:()=>C(`restoreProjectsDescription`)},{id:`scheduledTasks`,countKey:`scheduledTasks`,label:()=>C(`restoreScheduledTasks`),description:()=>C(`restoreScheduledTasksDescription`)},{id:`conversations`,countKey:`sessions`,label:()=>C(`restoreConversations`),description:()=>C(`restoreConversationsDescription`)}];function Qe(e,t){let n=new Blob([`${JSON.stringify(t,null,2)}\n`],{type:`application/json`}),r=URL.createObjectURL(n),i=document.createElement(`a`);i.href=r,i.download=e,document.body.appendChild(i),i.click(),i.remove(),URL.revokeObjectURL(r)}function $e(){return new Date().toISOString().replace(/[:.]/g,`-`)}function et(e){return e?Object.entries(e).map(([e,t])=>`${e}: ${t}`).join(`, `):``}function tt(e){return Ze.filter(t=>(e.sections?.[t.countKey]??0)>0)}function nt(e){return tt(e).filter(e=>e.id!==`conversations`).map(e=>e.id)}function rt(e){return e instanceof RangeError||e instanceof Error&&e.message.includes(`Invalid string length`)}function F(e,t){return rt(e)?C(`backupStringTooLarge`):e instanceof Error?e.message:t}function I(e){return!!(e&&typeof e==`object`&&!Array.isArray(e))}function it(e){if(!I(e))return e;let t={...e};return delete t.sessions,delete t.sessionsMetadata,delete t[`sessions-metadata`],t}function at(e){return I(e)?I(e.data)?{...e,scope:e.scope===`sessions`?`config`:e.scope,data:it(e.data)}:it(e):e}function ot(e,t){return t.has(`conversations`)?e:at(e)}var st=class extends p{exportScope=`config`;includeSecrets=!1;busy=!1;message=``;error=``;safetyBackupPath=``;pendingImport=null;getTabName(){return C(`backupRestore`)}setScope(e){this.exportScope=e===`config`||e===`sessions`?e:`all`,this.exportScope===`sessions`&&(this.includeSecrets=!1),this.clearStatus(),this.requestUpdate()}clearStatus(){this.message=``,this.error=``,this.safetyBackupPath=``}setIncludeSecrets(e){this.includeSecrets=e&&this.exportScope!==`sessions`,this.message=``,this.error=``,this.requestUpdate()}async exportBackup(){if(!(this.includeSecrets&&!await v({description:C(`backupExportSecretsConfirm`),confirmLabel:C(`exportBackup`),cancelLabel:C(`cancel`)}))){this.busy=!0,this.clearStatus(),this.requestUpdate();try{let e=new URLSearchParams({scope:this.exportScope,includeSecrets:this.includeSecrets?`1`:`0`}),t=await fetch(`/api/backup/export?${e.toString()}`,{cache:`no-store`}),n=await t.json().catch(()=>null);if(!t.ok)throw Error(n?.error||C(`backupExportFailed`));let r=this.includeSecrets?`with-secrets`:`no-secrets`;Qe(`${Xe}-${this.exportScope}-${r}-${$e()}.json`,n),this.message=C(`backupExported`)}catch(e){this.error=F(e,C(`backupExportFailed`))}finally{this.busy=!1,this.requestUpdate()}}}async inspectBackupFile(e){let t=await fetch(`/api/backup/inspect-file`,{method:`POST`,headers:{"content-type":`application/json`},body:e}),n=await t.json().catch(()=>null);if(!t.ok)throw Error(n?.error||C(`backupInspectFailed`));if(!n)throw Error(C(`backupInspectFailed`));return n}async importBackupFromFile(e){this.busy=!0,this.clearStatus(),this.pendingImport=null,this.requestUpdate();try{let t=await this.inspectBackupFile(e),n=new Set(nt(t));this.pendingImport={backup:null,importToken:t.importToken,inspect:t,selectedSections:n,mode:`replace`},this.message=t.sections?.sessions?C(`backupInspectedWithConversations`):C(`backupInspected`)}catch(e){this.error=F(e,C(`backupImportFailed`))}finally{this.busy=!1,this.requestUpdate()}}togglePendingSection(e,t){this.pendingImport&&(t?this.pendingImport.selectedSections.add(e):this.pendingImport.selectedSections.delete(e),this.message=``,this.error=``,this.requestUpdate())}setRestoreMode(e){this.pendingImport&&(this.pendingImport.mode=e,this.message=``,this.error=``,this.requestUpdate())}cancelPendingImport(){this.pendingImport=null,this.message=``,this.error=``,this.requestUpdate()}async confirmPendingImport(){if(this.pendingImport){if(this.pendingImport.selectedSections.size===0){this.error=C(`selectAtLeastOneRestoreSection`),this.requestUpdate();return}this.busy=!0,this.clearStatus(),this.requestUpdate();try{let e=this.pendingImport.importToken?{importToken:this.pendingImport.importToken,sections:[...this.pendingImport.selectedSections],mode:this.pendingImport.mode}:{backup:ot(this.pendingImport.backup,this.pendingImport.selectedSections),sections:[...this.pendingImport.selectedSections],mode:this.pendingImport.mode},t=await fetch(`/api/backup/import`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(e)}),n=await t.json().catch(()=>null);if(!t.ok)throw Error(n?.error||C(`backupImportFailed`));let r=et(n?.summary);this.safetyBackupPath=n?.safetyBackupPath||``,this.pendingImport=null,this.message=r?`${C(`backupImported`)} ${r}`:C(`backupImported`),window.setTimeout(()=>window.location.reload(),1500)}catch(e){this.error=F(e,C(`backupImportFailed`))}finally{this.busy=!1,this.requestUpdate()}}}handleFileChange(e){let t=e.target,n=t.files?.[0];t.value=``,n&&this.importBackupFromFile(n)}renderSwitch(e,t,n=!1){return f`
649
+ <label class="quickforge-settings-switch" aria-disabled=${n?`true`:`false`}>
650
+ <input
651
+ type="checkbox"
652
+ .checked=${e}
653
+ ?disabled=${n}
654
+ @change=${e=>t(e.target.checked)}
655
+ />
656
+ <span aria-hidden="true"></span>
657
+ </label>
658
+ `}renderPendingImport(){if(!this.pendingImport)return null;let{inspect:e,selectedSections:t}=this.pendingImport,n=tt(e);return f`
659
+ <section class="quickforge-settings-section" aria-label=${C(`backupInspectTitle`)}>
660
+ <div class="quickforge-settings-row quickforge-settings-row-top">
661
+ <div class="quickforge-settings-row-main">
662
+ <div class="quickforge-settings-row-title">${C(`backupInspectTitle`)}</div>
663
+ <div class="quickforge-settings-row-description">${C(`backupInspectDescription`)}</div>
664
+ </div>
665
+ </div>
666
+
667
+ <div class="quickforge-settings-nested-list">
668
+ <div class="quickforge-settings-subrow">
669
+ <div class="quickforge-settings-row-main">
670
+ <div class="quickforge-settings-row-title">${C(`backupInspectExportedAt`)}</div>
671
+ </div>
672
+ <div class="quickforge-settings-row-control quickforge-settings-readonly-value">${e.exportedAt||`-`}</div>
673
+ </div>
674
+ <div class="quickforge-settings-subrow">
675
+ <div class="quickforge-settings-row-main">
676
+ <div class="quickforge-settings-row-title">${C(`backupInspectVersion`)}</div>
677
+ </div>
678
+ <div class="quickforge-settings-row-control quickforge-settings-readonly-value">${e.version??`-`}</div>
679
+ </div>
680
+ <div class="quickforge-settings-subrow">
681
+ <div class="quickforge-settings-row-main">
682
+ <div class="quickforge-settings-row-title">${C(`backupInspectScope`)}</div>
683
+ </div>
684
+ <div class="quickforge-settings-row-control quickforge-settings-readonly-value">${e.scope||`-`}</div>
685
+ </div>
686
+ <div class="quickforge-settings-subrow">
687
+ <div class="quickforge-settings-row-main">
688
+ <div class="quickforge-settings-row-title">${C(`backupInspectSecrets`)}</div>
689
+ </div>
690
+ <div class="quickforge-settings-row-control quickforge-settings-readonly-value">${e.includeSecrets?C(`yes`):C(`no`)}</div>
691
+ </div>
692
+ </div>
693
+
694
+ ${e.warnings?.length?f`
695
+ <div class="quickforge-settings-warning quickforge-settings-warning-attached">
696
+ ${e.warnings.map(e=>f`<div>⚠ ${e}</div>`)}
697
+ </div>
698
+ `:null}
699
+
700
+ <div class="quickforge-settings-row">
701
+ <div class="quickforge-settings-row-main">
702
+ <div class="quickforge-settings-row-title">${C(`restoreMode`)}</div>
703
+ <div class="quickforge-settings-row-description">${C(`restoreModeDescription`)}</div>
704
+ </div>
705
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide">
706
+ <div class="quickforge-settings-segmented" role="group" aria-label=${C(`restoreMode`)}>
707
+ <button
708
+ type="button"
709
+ class="quickforge-settings-segmented-option ${this.pendingImport.mode===`replace`?`quickforge-settings-segmented-option-active`:``}"
710
+ ?disabled=${this.busy}
711
+ @click=${()=>this.setRestoreMode(`replace`)}
712
+ >
713
+ ${C(`restoreModeReplace`)}
714
+ </button>
715
+ <button
716
+ type="button"
717
+ class="quickforge-settings-segmented-option ${this.pendingImport.mode===`merge`?`quickforge-settings-segmented-option-active`:``}"
718
+ ?disabled=${this.busy}
719
+ @click=${()=>this.setRestoreMode(`merge`)}
720
+ >
721
+ ${C(`restoreModeMerge`)}
722
+ </button>
723
+ </div>
724
+ </div>
725
+ </div>
726
+
727
+ <div class="quickforge-settings-row quickforge-settings-row-top">
728
+ <div class="quickforge-settings-row-main">
729
+ <div class="quickforge-settings-row-title">${C(`selectRestoreSections`)}</div>
730
+ <div class="quickforge-settings-row-description">
731
+ ${this.pendingImport.mode===`replace`?C(`restoreModeReplaceDescription`):C(`restoreModeMergeDescription`)}
732
+ </div>
733
+ </div>
734
+ </div>
735
+ <div class="quickforge-settings-nested-list">
736
+ ${n.map(n=>f`
737
+ <div class="quickforge-settings-subrow">
738
+ <div class="quickforge-settings-row-main">
739
+ <div class="quickforge-settings-row-title">${n.label()} (${e.sections?.[n.countKey]??0})</div>
740
+ <div class="quickforge-settings-row-description">${n.description()}</div>
741
+ </div>
742
+ <div class="quickforge-settings-row-control">
743
+ ${this.renderSwitch(t.has(n.id),e=>this.togglePendingSection(n.id,e),this.busy)}
744
+ </div>
745
+ </div>
746
+ `)}
747
+ </div>
748
+
749
+ <div class="quickforge-settings-row">
750
+ <div class="quickforge-settings-row-main">
751
+ <div class="quickforge-settings-row-title">${C(`backupImportActions`)}</div>
752
+ <div class="quickforge-settings-row-description">${C(`backupImportActionsDescription`)}</div>
753
+ </div>
754
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide">
755
+ <button
756
+ class="quickforge-settings-button quickforge-settings-button-primary"
757
+ type="button"
758
+ ?disabled=${this.busy||t.size===0}
759
+ @click=${()=>this.confirmPendingImport()}
760
+ >
761
+ ${this.busy?C(`loading`):C(`confirmImportSelected`)}
762
+ </button>
763
+ <button
764
+ class="quickforge-settings-button quickforge-settings-button-secondary"
765
+ type="button"
766
+ ?disabled=${this.busy}
767
+ @click=${()=>this.cancelPendingImport()}
768
+ >
769
+ ${C(`cancel`)}
770
+ </button>
771
+ </div>
772
+ </div>
773
+ </section>
774
+ `}render(){return f`
775
+ <div class="quickforge-settings-stack">
776
+ <section class="quickforge-settings-section" aria-label=${C(`exportData`)}>
777
+ <div class="quickforge-settings-row">
778
+ <div class="quickforge-settings-row-main">
779
+ <div class="quickforge-settings-row-title">
780
+ ${C(`exportScope`)}
781
+ <quickforge-info-tip .label=${C(`exportDataDescription`)}></quickforge-info-tip>
782
+ </div>
783
+ <div class="quickforge-settings-row-description">${C(`exportScopeDescription`)}</div>
784
+ </div>
785
+ <div class="quickforge-settings-row-control">
786
+ <select
787
+ class="quickforge-settings-select"
788
+ .value=${this.exportScope}
789
+ ?disabled=${this.busy}
790
+ @change=${e=>this.setScope(e.target.value)}
791
+ >
792
+ <option value="all">${C(`exportScopeAll`)}</option>
793
+ <option value="config">${C(`exportScopeConfig`)}</option>
794
+ <option value="sessions">${C(`exportScopeSessions`)}</option>
795
+ </select>
796
+ </div>
797
+ </div>
798
+
799
+ ${this.exportScope===`config`?null:f`
800
+ <div class="quickforge-settings-warning quickforge-settings-warning-attached">
801
+ ⚠ ${C(`exportLargeDataWarning`)}
802
+ </div>
803
+ `}
804
+
805
+ <div class="quickforge-settings-row">
806
+ <div class="quickforge-settings-row-main ${this.exportScope===`sessions`?`opacity-60`:``}">
807
+ <div class="quickforge-settings-row-title">${C(`includeApiKeys`)}</div>
808
+ <div class="quickforge-settings-row-description">${C(`includeApiKeysDescription`)}</div>
809
+ </div>
810
+ <div class="quickforge-settings-row-control">
811
+ ${this.renderSwitch(this.includeSecrets,e=>this.setIncludeSecrets(e),this.busy||this.exportScope===`sessions`)}
812
+ </div>
813
+ </div>
814
+
815
+ <div class="quickforge-settings-row">
816
+ <div class="quickforge-settings-row-main">
817
+ <div class="quickforge-settings-row-title">${C(`exportBackup`)}</div>
818
+ <div class="quickforge-settings-row-description">${C(`exportBackupDescription`)}</div>
819
+ </div>
820
+ <div class="quickforge-settings-row-control">
821
+ <button
822
+ class="quickforge-settings-button quickforge-settings-button-primary"
823
+ type="button"
824
+ ?disabled=${this.busy}
825
+ @click=${()=>this.exportBackup()}
826
+ >
827
+ ${this.busy?C(`loading`):C(`exportBackup`)}
828
+ </button>
829
+ </div>
830
+ </div>
831
+ </section>
832
+
833
+ <section class="quickforge-settings-section" aria-label=${C(`importData`)}>
834
+ <div class="quickforge-settings-row">
835
+ <div class="quickforge-settings-row-main">
836
+ <div class="quickforge-settings-row-title">
837
+ ${C(`importBackup`)}
838
+ <quickforge-info-tip .label=${C(`importDataDescription`)}></quickforge-info-tip>
839
+ </div>
840
+ <div class="quickforge-settings-row-description">${C(`importDataDescription`)}</div>
841
+ </div>
842
+ <div class="quickforge-settings-row-control">
843
+ <label class="quickforge-settings-button quickforge-settings-button-secondary ${this.busy?`pointer-events-none opacity-60`:``}">
844
+ <input
845
+ class="hidden"
846
+ type="file"
847
+ accept="application/json,.json"
848
+ ?disabled=${this.busy}
849
+ @change=${e=>this.handleFileChange(e)}
850
+ />
851
+ ${C(`importBackup`)}
852
+ </label>
853
+ </div>
854
+ </div>
855
+ </section>
856
+
857
+ ${this.renderPendingImport()}
858
+ ${this.message?f`<div class="quickforge-settings-message">${this.message}</div>`:null}
859
+ ${this.safetyBackupPath?f`<div class="quickforge-settings-note">${C(`backupSafetyBackupPath`)}: <code>${this.safetyBackupPath}</code></div>`:null}
860
+ ${this.error?f`<div class="quickforge-settings-alert">${this.error}</div>`:null}
861
+ </div>
862
+ `}},L=`quickforge-backup-settings-tab`;customElements.get(L)||customElements.define(L,st);function ct(){return document.createElement(L)}function R(e){if(!e)return`-`;let t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString(we())}function lt(e){let t={...e};return delete t.archivedAt,t}function z(){if(!(typeof BroadcastChannel>`u`))try{let e=new BroadcastChannel(`quickforge-sync`);e.postMessage({type:`sessions-changed`,sourceTabId:`archived-conversations-settings-tab`,timestamp:Date.now()}),e.close()}catch{}}var ut=class extends p{sessions=[];projects=[];loading=!0;busySessionId=``;query=``;projectFilter=`all`;message=``;error=``;getTabName(){return C(`archivedConversations`)}async connectedCallback(){super.connectedCallback(),await this.loadData()}get projectNameById(){return new Map(this.projects.map(e=>[e.id,e.name]))}async loadData(){this.loading=!0,this.error=``,this.requestUpdate();try{let[e,t]=await Promise.all([fetch(`/api/storage/sessions-metadata/index/lastModified?direction=desc&limit=1000&offset=0&archived=only`,{cache:`no-store`}),fetch(`/api/project`,{cache:`no-store`}).catch(()=>null)]),n=await e.json().catch(()=>null);if(!e.ok)throw Error(n?.error||C(`requestFailed`));if(this.sessions=Array.isArray(n?.values)?n.values:[],t?.ok){let e=await t.json().catch(()=>null);this.projects=Array.isArray(e?.projects)?e.projects:[]}}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.loading=!1,this.requestUpdate()}}filteredSessions(){let e=this.query.trim().toLowerCase();return this.sessions.filter(t=>{if(this.projectFilter!==`all`&&(this.projectFilter===`global`&&t.scope===`project`||this.projectFilter!==`global`&&t.projectId!==this.projectFilter))return!1;if(!e)return!0;let n=t.projectId&&this.projectNameById.get(t.projectId)||``;return`${x(t.title)} ${n}`.toLowerCase().includes(e)})}groupedSessions(){let e=new Map;for(let t of this.filteredSessions()){let n=t.scope===`project`&&t.projectId?t.projectId:`global`;e.set(n,[...e.get(n)??[],t])}return[...e.entries()]}projectLabel(e){return e===`global`?C(`normalChat`):this.projectNameById.get(e)||C(`unknownProject`)}async restoreSession(e){if(!this.busySessionId){this.busySessionId=e,this.message=``,this.error=``,this.requestUpdate();try{let t=m(),n=await t.sessions.get(e),r=await t.sessions.getMetadata(e);if(!n||!r)throw Error(C(`sessionNotFound`));await t.sessions.save(lt(n),lt(r)),this.sessions=this.sessions.filter(t=>t.id!==e),this.message=C(`sessionRestored`),z()}catch(e){this.error=e instanceof Error?e.message:C(`restoreSessionFailed`)}finally{this.busySessionId=``,this.requestUpdate()}}}async deleteSession(e){if(!this.busySessionId&&await v({description:C(`deleteArchivedSessionConfirm`,{title:x(e.title)}),confirmLabel:C(`confirmDelete`),cancelLabel:C(`cancel`),variant:`destructive`})){this.busySessionId=e.id,this.message=``,this.error=``,this.requestUpdate();try{await m().sessions.delete(e.id),this.sessions=this.sessions.filter(t=>t.id!==e.id),this.message=C(`archivedSessionDeleted`),z()}catch(e){this.error=e instanceof Error?e.message:C(`deleteSessionFailed`)}finally{this.busySessionId=``,this.requestUpdate()}}}async deleteAllArchivedSessions(){if(!(this.busySessionId||this.sessions.length===0)&&await v({description:C(`deleteAllArchivedSessionsConfirm`),confirmLabel:C(`confirmDelete`),cancelLabel:C(`cancel`),variant:`destructive`})){this.busySessionId=`__all__`,this.message=``,this.error=``,this.requestUpdate();try{let e=m();await Promise.all(this.sessions.map(t=>e.sessions.delete(t.id))),this.sessions=[],this.message=C(`archivedSessionsDeleted`),z()}catch(e){this.error=e instanceof Error?e.message:C(`deleteSessionFailed`)}finally{this.busySessionId=``,this.requestUpdate()}}}renderSession(e){let t=this.busySessionId===e.id||this.busySessionId===`__all__`;return f`
863
+ <div class="border-t border-border px-4 py-3 first:border-t-0">
864
+ <div class="flex items-start justify-between gap-3">
865
+ <div class="min-w-0">
866
+ <div class="truncate text-sm font-medium text-foreground">${x(e.title)}</div>
867
+ <div class="mt-1 text-xs text-muted-foreground">
868
+ ${C(`lastModified`)}: ${R(e.lastModified)} · ${C(`archivedAt`)}: ${R(e.archivedAt)}
869
+ </div>
870
+ </div>
871
+ <div class="flex shrink-0 items-center gap-2">
872
+ <button
873
+ class="rounded-md border border-input px-2.5 py-1 text-xs hover:bg-muted/60 disabled:opacity-60"
874
+ type="button"
875
+ ?disabled=${t}
876
+ @click=${()=>this.restoreSession(e.id)}
877
+ >
878
+ ${C(`restoreSession`)}
879
+ </button>
880
+ <button
881
+ class="rounded-md px-2.5 py-1 text-xs text-destructive hover:bg-destructive/10 disabled:opacity-60"
882
+ type="button"
883
+ ?disabled=${t}
884
+ @click=${()=>this.deleteSession(e)}
885
+ >
886
+ ${C(`deletePermanently`)}
887
+ </button>
888
+ </div>
889
+ </div>
890
+ </div>
891
+ `}render(){let e=this.groupedSessions();return f`
892
+ <div class="flex flex-col gap-4">
893
+ <div class="flex justify-end">
894
+ <button
895
+ class="rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive hover:bg-destructive/15 disabled:opacity-60"
896
+ type="button"
897
+ ?disabled=${!!this.busySessionId||this.sessions.length===0}
898
+ @click=${()=>this.deleteAllArchivedSessions()}
899
+ >
900
+ ${C(`deleteAll`)}
901
+ </button>
902
+ </div>
903
+
904
+ <section class="rounded-lg border border-border">
905
+ <div class="grid gap-2 border-b border-border p-3 sm:grid-cols-[minmax(0,1fr)_12rem]">
906
+ <input
907
+ class="rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring/30"
908
+ type="search"
909
+ .value=${this.query}
910
+ placeholder=${C(`searchArchivedConversations`)}
911
+ @input=${e=>{this.query=e.target.value,this.requestUpdate()}}
912
+ />
913
+ <select
914
+ class="rounded-md border border-input bg-background px-3 py-2 text-sm"
915
+ .value=${this.projectFilter}
916
+ @change=${e=>{this.projectFilter=e.target.value,this.requestUpdate()}}
917
+ >
918
+ <option value="all">${C(`allProjects`)}</option>
919
+ <option value="global">${C(`normalChat`)}</option>
920
+ ${this.projects.map(e=>f`<option value=${e.id}>${e.name}</option>`)}
921
+ </select>
922
+ </div>
923
+
924
+ ${this.loading?f`
925
+ <div class="px-4 py-8 text-center text-sm text-muted-foreground">${C(`loading`)}</div>
926
+ `:e.length===0?f`
927
+ <div class="px-4 py-8 text-center text-sm text-muted-foreground">${C(`noArchivedConversations`)}</div>
928
+ `:f`
929
+ <div>
930
+ ${e.map(([e,t])=>f`
931
+ <div class="border-b border-border last:border-b-0">
932
+ <div class="flex items-center justify-between gap-3 bg-muted/20 px-4 py-2 text-sm text-muted-foreground">
933
+ <div class="truncate">${this.projectLabel(e)}</div>
934
+ <div class="shrink-0">${C(`conversationCount`,{count:t.length})}</div>
935
+ </div>
936
+ ${t.map(e=>this.renderSession(e))}
937
+ </div>
938
+ `)}
939
+ </div>
940
+ `}
941
+ </section>
942
+
943
+ ${this.message?f`<div class="rounded-md border border-emerald-500/30 bg-emerald-500/10 px-3 py-2 text-sm text-emerald-700 dark:text-emerald-300">${this.message}</div>`:null}
944
+ ${this.error?f`<div class="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">${this.error}</div>`:null}
945
+ </div>
946
+ `}},B=`quickforge-archived-conversations-settings-tab`;customElements.get(B)||customElements.define(B,ut);function dt(){return document.createElement(B)}function ft(){return Array.from({length:16},()=>`ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*`[Math.floor(Math.random()*65)]).join(``)}var pt=class extends p{loading=!0;saving=!1;enabled=!1;hasPassword=!1;password=``;sessionTtlHours=12;activeTokenCount=0;lanUrls=[];error=``;message=``;getTabName(){return C(`lanAccess`)}async connectedCallback(){super.connectedCallback(),await this.loadStatus()}async request(e,t){let n=await fetch(e,{...t,cache:`no-store`,headers:{...t?.body?{"content-type":`application/json`}:void 0,...t?.headers}}),r=await n.json().catch(()=>null);if(!n.ok)throw Error(r?.error||C(`requestFailed`));return r}applyStatus(e){this.enabled=!!e.enabled,this.hasPassword=!!e.hasPassword,this.sessionTtlHours=Number(e.sessionTtlHours||12),this.activeTokenCount=Number(e.activeTokenCount||0),this.lanUrls=Array.isArray(e.lanUrls)?e.lanUrls:[]}async loadStatus(){this.loading=!0,this.error=``,this.requestUpdate();try{this.applyStatus(await this.request(`/api/lan-access/status`))}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.loading=!1,this.requestUpdate()}}updatePassword(e){this.password=e,this.requestUpdate()}updateEnabled(e){this.enabled=e,this.requestUpdate()}updateTtl(e){this.sessionTtlHours=Number(e)||12,this.requestUpdate()}async saveSettings(){if(!this.saving){if(this.enabled&&!this.hasPassword&&!this.password.trim()){this.error=C(`lanAccessPasswordRequired`),this.requestUpdate();return}if(this.password.trim()&&this.password.trim().length<8){this.error=C(`lanAccessPasswordTooShort`),this.requestUpdate();return}if(!(this.enabled&&!await v({description:C(`lanAccessEnableConfirm`),confirmLabel:C(`enabled`),cancelLabel:C(`cancel`)}))){this.saving=!0,this.error=``,this.message=``,this.requestUpdate();try{let e=await this.request(`/api/lan-access/settings`,{method:`PUT`,body:JSON.stringify({enabled:this.enabled,password:this.password.trim()||void 0,sessionTtlHours:this.sessionTtlHours})});this.applyStatus(e),this.password=``,this.message=C(`lanAccessSaved`)}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.saving=!1,this.requestUpdate()}}}}async revokeAll(){if(await v({description:C(`lanAccessRevokeAllConfirm`),confirmLabel:C(`lanAccessRevokeAll`),cancelLabel:C(`cancel`),variant:`destructive`})){this.saving=!0,this.error=``,this.message=``,this.requestUpdate();try{let e=await this.request(`/api/lan-access/revoke-all`,{method:`POST`});this.applyStatus(e),this.message=C(`lanAccessRevoked`)}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.saving=!1,this.requestUpdate()}}}renderSwitch(e,t,n=!1){return f`
947
+ <label class="quickforge-settings-switch" aria-disabled=${n?`true`:`false`}>
948
+ <input
949
+ type="checkbox"
950
+ .checked=${e}
951
+ ?disabled=${n}
952
+ @change=${e=>t(e.target.checked)}
953
+ />
954
+ <span aria-hidden="true"></span>
955
+ </label>
956
+ `}render(){return this.loading?f`<div class="text-sm text-muted-foreground">${C(`loading`)}</div>`:f`
957
+ <div class="quickforge-settings-stack">
958
+ <div class="quickforge-settings-warning">${C(`lanAccessRiskWarning`)}</div>
959
+
960
+ <section class="quickforge-settings-section" aria-label=${C(`lanAccessStatus`)}>
961
+ <div class="quickforge-settings-row">
962
+ <div class="quickforge-settings-row-main">
963
+ <div class="quickforge-settings-row-title">${C(`lanAccessEnabled`)}</div>
964
+ <div class="quickforge-settings-row-description">${C(`lanAccessEnabledDescription`)}</div>
965
+ </div>
966
+ <div class="quickforge-settings-row-control quickforge-settings-readonly-value">${this.enabled?C(`enabled`):C(`disabled`)}</div>
967
+ </div>
968
+ <div class="quickforge-settings-row">
969
+ <div class="quickforge-settings-row-main">
970
+ <div class="quickforge-settings-row-title">${C(`lanAccessPassword`)}</div>
971
+ <div class="quickforge-settings-row-description">${C(`lanAccessPasswordStatusDescription`)}</div>
972
+ </div>
973
+ <div class="quickforge-settings-row-control quickforge-settings-readonly-value">${this.hasPassword?C(`configured`):C(`notConfigured`)}</div>
974
+ </div>
975
+ <div class="quickforge-settings-row">
976
+ <div class="quickforge-settings-row-main">
977
+ <div class="quickforge-settings-row-title">${C(`lanAccessActiveDevices`)}</div>
978
+ <div class="quickforge-settings-row-description">${C(`lanAccessActiveDevicesDescription`)}</div>
979
+ </div>
980
+ <div class="quickforge-settings-row-control quickforge-settings-readonly-value">${this.activeTokenCount}</div>
981
+ </div>
982
+ <div class="quickforge-settings-row">
983
+ <div class="quickforge-settings-row-main">
984
+ <div class="quickforge-settings-row-title">${C(`lanAccessUrls`)}</div>
985
+ <div class="quickforge-settings-row-description">${C(`lanAccessUrlsDescription`)}</div>
986
+ </div>
987
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide quickforge-settings-readonly-value">
988
+ ${this.lanUrls.length?this.lanUrls.map(e=>f`<div>${e}</div>`):`-`}
989
+ </div>
990
+ </div>
991
+ </section>
992
+
993
+ <section class="quickforge-settings-section" aria-label=${C(`lanAccess`)}>
994
+ <div class="quickforge-settings-row">
995
+ <div class="quickforge-settings-row-main">
996
+ <div class="quickforge-settings-row-title">${C(`lanAccessAllowFull`)}</div>
997
+ <div class="quickforge-settings-row-description">${C(`lanAccessAllowFullDescription`)}</div>
998
+ </div>
999
+ <div class="quickforge-settings-row-control">
1000
+ ${this.renderSwitch(this.enabled,e=>this.updateEnabled(e),this.saving)}
1001
+ </div>
1002
+ </div>
1003
+
1004
+ <div class="quickforge-settings-row">
1005
+ <div class="quickforge-settings-row-main">
1006
+ <div class="quickforge-settings-row-title">${C(`lanAccessPassword`)}</div>
1007
+ <div class="quickforge-settings-row-description">${this.hasPassword?C(`lanAccessPasswordPlaceholderConfigured`):C(`lanAccessPasswordPlaceholder`)}</div>
1008
+ </div>
1009
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide quickforge-lan-password-control">
1010
+ <input
1011
+ class="quickforge-settings-input"
1012
+ type="password"
1013
+ .value=${this.password}
1014
+ @input=${e=>this.updatePassword(e.target.value)}
1015
+ placeholder=${this.hasPassword?C(`lanAccessPasswordPlaceholderConfigured`):C(`lanAccessPasswordPlaceholder`)}
1016
+ />
1017
+ <button
1018
+ class="quickforge-settings-button quickforge-settings-button-secondary"
1019
+ type="button"
1020
+ @click=${()=>this.updatePassword(ft())}
1021
+ >
1022
+ ${C(`generatePassword`)}
1023
+ </button>
1024
+ </div>
1025
+ </div>
1026
+
1027
+ <div class="quickforge-settings-row">
1028
+ <div class="quickforge-settings-row-main">
1029
+ <div class="quickforge-settings-row-title">${C(`lanAccessSessionTtl`)}</div>
1030
+ <div class="quickforge-settings-row-description">${C(`lanAccessSessionTtlDescription`)}</div>
1031
+ </div>
1032
+ <div class="quickforge-settings-row-control">
1033
+ <select class="quickforge-settings-select" .value=${String(this.sessionTtlHours)} @change=${e=>this.updateTtl(e.target.value)}>
1034
+ <option value="1">1 ${C(`hour`)}</option>
1035
+ <option value="12">12 ${C(`hours`)}</option>
1036
+ <option value="24">24 ${C(`hours`)}</option>
1037
+ <option value="168">7 ${C(`days`)}</option>
1038
+ </select>
1039
+ </div>
1040
+ </div>
1041
+
1042
+ <div class="quickforge-settings-row">
1043
+ <div class="quickforge-settings-row-main">
1044
+ <div class="quickforge-settings-row-title">${C(`lanAccessActions`)}</div>
1045
+ <div class="quickforge-settings-row-description">${C(`lanAccessActionsDescription`)}</div>
1046
+ </div>
1047
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide">
1048
+ <button class="quickforge-settings-button quickforge-settings-button-primary" type="button" ?disabled=${this.saving} @click=${()=>this.saveSettings()}>
1049
+ ${this.saving?C(`saving`):C(`save`)}
1050
+ </button>
1051
+ <button class="quickforge-settings-button quickforge-settings-button-danger" type="button" ?disabled=${this.saving} @click=${()=>this.revokeAll()}>
1052
+ ${C(`lanAccessRevokeAll`)}
1053
+ </button>
1054
+ </div>
1055
+ </div>
1056
+ </section>
1057
+
1058
+ ${this.message?f`<div class="quickforge-settings-message">${this.message}</div>`:null}
1059
+ ${this.error?f`<div class="quickforge-settings-alert">${this.error}</div>`:null}
1060
+ </div>
1061
+ `}},V=`quickforge-lan-access-settings-tab`;customElements.get(V)||customElements.define(V,pt);function mt(){return document.createElement(V)}var ht=18e4,gt=3e4,_t=1e3,H=`https://github.com/shawnstack/quickforge/releases/latest`;function U(){if(typeof document>`u`)return!1;let e=window;return document.body.classList.contains(`quickforge-desktop-app`)||e.__quickforgeDesktopApp===!0}function W(e){return new Promise(t=>window.setTimeout(t,e))}var vt=[{value:`startup`,label:()=>C(`frequencyStartup`)},{value:`daily`,label:()=>C(`frequencyDaily`)},{value:`weekly`,label:()=>C(`frequencyWeekly`)},{value:`off`,label:()=>C(`frequencyOff`)}],yt=class extends p{about;updateInfo;loading=!0;checking=!1;updating=!1;restarting=!1;message=``;error=``;frequency=be.frequency;lastCheckAt=null;serviceStatus;getTabName(){return C(`about`)}async connectedCallback(){super.connectedCallback(),await this.loadAbout()}async loadAbout(){this.loading=!0,this.error=``,this.requestUpdate();try{let e=await fetch(`/api/system/about`,{cache:`no-store`}),t=await e.json().catch(()=>null);if(!e.ok)throw Error(t?.error||C(`requestFailed`));this.about=t;try{let[e,t]=await Promise.all([ve(m()),this.loadServiceStatus()]);this.frequency=e.frequency,this.lastCheckAt=e.lastCheckAt,this.serviceStatus=t}catch{}}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.loading=!1,this.requestUpdate()}}async loadServiceStatus(){let e=await fetch(`/api/health`,{cache:`no-store`}),t=await e.json().catch(()=>null);if(!e.ok)throw Error(t?.error||C(`requestFailed`));return t}async checkUpdate(){if(!(this.checking||this.updating)){if(U()){window.open(H,`_blank`,`noopener,noreferrer`),this.message=C(`desktopUpdateHint`),this.error=``,this.requestUpdate();return}this.checking=!0,this.message=``,this.error=``,this.requestUpdate();try{let e=await fetch(`/api/system/update/check`,{cache:`no-store`}),t=await e.json().catch(()=>null);if(!e.ok)throw Error(t?.error||C(`updateCheckFailed`));this.updateInfo=t,this.about=t,this.updateInfo.updateAvailable?this.message=C(`updateAvailableMessage`,{current:this.updateInfo.currentVersion,latest:this.updateInfo.latestVersion}):this.updateInfo.localVersionIsNewer?this.message=C(`localVersionNewerMessage`,{current:this.updateInfo.currentVersion,latest:this.updateInfo.latestVersion}):this.message=C(`alreadyLatestVersion`,{version:this.updateInfo.currentVersion})}catch(e){this.error=e instanceof Error?e.message:C(`updateCheckFailed`)}finally{this.checking=!1,this.requestUpdate()}}}async pollUntilUpdated(e){let t=Date.now();for(;Date.now()-t<ht;){await W(_t);try{let t=await fetch(`/api/health?updatePoll=${Date.now()}`,{cache:`no-store`}),n=await t.json().catch(()=>null);if(t.ok&&n?.ok&&n.bootId&&n.bootId!==e){this.message=C(`updateRestarted`),this.requestUpdate(),window.setTimeout(()=>window.location.reload(),300);return}}catch{}}throw Error(C(`updateRestartTimeout`))}async pollUntilRestarted(e){let t=Date.now();for(;Date.now()-t<gt;){await W(_t);try{let t=await fetch(`/api/health?restartPoll=${Date.now()}`,{cache:`no-store`}),n=await t.json().catch(()=>null);if(t.ok&&n?.ok&&n.bootId&&n.bootId!==e){this.message=C(`backendRestarted`),this.requestUpdate(),window.setTimeout(()=>window.location.reload(),300);return}}catch{}}throw Error(C(`backendRestartTimeout`))}async restartService(){if(!this.serviceStatus||this.restarting||!await v({description:C(`restartBackendConfirm`),confirmLabel:C(`restartBackendService`),cancelLabel:C(`cancel`)}))return;this.restarting=!0,this.message=C(`backendRestarting`),this.error=``,this.requestUpdate();let e=this.serviceStatus.bootId;try{let t=await fetch(`/api/system/restart`,{method:`POST`,headers:{"x-quickforge-action":`restart`}}),n=await t.json().catch(()=>null);if(!t.ok)throw Error(n?.error||C(`backendRestartFailed`));await this.pollUntilRestarted(e)}catch(e){this.error=e instanceof Error?e.message:C(`backendRestartFailed`),this.message=``,this.restarting=!1,this.requestUpdate()}}async updateQuickForge(){if(U()){window.open(H,`_blank`,`noopener,noreferrer`),this.message=C(`desktopUpdateHint`),this.error=``,this.requestUpdate();return}if(!(!this.updateInfo?.updateAvailable||this.updating)&&await v({description:C(`updateConfirm`,{command:this.updateInfo.installCommand}),confirmLabel:C(`updateRuntimeNow`),cancelLabel:C(`cancel`)})){this.updating=!0,this.message=C(`updatingQuickForge`),this.error=``,this.requestUpdate();try{let e=await fetch(`/api/system/update`,{method:`POST`,headers:{"x-quickforge-action":`update`}}),t=await e.json().catch(()=>null);if(!e.ok)throw Error(t?.error||C(`updateFailed`));if(this.updateInfo=t,t?.updateStarted){let e=t.logFile?` ${C(`updateLogFile`,{path:t.logFile})}`:``;this.message=`${C(`updateStarted`)}${e}`,this.requestUpdate(),await this.pollUntilUpdated(t.bootId)}else this.message=t?.updated?C(`updateCompleted`):C(`alreadyLatestVersion`,{version:t?.currentVersion||this.about?.version||`-`}),this.updating=!1}catch(e){this.error=e instanceof Error?e.message:C(`updateFailed`),this.message=``,this.updating=!1}finally{this.requestUpdate()}}}async selectFrequency(e){if(this.frequency!==e){this.frequency=e,this.requestUpdate();try{let t=m();await Ee(t,{...await ve(t),frequency:e}),this.error=``}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.requestUpdate()}}}renderFrequencyOption(e){let t=this.frequency===e.value;return f`
1062
+ <button
1063
+ type="button"
1064
+ class="quickforge-settings-segmented-option ${t?`quickforge-settings-segmented-option-active`:``}"
1065
+ aria-pressed=${t?`true`:`false`}
1066
+ @click=${()=>this.selectFrequency(e.value)}
1067
+ >
1068
+ ${e.label()}
1069
+ </button>
1070
+ `}infoRows(){let e=this.about;return e?f`
1071
+ <div class="quickforge-settings-row">
1072
+ <div class="quickforge-settings-row-main">
1073
+ <div class="quickforge-settings-row-title">${C(`packageName`)}</div>
1074
+ <div class="quickforge-settings-row-description">${C(`packageNameDescription`)}</div>
1075
+ </div>
1076
+ <div class="quickforge-settings-row-control quickforge-settings-readonly-value">${e.name}</div>
1077
+ </div>
1078
+ <div class="quickforge-settings-row">
1079
+ <div class="quickforge-settings-row-main">
1080
+ <div class="quickforge-settings-row-title">${C(`currentVersion`)}</div>
1081
+ <div class="quickforge-settings-row-description">${C(`currentVersionDescription`)}</div>
1082
+ </div>
1083
+ <div class="quickforge-settings-row-control quickforge-settings-readonly-value">${e.version}</div>
1084
+ </div>
1085
+ <div class="quickforge-settings-row">
1086
+ <div class="quickforge-settings-row-main">
1087
+ <div class="quickforge-settings-row-title">${C(`github`)}</div>
1088
+ <div class="quickforge-settings-row-description">${C(`githubDescription`)}</div>
1089
+ </div>
1090
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide quickforge-settings-readonly-value">
1091
+ <a class="quickforge-settings-link" href=${e.repositoryUrl} target="_blank" rel="noreferrer">
1092
+ ${e.repositoryUrl}
1093
+ </a>
1094
+ </div>
1095
+ </div>
1096
+ `:null}updateStatus(){return this.updateInfo?f`
1097
+ <div class="quickforge-settings-row">
1098
+ <div class="quickforge-settings-row-main">
1099
+ <div class="quickforge-settings-row-title">${C(`latestVersion`)}</div>
1100
+ <div class="quickforge-settings-row-description">${C(`latestVersionDescription`)}</div>
1101
+ </div>
1102
+ <div class="quickforge-settings-row-control quickforge-settings-readonly-value">${this.updateInfo.latestVersion}</div>
1103
+ </div>
1104
+ ${this.updateInfo.logFile?f`
1105
+ <div class="quickforge-settings-row">
1106
+ <div class="quickforge-settings-row-main">
1107
+ <div class="quickforge-settings-row-title">${C(`updateLog`)}</div>
1108
+ <div class="quickforge-settings-row-description">${C(`updateLogDescription`)}</div>
1109
+ </div>
1110
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide quickforge-settings-readonly-value">
1111
+ <code>${this.updateInfo.logFile}</code>
1112
+ </div>
1113
+ </div>
1114
+ `:null}
1115
+ `:null}restartSection(){let e=this.serviceStatus?.restartUnsupportedReason||C(`backendRestartUnsupported`),t=this.restarting||!this.serviceStatus?.restartSupported;return f`
1116
+ <section class="quickforge-settings-section" aria-label=${C(`restartBackendService`)}>
1117
+ <div class="quickforge-settings-row">
1118
+ <div class="quickforge-settings-row-main">
1119
+ <div class="quickforge-settings-row-title">
1120
+ ${C(`restartBackendService`)}
1121
+ <quickforge-info-tip .label=${C(`restartBackendServiceDescription`)}></quickforge-info-tip>
1122
+ </div>
1123
+ <div class="quickforge-settings-row-description">
1124
+ ${this.serviceStatus?.restartSupported?C(`restartBackendServiceDescription`):e}
1125
+ </div>
1126
+ </div>
1127
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide">
1128
+ <button
1129
+ class="quickforge-settings-button quickforge-settings-button-danger"
1130
+ type="button"
1131
+ ?disabled=${t}
1132
+ @click=${()=>this.restartService()}
1133
+ >
1134
+ ${this.restarting?C(`backendRestarting`):C(`restartBackendService`)}
1135
+ </button>
1136
+ </div>
1137
+ </div>
1138
+ </section>
1139
+ `}render(){if(this.loading)return f`<div class="text-sm text-muted-foreground">${C(`loading`)}</div>`;let e=this.checking||this.updating||!this.updateInfo?.updateAvailable,t=U();return f`
1140
+ <div class="quickforge-settings-stack">
1141
+ <section class="quickforge-settings-section" aria-label=${C(`projectInfo`)}>
1142
+ ${this.infoRows()}
1143
+ </section>
1144
+
1145
+ <section class="quickforge-settings-section" aria-label=${C(t?`desktopUpdates`:`runtimeUpdates`)}>
1146
+ ${t?null:f`
1147
+ <div class="quickforge-settings-row">
1148
+ <div class="quickforge-settings-row-main">
1149
+ <div class="quickforge-settings-row-title">
1150
+ ${C(`updateFrequencySection`)}
1151
+ <quickforge-info-tip .label=${C(`updateFrequencyDescription`)}></quickforge-info-tip>
1152
+ </div>
1153
+ <div class="quickforge-settings-row-description">
1154
+ ${this.lastCheckAt?C(`lastCheckedAt`,{time:new Date(this.lastCheckAt).toLocaleString(we())}):C(`lastCheckedNever`)}
1155
+ </div>
1156
+ </div>
1157
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide">
1158
+ <div class="quickforge-settings-segmented quickforge-settings-segmented-wrap" role="group" aria-label=${C(`updateFrequencySection`)}>
1159
+ ${vt.map(e=>this.renderFrequencyOption(e))}
1160
+ </div>
1161
+ </div>
1162
+ </div>
1163
+
1164
+ ${this.updateStatus()}
1165
+ `}
1166
+
1167
+ <div class="quickforge-settings-row">
1168
+ <div class="quickforge-settings-row-main">
1169
+ <div class="quickforge-settings-row-title">
1170
+ ${C(t?`desktopUpdates`:`runtimeUpdates`)}
1171
+ <quickforge-info-tip .label=${C(t?`desktopUpdatesDescription`:`runtimeUpdatesDescription`)}></quickforge-info-tip>
1172
+ </div>
1173
+ <div class="quickforge-settings-row-description">
1174
+ ${C(t?`desktopUpdatesDescription`:`runtimeUpdatesDescription`)}
1175
+ </div>
1176
+ </div>
1177
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide">
1178
+ <button
1179
+ class="quickforge-settings-button quickforge-settings-button-secondary"
1180
+ type="button"
1181
+ ?disabled=${this.checking||this.updating}
1182
+ @click=${()=>this.checkUpdate()}
1183
+ >
1184
+ ${this.checking?C(`checkingUpdate`):C(t?`openDesktopReleases`:`checkRuntimeUpdate`)}
1185
+ </button>
1186
+ ${t?null:f`
1187
+ <button
1188
+ class="quickforge-settings-button quickforge-settings-button-primary"
1189
+ type="button"
1190
+ ?disabled=${e}
1191
+ @click=${()=>this.updateQuickForge()}
1192
+ >
1193
+ ${this.updating?C(`updatingQuickForge`):C(`updateRuntimeNow`)}
1194
+ </button>
1195
+ `}
1196
+ </div>
1197
+ </div>
1198
+ </section>
1199
+
1200
+ ${this.restartSection()}
1201
+
1202
+ ${this.message?f`<div class="quickforge-settings-message">${this.message}</div>`:null}
1203
+ ${this.error?f`<div class="quickforge-settings-alert">${this.error}</div>`:null}
1204
+ </div>
1205
+ `}},G=`quickforge-about-settings-tab`;customElements.get(G)||customElements.define(G,yt);function bt(){return document.createElement(G)}var xt=class extends p{loading=!0;saving=!1;saved=!1;error=``;message=``;projects=[];project;commandDir=``;commands=[];loadingCommands=!1;saveTimer=null;lastSavedCommandDir=``;pendingSaveAfterCurrent=!1;getTabName(){return C(`projectCommands`)}async connectedCallback(){super.connectedCallback(),await this.loadProjects()}async request(e,t){let n=await fetch(e,{...t,cache:`no-store`,headers:{...t?.body?{"content-type":`application/json`}:void 0,...t?.headers}}),r=await n.json().catch(()=>null);if(!n.ok)throw Error(r?.error||C(`requestFailed`));return r}async loadProjects(){this.loading=!0,this.error=``,this.requestUpdate();try{let e=await this.request(`/api/project`);this.projects=e?.projects??(e?.project?[e.project]:[]),this.project=e?.project??this.projects[0],this.commandDir=typeof this.project?.commandDir==`string`?this.project.commandDir:``,this.lastSavedCommandDir=this.commandDir,await this.loadCommands()}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.loading=!1,this.requestUpdate()}}selectProject(e){this.clearSaveTimer();let t=e.target,n=this.projects.find(e=>e.id===t.value);n&&(this.project=n,this.commandDir=typeof n.commandDir==`string`?n.commandDir:``,this.lastSavedCommandDir=this.commandDir,this.saved=!1,this.message=``,this.error=``,this.commands=[],this.requestUpdate(),this.loadCommands())}async loadCommands(){if(this.project?.id){this.loadingCommands=!0,this.requestUpdate();try{let e=await this.request(`/api/project/commands?projectId=${encodeURIComponent(this.project.id)}`);this.commands=e?.commands??[]}catch{this.commands=[]}finally{this.loadingCommands=!1,this.requestUpdate()}}}updateCommandDir(e){this.commandDir=e,this.saved=!1,this.message=``,this.scheduleSave(),this.requestUpdate()}clearSaveTimer(){this.saveTimer!==null&&(window.clearTimeout(this.saveTimer),this.saveTimer=null)}scheduleSave(){this.clearSaveTimer(),this.saveTimer=window.setTimeout(()=>{this.saveTimer=null,this.save()},800)}disconnectedCallback(){this.clearSaveTimer(),super.disconnectedCallback()}async save(){if(!this.project)return;if(this.saving){this.pendingSaveAfterCurrent=!0;return}let e=this.project.id,t=this.commandDir;if(t!==this.lastSavedCommandDir){this.saving=!0,this.saved=!1,this.error=``,this.message=``,this.requestUpdate();try{let n=await this.request(`/api/project/${encodeURIComponent(e)}/command-dir`,{method:`PUT`,body:JSON.stringify({commandDir:t})});if(this.project?.id===e){if(this.project=n?.project??this.project,this.project){let e=this.projects.findIndex(e=>e.id===this.project.id);e>=0&&(this.projects[e]=this.project)}this.commandDir=typeof this.project?.commandDir==`string`?this.project.commandDir:t,this.lastSavedCommandDir=this.commandDir,this.saved=!0,await this.loadCommands()}}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.saving=!1,this.pendingSaveAfterCurrent&&(this.pendingSaveAfterCurrent=!1,this.scheduleSave()),this.requestUpdate()}}}async openCommandDir(){if(this.project){this.error=``,this.message=``,this.requestUpdate();try{await this.request(`/api/project/open-path`,{method:`POST`,body:JSON.stringify({path:`.ai/commands`,projectId:this.project.id})})}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`),this.requestUpdate()}}}async createCommand(){if(!this.project)return;let e=window.prompt(C(`newCommandPrompt`));if(e?.trim()){this.error=``,this.message=``,this.requestUpdate();try{let t=await this.request(`/api/project/command`,{method:`POST`,body:JSON.stringify({name:e.trim(),projectId:this.project.id})});t.ok?(this.message=C(`commandCreated`,{name:t.name??e.trim()}),await this.loadCommands()):t.reason===`exists`?this.error=C(`commandAlreadyExists`,{name:t.name??e.trim()}):this.error=C(`invalidCommandName`)}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.requestUpdate()}}}render(){return this.loading?f`<div class="quickforge-settings-note">${C(`loading`)}</div>`:this.projects.length===0?f`
1206
+ <div class="quickforge-settings-stack">
1207
+ <div class="quickforge-settings-note">${C(`selectProjectForCommands`)}</div>
1208
+ ${this.error?f`<div class="quickforge-settings-alert">${this.error}</div>`:null}
1209
+ </div>
1210
+ `:f`
1211
+ <div class="quickforge-settings-stack">
1212
+ <section class="quickforge-settings-section" aria-label=${C(`projectCommands`)}>
1213
+ <div class="quickforge-settings-row">
1214
+ <div class="quickforge-settings-row-main">
1215
+ <div class="quickforge-settings-row-title">${C(`project`)}</div>
1216
+ <div class="quickforge-settings-row-description">
1217
+ ${this.project?.path?this.project.path:C(`projectCommandsProjectDescription`)}
1218
+ </div>
1219
+ </div>
1220
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide">
1221
+ <select
1222
+ class="quickforge-settings-select"
1223
+ .value=${this.project?.id??``}
1224
+ @change=${e=>this.selectProject(e)}
1225
+ >
1226
+ ${this.projects.map(e=>f`<option value=${e.id} ?selected=${e.id===this.project?.id}>${e.name}</option>`)}
1227
+ </select>
1228
+ </div>
1229
+ </div>
1230
+
1231
+ <div class="quickforge-settings-row quickforge-settings-row-align-start">
1232
+ <div class="quickforge-settings-row-main">
1233
+ <div class="quickforge-settings-row-title">
1234
+ ${C(`commandDirectories`)}
1235
+ <quickforge-info-tip .label=${C(`commandDirectoryHelp`)}></quickforge-info-tip>
1236
+ </div>
1237
+ <div class="quickforge-settings-row-description">${C(`commandDirectoriesDescription`)}</div>
1238
+ </div>
1239
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide">
1240
+ <textarea
1241
+ class="quickforge-settings-textarea quickforge-settings-mono"
1242
+ .value=${this.commandDir}
1243
+ placeholder=${C(`commandDirectoryPlaceholder`)}
1244
+ @input=${e=>this.updateCommandDir(e.target.value)}
1245
+ ></textarea>
1246
+ </div>
1247
+ </div>
1248
+
1249
+ <div class="quickforge-settings-row quickforge-settings-row-align-start">
1250
+ <div class="quickforge-settings-row-main">
1251
+ <div class="quickforge-settings-row-title">${C(`commandDirectoryExamples`)}</div>
1252
+ <div class="quickforge-settings-row-description">${C(`commandDirectoryExamplesDescription`)}</div>
1253
+ </div>
1254
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide quickforge-settings-code-list">
1255
+ <code>.ai/commands</code>
1256
+ <code>.claude/commands</code>
1257
+ <code>.opencode/commands</code>
1258
+ <code>D:\shared\ai-commands</code>
1259
+ </div>
1260
+ </div>
1261
+ </section>
1262
+
1263
+ <section class="quickforge-settings-section" aria-label=${C(`loadedCommands`,{count:this.commands.length})}>
1264
+ <div class="quickforge-settings-row">
1265
+ <div class="quickforge-settings-row-main">
1266
+ <div class="quickforge-settings-row-title">${C(`loadedCommands`,{count:this.commands.length})}</div>
1267
+ <div class="quickforge-settings-row-description">${C(`loadedCommandsDescription`)}</div>
1268
+ </div>
1269
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide">
1270
+ <button
1271
+ class="quickforge-settings-button quickforge-settings-button-secondary"
1272
+ type="button"
1273
+ ?disabled=${this.loadingCommands}
1274
+ @click=${()=>this.openCommandDir()}
1275
+ >
1276
+ ${C(`openCommandDir`)}
1277
+ </button>
1278
+ <button
1279
+ class="quickforge-settings-button quickforge-settings-button-primary"
1280
+ type="button"
1281
+ ?disabled=${this.loadingCommands}
1282
+ @click=${()=>this.createCommand()}
1283
+ >
1284
+ ${C(`createCommand`)}
1285
+ </button>
1286
+ </div>
1287
+ </div>
1288
+
1289
+ <div class="quickforge-settings-nested-list">
1290
+ ${this.loadingCommands?f`<div class="quickforge-settings-empty-row">${C(`loading`)}</div>`:this.commands.length===0?f`<div class="quickforge-settings-empty-row">${C(`noCommandsLoaded`)}</div>`:this.commands.map(e=>{let t=e.argumentHint?` ${e.argumentHint}`:``;return f`
1291
+ <div class="quickforge-settings-subrow">
1292
+ <div class="quickforge-settings-row-main">
1293
+ <div class="quickforge-settings-row-title">
1294
+ <code class="quickforge-settings-command-name">/${e.name}${t}</code>
1295
+ </div>
1296
+ <div class="quickforge-settings-row-description">
1297
+ ${e.description||C(`noDescription`)}
1298
+ </div>
1299
+ </div>
1300
+ ${e.relativePath?f`<div class="quickforge-settings-row-control quickforge-settings-row-control-wide quickforge-settings-readonly-value">${e.relativePath}</div>`:null}
1301
+ </div>
1302
+ `})}
1303
+ </div>
1304
+ </section>
1305
+
1306
+ ${this.saving?f`<div class="quickforge-settings-note">${C(`saving`)}</div>`:null}
1307
+ ${this.saved?f`<div class="quickforge-settings-message">${C(`projectCommandsSaved`)}</div>`:null}
1308
+ ${this.message?f`<div class="quickforge-settings-message">${this.message}</div>`:null}
1309
+ ${this.error?f`<div class="quickforge-settings-alert">${this.error}</div>`:null}
1310
+ </div>
1311
+ `}},K=`quickforge-project-commands-settings-tab`;customElements.get(K)||customElements.define(K,xt);function St(){return document.createElement(K)}var q={"x-quickforge-action":`channel-action`};function Ct(e){switch(e){case`running`:return`quickforge-settings-badge-success`;case`waiting_scan`:case`starting`:case`stopping`:return`quickforge-settings-badge-warning`;case`error`:return`quickforge-settings-badge-danger`;default:return`quickforge-settings-badge-muted`}}function wt(e){switch(e){case`starting`:return C(`channelStatusStarting`);case`waiting_scan`:return C(`channelStatusWaitingScan`);case`running`:return C(`channelStatusRunning`);case`stopping`:return C(`channelStatusStopping`);case`error`:return C(`channelStatusError`);default:return C(`channelStatusStopped`)}}function Tt(e){if(!e)return`-`;let t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString()}var Et=class extends p{loading=!0;channels=[];workspaces=[];selectedWorkspaceIdByChannel={};error=``;message=``;eventSource;busyChannelId=``;getTabName(){return C(`channels`)}async connectedCallback(){super.connectedCallback(),await Promise.all([this.loadChannels(),this.loadWorkspaces()]),this.connectEvents()}disconnectedCallback(){this.eventSource?.close(),this.eventSource=void 0,super.disconnectedCallback()}async request(e,t){let n=await fetch(e,{...t,cache:`no-store`,headers:{...t?.headers||{}}}),r=await n.json().catch(()=>null);if(!n.ok)throw Error(r?.error||C(`requestFailed`));return r}async loadChannels(){this.loading=!0,this.error=``,this.requestUpdate();try{let e=await this.request(`/api/channels`);this.channels=Array.isArray(e.channels)?e.channels:[],this.ensureWorkspaceSelections()}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.loading=!1,this.requestUpdate()}}async loadWorkspaces(){try{let e=await this.request(`/api/project`),t=e.defaultWorkspaceRoot||``,n=t?[{id:`default`,name:C(`channelDefaultWorkspace`),path:t,kind:`default`}]:[],r=(Array.isArray(e.projects)?e.projects:[]).map(e=>({id:e.id,name:e.name,path:e.path,kind:`project`}));this.workspaces=[...n,...r],this.ensureWorkspaceSelections()}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.requestUpdate()}}ensureWorkspaceSelections(){let e=new Set(this.workspaces.map(e=>e.id)),t={...this.selectedWorkspaceIdByChannel};for(let n of this.channels){if(!n.supportsWorkspaceSelection)continue;let r=t[n.id],i=n.launchWorkspace?.id,a=n.status!==`stopped`&&n.status!==`error`;if(i&&e.has(i)&&a){t[n.id]=i;continue}r&&e.has(r)||(t[n.id]=i&&e.has(i)?i:`default`)}this.selectedWorkspaceIdByChannel=t}connectEvents(){this.eventSource?.close();let e=new EventSource(`/api/channels/events`);this.eventSource=e;let t=e=>{try{let t=JSON.parse(e.data);this.applyChannelEvent(t)}catch{}};e.addEventListener(`snapshot`,t),e.addEventListener(`status`,t),e.addEventListener(`log`,t),e.addEventListener(`qrcode`,t),e.addEventListener(`error`,()=>{})}applyChannelEvent(e){if(Array.isArray(e.channels)){this.channels=e.channels,this.ensureWorkspaceSelections(),this.requestUpdate();return}if(e.snapshot){this.upsertChannel(e.snapshot);return}if(e.channelId&&e.log){let t=this.channels.find(t=>t.id===e.channelId);t&&(t.logs=[...t.logs||[],e.log].slice(-300),this.requestUpdate())}}upsertChannel(e){let t=this.channels.findIndex(t=>t.id===e.id);t>=0?this.channels=[...this.channels.slice(0,t),e,...this.channels.slice(t+1)]:this.channels=[...this.channels,e],this.ensureWorkspaceSelections(),this.requestUpdate()}selectedWorkspaceId(e){return this.selectedWorkspaceIdByChannel[e.id]||e.launchWorkspace?.id||`default`}handleWorkspaceChange(e,t){let n=t.currentTarget;n&&(this.selectedWorkspaceIdByChannel={...this.selectedWorkspaceIdByChannel,[e.id]:n.value||`default`},this.requestUpdate())}startOptions(e){return e.supportsWorkspaceSelection?{projectId:this.selectedWorkspaceId(e)}:void 0}async invoke(e,t){if(!this.busyChannelId&&!(t===`restart`&&!await v({description:C(`channelRestartConfirm`,{name:e.name}),confirmLabel:C(`restart`),cancelLabel:C(`cancel`)}))){this.busyChannelId=e.id,this.error=``,this.message=``,this.requestUpdate();try{let n=t===`start`||t===`restart`?this.startOptions(e):void 0,r=await this.request(`/api/channels/${encodeURIComponent(e.id)}/${t}`,{method:`POST`,headers:n?{...q,"content-type":`application/json`}:q,body:n?JSON.stringify(n):void 0});this.upsertChannel(r)}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.busyChannelId=``,this.requestUpdate()}}}async invokeAction(e,t){if(!this.busyChannelId&&!((t.destructive||t.id===`relogin`)&&!await v({description:t.id===`relogin`?C(`channelReloginConfirm`,{name:e.name}):C(`channelLogoutConfirm`,{name:e.name}),confirmLabel:t.label,cancelLabel:C(`cancel`),variant:t.destructive?`destructive`:void 0}))){this.busyChannelId=e.id,this.error=``,this.message=``,this.requestUpdate();try{let n=t.id===`relogin`?this.startOptions(e):void 0,r=await this.request(`/api/channels/${encodeURIComponent(e.id)}/actions/${encodeURIComponent(t.id)}`,{method:`POST`,headers:n?{...q,"content-type":`application/json`}:q,body:n?JSON.stringify(n):void 0});this.upsertChannel(r)}catch(e){this.error=e instanceof Error?e.message:C(`requestFailed`)}finally{this.busyChannelId=``,this.requestUpdate()}}}workspaceSection(e,t){if(!e.supportsWorkspaceSelection)return null;let n=this.selectedWorkspaceId(e),r=e.launchWorkspace;return f`
1312
+ <div class="quickforge-settings-row quickforge-settings-row-align-start">
1313
+ <div class="quickforge-settings-row-main">
1314
+ <div class="quickforge-settings-row-title">
1315
+ ${C(`channelWorkspace`)}
1316
+ <quickforge-info-tip .label=${C(`channelWorkspaceDescription`)}></quickforge-info-tip>
1317
+ </div>
1318
+ ${r?f`<div class="quickforge-settings-row-description">${C(`channelCurrentWorkspace`)}: <span class="quickforge-settings-mono">${r.path}</span></div>`:f`<div class="quickforge-settings-row-description">${C(`channelWorkspaceDescription`)}</div>`}
1319
+ </div>
1320
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide">
1321
+ <select
1322
+ class="quickforge-settings-select"
1323
+ ?disabled=${t}
1324
+ .value=${n}
1325
+ @change=${t=>this.handleWorkspaceChange(e,t)}
1326
+ >
1327
+ ${this.workspaces.map(e=>f`
1328
+ <option value=${e.id}>${e.kind===`default`?C(`channelDefaultWorkspace`):e.name} — ${e.path}</option>
1329
+ `)}
1330
+ </select>
1331
+ </div>
1332
+ </div>
1333
+ `}channelMeta(e){return f`
1334
+ <div class="quickforge-settings-nested-list">
1335
+ ${[[C(`channelProvider`),e.provider||`-`],[C(`channelCommand`),e.commandLabel||`-`],[C(`channelPid`),e.pid?String(e.pid):`-`],[C(`channelStartedAt`),Tt(e.startedAt)]].map(([e,t])=>f`
1336
+ <div class="quickforge-settings-subrow">
1337
+ <div class="quickforge-settings-row-main">
1338
+ <div class="quickforge-settings-row-title">${e}</div>
1339
+ </div>
1340
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide quickforge-settings-readonly-value ${e===C(`channelCommand`)?`quickforge-settings-mono`:``}">${t}</div>
1341
+ </div>
1342
+ `)}
1343
+ </div>
1344
+ `}qrSection(e){return!e.qrCodeText&&!e.qrCodeUrl&&e.status!==`waiting_scan`?null:f`
1345
+ <div class="quickforge-settings-row quickforge-settings-row-align-start">
1346
+ <div class="quickforge-settings-row-main">
1347
+ <div class="quickforge-settings-row-title">
1348
+ ${C(`channelQrTitle`)}
1349
+ <quickforge-info-tip .label=${C(`channelQrDescription`)}></quickforge-info-tip>
1350
+ </div>
1351
+ <div class="quickforge-settings-row-description">${C(`channelQrDescription`)}</div>
1352
+ ${e.qrCodeUrl?f`
1353
+ <div class="quickforge-settings-meta">
1354
+ <a class="quickforge-settings-link quickforge-settings-mono" href=${e.qrCodeUrl} target="_blank" rel="noreferrer">${e.qrCodeUrl}</a>
1355
+ </div>
1356
+ `:null}
1357
+ </div>
1358
+ ${e.qrCodeText?f`<pre class="quickforge-channel-qr-text">${e.qrCodeText}</pre>`:null}
1359
+ </div>
1360
+ `}logsSection(e){let t=e.logs||[];return f`
1361
+ <details class="quickforge-channel-logs" open>
1362
+ <summary class="quickforge-channel-logs-summary">${C(`channelRecentLogs`)}</summary>
1363
+ <div class="quickforge-channel-logs-body">
1364
+ ${t.length?t.slice(-80).map(e=>f`
1365
+ <div class="quickforge-channel-log-row">
1366
+ <span class="text-muted-foreground/55">${new Date(e.time).toLocaleTimeString()}</span>
1367
+ <span class=${e.stream===`stderr`?`text-destructive/80`:`text-muted-foreground/70`}>${e.stream}</span>
1368
+ <span class="min-w-0 whitespace-pre-wrap break-words text-foreground/85">${e.text}</span>
1369
+ </div>
1370
+ `):f`<div class="quickforge-settings-empty-row">${C(`channelNoLogs`)}</div>`}
1371
+ </div>
1372
+ </details>
1373
+ `}channelCard(e){let t=this.busyChannelId===e.id||!!e.activeAction,n=e.status===`running`||e.status===`waiting_scan`||e.status===`starting`,r=e.status===`stopping`,i=!!(e.supportsWorkspaceSelection&&this.workspaces.length===0);return f`
1374
+ <section class="quickforge-settings-section" aria-label=${e.name}>
1375
+ <div class="quickforge-settings-row quickforge-settings-row-top">
1376
+ <div class="quickforge-settings-row-main">
1377
+ <div class="quickforge-settings-row-title">
1378
+ ${e.name}
1379
+ <span class="quickforge-settings-badge ${Ct(e.status)}">${wt(e.status)}</span>
1380
+ </div>
1381
+ <div class="quickforge-settings-row-description">${e.description}</div>
1382
+ ${e.requirements?.length?f`<div class="quickforge-settings-row-description">${C(`channelRequirements`)}: ${e.requirements.join(` · `)}</div>`:null}
1383
+ </div>
1384
+ <div class="quickforge-settings-row-control quickforge-settings-row-control-wide">
1385
+ <button class="quickforge-settings-button quickforge-settings-button-primary" type="button" ?disabled=${t||n||r||i} @click=${()=>this.invoke(e,`start`)}>${C(`start`)}</button>
1386
+ <button class="quickforge-settings-button quickforge-settings-button-secondary" type="button" ?disabled=${t||!n||r} @click=${()=>this.invoke(e,`stop`)}>${C(`stop`)}</button>
1387
+ <button class="quickforge-settings-button quickforge-settings-button-secondary" type="button" ?disabled=${t||r||i} @click=${()=>this.invoke(e,`restart`)}>${C(`restart`)}</button>
1388
+ ${e.actions?.map(n=>f`
1389
+ <button class="quickforge-settings-button ${n.destructive?`quickforge-settings-button-danger`:`quickforge-settings-button-secondary`}" type="button" ?disabled=${t} @click=${()=>this.invokeAction(e,n)}>${n.label}</button>
1390
+ `)}
1391
+ </div>
1392
+ </div>
1393
+
1394
+ ${this.workspaceSection(e,t||n||r)}
1395
+ ${i?f`<div class="quickforge-settings-alert quickforge-settings-warning-attached">${C(`channelNoWorkspaces`)}</div>`:null}
1396
+ ${this.channelMeta(e)}
1397
+ ${e.error?f`<div class="quickforge-settings-alert quickforge-settings-warning-attached">${e.error}</div>`:null}
1398
+ ${this.qrSection(e)}
1399
+ ${this.logsSection(e)}
1400
+ </section>
1401
+ `}render(){return this.loading?f`<div class="quickforge-settings-note">${C(`loading`)}</div>`:f`
1402
+ <div class="quickforge-settings-stack">
1403
+ <section class="quickforge-settings-warning">
1404
+ ${C(`channelsSecurityWarning`)}
1405
+ </section>
1406
+
1407
+ ${this.channels.length?f`${this.channels.map(e=>this.channelCard(e))}`:f`<div class="quickforge-settings-note">${C(`channelsEmpty`)}</div>`}
1408
+
1409
+ ${this.message?f`<div class="quickforge-settings-message">${this.message}</div>`:null}
1410
+ ${this.error?f`<div class="quickforge-settings-alert">${this.error}</div>`:null}
1411
+ </div>
1412
+ `}},J=`quickforge-channels-settings-tab`;customElements.get(J)||customElements.define(J,Et);function Dt(){return document.createElement(J)}var Ot=oe(),Y=ae(),kt=(0,T.lazy)(()=>h(()=>import(`./AgentProfilesPage-BTWTENpJ.js`).then(e=>({default:e.AgentProfilesPage})),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11]))),At=(0,T.lazy)(()=>h(()=>import(`./mcp-servers-dialog-1Y9X80tB.js`).then(e=>({default:e.McpServersPanel})),__vite__mapDeps([12,1,2,3,4,5,6,7,8,9,10,11]))),jt=(0,T.lazy)(()=>h(()=>import(`./skills-dialog-vMwLkH49.js`).then(e=>e.n).then(e=>({default:e.SkillsManagerPanel})),__vite__mapDeps([9,1,2,3,4,5,6,7,8,10,11]))),Mt=(0,T.lazy)(()=>h(()=>import(`./PluginsPage-DRbQLXc6.js`).then(e=>({default:e.PluginsPage})),__vite__mapDeps([13,1,2,3,4,5,6,7,8,9,10,11,14]))),Nt=(0,T.lazy)(()=>h(()=>import(`./ScheduledTasksPage-_Te96TTL.js`).then(e=>({default:e.ScheduledTasksPage})),__vite__mapDeps([15,1,2,3,4,5,6,7,8,9,10,11]))),Pt=class extends p{tabName=``;renderReact;root;getTabName(){return this.tabName}connectedCallback(){super.connectedCallback(),this.updateComplete.then(()=>this.mountReact())}disconnectedCallback(){this.root?.unmount(),this.root=void 0,super.disconnectedCallback()}render(){return f`<div class="quickforge-react-settings-tab h-full min-h-0"></div>`}mountReact(){let e=this.querySelector(`.quickforge-react-settings-tab`);!e||!this.renderReact||(this.root??=(0,Ot.createRoot)(e),this.root.render(this.renderReact()))}},X=`quickforge-react-settings-tab`;customElements.get(X)||customElements.define(X,Pt);function Z(e,t){let n=document.createElement(X);return n.tabName=e,n.renderReact=t,n}function Q({children:e}){return(0,Y.jsx)(`div`,{className:`quickforge-settings-stack h-full min-h-[30rem]`,children:(0,Y.jsx)(T.Suspense,{fallback:(0,Y.jsx)(`div`,{className:`quickforge-settings-note`,children:C(`loading`)}),children:e})})}function Ft(e){window.dispatchEvent(new CustomEvent(`quickforge:open-session-from-settings`,{detail:{sessionId:e}}))}function It(){return Z(C(`agentsTab`),()=>(0,Y.jsx)(Q,{children:(0,Y.jsx)(kt,{})}))}function Lt(){return Z(C(`skills`),()=>(0,Y.jsx)(Q,{children:(0,Y.jsx)(jt,{active:!0,scope:`global`,embedded:!0,onSaved:()=>void 0})}))}function Rt(){return Z(C(`mcpServers`),()=>(0,Y.jsx)(Q,{children:(0,Y.jsx)(At,{active:!0})}))}function zt(){return Z(C(`plugins`),()=>(0,Y.jsx)(Q,{children:(0,Y.jsx)(Mt,{})}))}function Bt(){return Z(C(`scheduledTasks`),()=>(0,Y.jsx)(Q,{children:(0,Y.jsx)(Nt,{onOpenSession:Ft})}))}function Vt(e){let t=[{key:`appearance`,tab:Ie(),getDescription:()=>C(`appearanceDescription`)},{key:`defaults`,tab:Ye(),getDescription:()=>C(`defaultOptionsDescription`)},{key:`customModels`,tab:Ne(e),getDescription:()=>C(`customModelsDescription`)},{key:`agents`,tab:It(),getDescription:()=>C(`agentsDescription`)},{key:`skills`,tab:Lt(),getDescription:()=>C(`globalSkillsDescription`)},{key:`mcp`,tab:Rt(),getDescription:void 0},{key:`plugins`,tab:zt(),getDescription:()=>C(`pluginsDescription`)},{key:`scheduledTasks`,tab:Bt(),getDescription:()=>C(`scheduledTasksDescription`)},{key:`projectCommands`,tab:St(),getDescription:()=>C(`projectCommandsDescription`)},{key:`backup`,tab:ct(),getDescription:()=>C(`backupRestoreDescription`)},{key:`archivedConversations`,tab:dt(),getDescription:()=>C(`archivedConversationsDescription`)},{key:`channels`,tab:Dt(),getDescription:()=>C(`channelsDescription`)},{key:`lanAccess`,tab:mt(),getDescription:()=>C(`lanAccessDescription`)},{key:`about`,tab:bt(),getDescription:()=>C(`aboutQuickForgeDescription`)}];return{items:[...t],tabs:t.map(e=>e.tab),indexOf:e=>t.findIndex(t=>t.key===e)}}var $={appearance:l,defaults:d,customModels:ee,agents:n,skills:t,mcp:s,plugins:a,scheduledTasks:re,projectCommands:te,backup:ne,archivedConversations:u,channels:o,lanAccess:i,about:ie};function Ht({tab:e}){let t=(0,T.useRef)(null);return(0,T.useEffect)(()=>{let n=t.current;if(n)return n.replaceChildren(e),()=>{e.parentNode===n&&n.removeChild(e)}},[e]),(0,Y.jsx)(`div`,{ref:t,className:`quickforge-settings-tab-host min-h-0 flex-1`})}function Ut({initialTab:e,customProvider:t,onBack:n}){let i=(0,T.useMemo)(()=>Vt(t),[t]),a=Math.max(0,i.indexOf(e)),[o,s]=(0,T.useState)(),c=o??a,l=i.items[c]??i.items[0],u=l?.getDescription?.(),d=l?$[l.key]:void 0;return(0,Y.jsxs)(`div`,{className:`flex h-screen min-h-0 bg-[var(--quickforge-sidebar-bg)] text-foreground`,children:[(0,Y.jsxs)(`aside`,{className:`relative z-10 hidden w-80 shrink-0 overflow-hidden bg-[var(--quickforge-sidebar-bg)] md:flex md:min-h-0 md:flex-col`,children:[(0,Y.jsx)(`div`,{className:`shrink-0 px-3 pb-1 pt-3`,children:(0,Y.jsxs)(`button`,{type:`button`,className:`group relative flex w-full items-center gap-2 overflow-hidden rounded-lg px-2 py-1.5 text-left text-muted-foreground/72 transition-[background-color,color,box-shadow] duration-160 ease-out hover:bg-[var(--quickforge-sidebar-hover-bg)] hover:text-foreground/86 hover:shadow-[0_8px_20px_-18px_rgb(15_23_42_/_0.35)]`,onClick:n,"aria-label":`返回工作区`,children:[(0,Y.jsx)(`span`,{className:`inline-flex size-6 shrink-0 items-center justify-center rounded-full text-muted-foreground/55 transition-colors group-hover:text-foreground/70`,children:(0,Y.jsx)(r,{className:`size-4`})}),(0,Y.jsx)(`span`,{className:`truncate text-sm leading-5`,children:`返回工作区`})]})}),(0,Y.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto px-3 pb-4 pt-4`,children:(0,Y.jsx)(`nav`,{className:`space-y-1`,"aria-label":C(`settings`),children:i.items.map((e,t)=>{let n=t===c,r=$[e.key];return(0,Y.jsxs)(`button`,{type:`button`,className:S(`group relative flex w-full items-center gap-2.5 overflow-hidden rounded-lg px-2 py-1.5 text-left text-sm leading-5 transition-[background-color,color,box-shadow] duration-160 ease-out`,n?`bg-[var(--quickforge-sidebar-active-bg)] font-medium text-foreground/92 shadow-[0_8px_22px_-20px_rgb(15_23_42_/_0.32)]`:`text-muted-foreground/76 hover:bg-[var(--quickforge-sidebar-hover-bg)] hover:text-foreground/90 hover:shadow-[0_8px_20px_-18px_rgb(15_23_42_/_0.35)]`),onClick:()=>s(t),"aria-current":n?`page`:void 0,children:[(0,Y.jsx)(`span`,{className:S(`inline-flex size-5 shrink-0 items-center justify-center transition-colors`,n?`text-foreground/72`:`text-muted-foreground/52 group-hover:text-foreground/68`),children:(0,Y.jsx)(r,{className:`size-4`,"aria-hidden":`true`})}),(0,Y.jsx)(`span`,{className:`truncate`,children:e.tab.getTabName()})]},e.key)})})})]}),(0,Y.jsxs)(`main`,{className:`flex min-w-0 flex-1 flex-col bg-[var(--quickforge-main-bg)] md:overflow-hidden md:rounded-l-2xl`,children:[(0,Y.jsxs)(`header`,{className:`flex h-14 shrink-0 items-center gap-2 border-b-[0.5px] border-[color-mix(in_oklab,var(--border)_34%,transparent)] px-3 pr-4 md:px-5`,children:[(0,Y.jsx)(se,{variant:`ghost`,size:`icon`,className:`md:hidden`,onClick:n,"aria-label":`返回工作区`,children:(0,Y.jsx)(r,{className:`size-4`})}),(0,Y.jsx)(`div`,{className:`min-w-0 flex-1`,children:(0,Y.jsxs)(`div`,{className:`flex min-w-0 items-center gap-2`,children:[d?(0,Y.jsx)(d,{className:`size-4 shrink-0 text-muted-foreground/65`,"aria-hidden":`true`}):null,(0,Y.jsx)(`div`,{className:`min-w-0 truncate text-sm font-medium text-foreground/90`,children:l?.tab.getTabName()}),u?(0,Y.jsx)(me,{label:u}):null]})})]}),(0,Y.jsx)(`div`,{className:`quickforge-settings-mobile-tabs flex shrink-0 gap-1 overflow-x-auto border-b-[0.5px] border-[color-mix(in_oklab,var(--border)_30%,transparent)] px-3 py-2 md:hidden`,children:i.items.map((e,t)=>{let n=t===c,r=$[e.key];return(0,Y.jsxs)(`button`,{type:`button`,className:S(`inline-flex shrink-0 items-center gap-1.5 rounded-full px-3 py-1.5 text-sm transition-colors`,n?`bg-muted text-foreground`:`text-muted-foreground hover:bg-muted/55 hover:text-foreground`),onClick:()=>s(t),children:[(0,Y.jsx)(r,{className:`size-3.5 shrink-0`,"aria-hidden":`true`}),(0,Y.jsx)(`span`,{children:e.tab.getTabName()})]},e.key)})}),(0,Y.jsx)(`section`,{className:`min-h-0 flex-1 overflow-y-auto px-4 py-5 md:px-8 md:py-7`,children:(0,Y.jsx)(`div`,{className:`mx-auto flex min-h-full w-full max-w-5xl flex-col`,children:l?(0,Y.jsx)(Ht,{tab:l.tab}):null})})]})]})}export{Ut as SettingsWorkspacePage};