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