@mariozechner/pi-web-ui 0.5.48 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ChatPanel.d.ts +1 -0
- package/dist/ChatPanel.d.ts.map +1 -1
- package/dist/ChatPanel.js +3 -2
- package/dist/ChatPanel.js.map +1 -1
- package/dist/agent/transports/ProviderTransport.d.ts +1 -1
- package/dist/agent/transports/ProviderTransport.d.ts.map +1 -1
- package/dist/agent/transports/ProviderTransport.js +5 -10
- package/dist/agent/transports/ProviderTransport.js.map +1 -1
- package/dist/app.css +4188 -2
- package/dist/components/AgentInterface.d.ts +1 -0
- package/dist/components/AgentInterface.d.ts.map +1 -1
- package/dist/components/AgentInterface.js +13 -3
- package/dist/components/AgentInterface.js.map +1 -1
- package/dist/components/AttachmentTile.d.ts.map +1 -1
- package/dist/components/AttachmentTile.js +2 -1
- package/dist/components/AttachmentTile.js.map +1 -1
- package/dist/components/ConsoleBlock.d.ts.map +1 -1
- package/dist/components/ConsoleBlock.js +2 -1
- package/dist/components/ConsoleBlock.js.map +1 -1
- package/dist/components/CustomProviderCard.d.ts +17 -0
- package/dist/components/CustomProviderCard.d.ts.map +1 -0
- package/dist/components/CustomProviderCard.js +110 -0
- package/dist/components/CustomProviderCard.js.map +1 -0
- package/dist/components/Input.d.ts +2 -2
- package/dist/components/Input.d.ts.map +1 -1
- package/dist/components/Input.js +2 -1
- package/dist/components/Input.js.map +1 -1
- package/dist/components/MessageEditor.d.ts +1 -3
- package/dist/components/MessageEditor.d.ts.map +1 -1
- package/dist/components/MessageEditor.js +6 -31
- package/dist/components/MessageEditor.js.map +1 -1
- package/dist/components/MessageList.d.ts +1 -0
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +6 -3
- package/dist/components/MessageList.js.map +1 -1
- package/dist/components/Messages.d.ts +2 -0
- package/dist/components/Messages.d.ts.map +1 -1
- package/dist/components/Messages.js +25 -14
- package/dist/components/Messages.js.map +1 -1
- package/dist/components/ProviderKeyInput.d.ts +1 -1
- package/dist/components/ProviderKeyInput.d.ts.map +1 -1
- package/dist/components/ProviderKeyInput.js +22 -36
- package/dist/components/ProviderKeyInput.js.map +1 -1
- package/dist/components/StreamingMessageContainer.d.ts +1 -0
- package/dist/components/StreamingMessageContainer.d.ts.map +1 -1
- package/dist/components/StreamingMessageContainer.js +5 -2
- package/dist/components/StreamingMessageContainer.js.map +1 -1
- package/dist/components/ThinkingBlock.d.ts +11 -0
- package/dist/components/ThinkingBlock.d.ts.map +1 -0
- package/dist/components/ThinkingBlock.js +58 -0
- package/dist/components/ThinkingBlock.js.map +1 -0
- package/dist/dialogs/ApiKeyPromptDialog.d.ts +1 -1
- package/dist/dialogs/ApiKeyPromptDialog.d.ts.map +1 -1
- package/dist/dialogs/ApiKeyPromptDialog.js +3 -1
- package/dist/dialogs/ApiKeyPromptDialog.js.map +1 -1
- package/dist/dialogs/AttachmentOverlay.d.ts.map +1 -1
- package/dist/dialogs/AttachmentOverlay.js +3 -2
- package/dist/dialogs/AttachmentOverlay.js.map +1 -1
- package/dist/dialogs/CustomProviderDialog.d.ts +25 -0
- package/dist/dialogs/CustomProviderDialog.d.ts.map +1 -0
- package/dist/dialogs/CustomProviderDialog.js +270 -0
- package/dist/dialogs/CustomProviderDialog.js.map +1 -0
- package/dist/dialogs/ModelSelector.d.ts +6 -6
- package/dist/dialogs/ModelSelector.d.ts.map +1 -1
- package/dist/dialogs/ModelSelector.js +60 -74
- package/dist/dialogs/ModelSelector.js.map +1 -1
- package/dist/dialogs/PersistentStorageDialog.d.ts +1 -1
- package/dist/dialogs/PersistentStorageDialog.d.ts.map +1 -1
- package/dist/dialogs/PersistentStorageDialog.js +4 -1
- package/dist/dialogs/PersistentStorageDialog.js.map +1 -1
- package/dist/dialogs/ProvidersModelsTab.d.ts +20 -0
- package/dist/dialogs/ProvidersModelsTab.d.ts.map +1 -0
- package/dist/dialogs/ProvidersModelsTab.js +191 -0
- package/dist/dialogs/ProvidersModelsTab.js.map +1 -0
- package/dist/dialogs/SessionListDialog.d.ts +1 -1
- package/dist/dialogs/SessionListDialog.d.ts.map +1 -1
- package/dist/dialogs/SessionListDialog.js +3 -1
- package/dist/dialogs/SessionListDialog.js.map +1 -1
- package/dist/dialogs/SettingsDialog.d.ts +1 -2
- package/dist/dialogs/SettingsDialog.d.ts.map +1 -1
- package/dist/dialogs/SettingsDialog.js +10 -3
- package/dist/dialogs/SettingsDialog.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/storage/app-storage.d.ts +3 -1
- package/dist/storage/app-storage.d.ts.map +1 -1
- package/dist/storage/app-storage.js +2 -1
- package/dist/storage/app-storage.js.map +1 -1
- package/dist/storage/stores/custom-providers-store.d.ts +25 -0
- package/dist/storage/stores/custom-providers-store.d.ts.map +1 -0
- package/dist/storage/stores/custom-providers-store.js +35 -0
- package/dist/storage/stores/custom-providers-store.js.map +1 -0
- package/dist/storage/stores/sessions-store.d.ts.map +1 -1
- package/dist/storage/stores/sessions-store.js +0 -1
- package/dist/storage/stores/sessions-store.js.map +1 -1
- package/dist/storage/types.d.ts +0 -2
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/tools/artifacts/ArtifactPill.d.ts +1 -1
- package/dist/tools/artifacts/ArtifactPill.d.ts.map +1 -1
- package/dist/tools/artifacts/ArtifactPill.js +2 -1
- package/dist/tools/artifacts/ArtifactPill.js.map +1 -1
- package/dist/tools/artifacts/DocxArtifact.js +1 -1
- package/dist/tools/artifacts/DocxArtifact.js.map +1 -1
- package/dist/tools/artifacts/ExcelArtifact.js +1 -1
- package/dist/tools/artifacts/ExcelArtifact.js.map +1 -1
- package/dist/tools/artifacts/GenericArtifact.js +1 -1
- package/dist/tools/artifacts/GenericArtifact.js.map +1 -1
- package/dist/tools/artifacts/HtmlArtifact.d.ts.map +1 -1
- package/dist/tools/artifacts/HtmlArtifact.js +5 -1
- package/dist/tools/artifacts/HtmlArtifact.js.map +1 -1
- package/dist/tools/artifacts/ImageArtifact.js +1 -1
- package/dist/tools/artifacts/ImageArtifact.js.map +1 -1
- package/dist/tools/artifacts/MarkdownArtifact.d.ts.map +1 -1
- package/dist/tools/artifacts/MarkdownArtifact.js +3 -1
- package/dist/tools/artifacts/MarkdownArtifact.js.map +1 -1
- package/dist/tools/artifacts/PdfArtifact.js +1 -1
- package/dist/tools/artifacts/PdfArtifact.js.map +1 -1
- package/dist/tools/artifacts/SvgArtifact.d.ts.map +1 -1
- package/dist/tools/artifacts/SvgArtifact.js +3 -1
- package/dist/tools/artifacts/SvgArtifact.js.map +1 -1
- package/dist/tools/artifacts/TextArtifact.d.ts.map +1 -1
- package/dist/tools/artifacts/TextArtifact.js +2 -1
- package/dist/tools/artifacts/TextArtifact.js.map +1 -1
- package/dist/tools/artifacts/artifacts-tool-renderer.d.ts.map +1 -1
- package/dist/tools/artifacts/artifacts-tool-renderer.js +18 -8
- package/dist/tools/artifacts/artifacts-tool-renderer.js.map +1 -1
- package/dist/tools/artifacts/artifacts.d.ts.map +1 -1
- package/dist/tools/artifacts/artifacts.js +3 -2
- package/dist/tools/artifacts/artifacts.js.map +1 -1
- package/dist/tools/extract-document.d.ts.map +1 -1
- package/dist/tools/extract-document.js +78 -58
- package/dist/tools/extract-document.js.map +1 -1
- package/dist/tools/javascript-repl.d.ts.map +1 -1
- package/dist/tools/javascript-repl.js +7 -3
- package/dist/tools/javascript-repl.js.map +1 -1
- package/dist/tools/renderer-registry.d.ts +1 -1
- package/dist/tools/renderer-registry.d.ts.map +1 -1
- package/dist/tools/renderer-registry.js +20 -6
- package/dist/tools/renderer-registry.js.map +1 -1
- package/dist/tools/renderers/BashRenderer.d.ts.map +1 -1
- package/dist/tools/renderers/BashRenderer.js +5 -2
- package/dist/tools/renderers/BashRenderer.js.map +1 -1
- package/dist/tools/renderers/CalculateRenderer.d.ts.map +1 -1
- package/dist/tools/renderers/CalculateRenderer.js +5 -2
- package/dist/tools/renderers/CalculateRenderer.js.map +1 -1
- package/dist/tools/renderers/DefaultRenderer.d.ts.map +1 -1
- package/dist/tools/renderers/DefaultRenderer.js +5 -2
- package/dist/tools/renderers/DefaultRenderer.js.map +1 -1
- package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts.map +1 -1
- package/dist/tools/renderers/GetCurrentTimeRenderer.js +9 -3
- package/dist/tools/renderers/GetCurrentTimeRenderer.js.map +1 -1
- package/dist/utils/auth-token.js +1 -1
- package/dist/utils/auth-token.js.map +1 -1
- package/dist/utils/i18n.d.ts +105 -3
- package/dist/utils/i18n.d.ts.map +1 -1
- package/dist/utils/i18n.js +72 -2
- package/dist/utils/i18n.js.map +1 -1
- package/dist/utils/model-discovery.d.ts +38 -0
- package/dist/utils/model-discovery.d.ts.map +1 -0
- package/dist/utils/model-discovery.js +243 -0
- package/dist/utils/model-discovery.js.map +1 -0
- package/dist/utils/proxy-utils.d.ts +37 -0
- package/dist/utils/proxy-utils.d.ts.map +1 -0
- package/dist/utils/proxy-utils.js +97 -0
- package/dist/utils/proxy-utils.js.map +1 -0
- package/example/package.json +2 -2
- package/example/src/custom-messages.ts +1 -1
- package/example/src/main.ts +17 -6
- package/package.json +9 -8
- package/src/ChatPanel.ts +4 -2
- package/src/agent/transports/ProviderTransport.ts +5 -10
- package/src/app.css +24 -0
- package/src/components/AgentInterface.ts +14 -3
- package/src/components/AttachmentTile.ts +2 -1
- package/src/components/ConsoleBlock.ts +2 -1
- package/src/components/CustomProviderCard.ts +100 -0
- package/src/components/Input.ts +2 -1
- package/src/components/MessageEditor.ts +6 -33
- package/src/components/MessageList.ts +4 -3
- package/src/components/Messages.ts +32 -20
- package/src/components/ProviderKeyInput.ts +19 -38
- package/src/components/StreamingMessageContainer.ts +3 -2
- package/src/components/ThinkingBlock.ts +43 -0
- package/src/dialogs/ApiKeyPromptDialog.ts +3 -1
- package/src/dialogs/AttachmentOverlay.ts +3 -2
- package/src/dialogs/CustomProviderDialog.ts +274 -0
- package/src/dialogs/ModelSelector.ts +61 -75
- package/src/dialogs/PersistentStorageDialog.ts +4 -1
- package/src/dialogs/ProvidersModelsTab.ts +212 -0
- package/src/dialogs/SessionListDialog.ts +3 -1
- package/src/dialogs/SettingsDialog.ts +10 -13
- package/src/index.ts +8 -0
- package/src/storage/app-storage.ts +4 -0
- package/src/storage/stores/custom-providers-store.ts +62 -0
- package/src/storage/stores/sessions-store.ts +0 -1
- package/src/storage/types.ts +0 -3
- package/src/tools/artifacts/ArtifactPill.ts +2 -1
- package/src/tools/artifacts/DocxArtifact.ts +1 -1
- package/src/tools/artifacts/ExcelArtifact.ts +1 -1
- package/src/tools/artifacts/GenericArtifact.ts +1 -1
- package/src/tools/artifacts/HtmlArtifact.ts +5 -1
- package/src/tools/artifacts/ImageArtifact.ts +1 -1
- package/src/tools/artifacts/MarkdownArtifact.ts +3 -1
- package/src/tools/artifacts/PdfArtifact.ts +1 -1
- package/src/tools/artifacts/SvgArtifact.ts +3 -1
- package/src/tools/artifacts/TextArtifact.ts +2 -1
- package/src/tools/artifacts/artifacts-tool-renderer.ts +20 -8
- package/src/tools/artifacts/artifacts.ts +3 -2
- package/src/tools/extract-document.ts +82 -61
- package/src/tools/javascript-repl.ts +8 -3
- package/src/tools/renderer-registry.ts +20 -6
- package/src/tools/renderers/BashRenderer.ts +6 -2
- package/src/tools/renderers/CalculateRenderer.ts +6 -2
- package/src/tools/renderers/DefaultRenderer.ts +6 -2
- package/src/tools/renderers/GetCurrentTimeRenderer.ts +11 -3
- package/src/utils/auth-token.ts +1 -1
- package/src/utils/i18n.ts +120 -5
- package/src/utils/model-discovery.ts +277 -0
- package/src/utils/proxy-utils.ts +112 -0
- package/example/package-lock.json +0 -1965
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { i18n } from "@mariozechner/mini-lit";
|
|
2
|
+
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
|
3
|
+
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
|
4
|
+
import { Input } from "@mariozechner/mini-lit/dist/Input.js";
|
|
5
|
+
import { Label } from "@mariozechner/mini-lit/dist/Label.js";
|
|
6
|
+
import { Select } from "@mariozechner/mini-lit/dist/Select.js";
|
|
7
|
+
import type { Model } from "@mariozechner/pi-ai";
|
|
8
|
+
import { html, type TemplateResult } from "lit";
|
|
9
|
+
import { state } from "lit/decorators.js";
|
|
10
|
+
import { getAppStorage } from "../storage/app-storage.js";
|
|
11
|
+
import type { CustomProvider, CustomProviderType } from "../storage/stores/custom-providers-store.js";
|
|
12
|
+
import { discoverModels } from "../utils/model-discovery.js";
|
|
13
|
+
|
|
14
|
+
export class CustomProviderDialog extends DialogBase {
|
|
15
|
+
private provider?: CustomProvider;
|
|
16
|
+
private initialType?: CustomProviderType;
|
|
17
|
+
private onSaveCallback?: () => void;
|
|
18
|
+
|
|
19
|
+
@state() private name = "";
|
|
20
|
+
@state() private type: CustomProviderType = "openai-completions";
|
|
21
|
+
@state() private baseUrl = "";
|
|
22
|
+
@state() private apiKey = "";
|
|
23
|
+
@state() private testing = false;
|
|
24
|
+
@state() private testError = "";
|
|
25
|
+
@state() private discoveredModels: Model<any>[] = [];
|
|
26
|
+
|
|
27
|
+
protected modalWidth = "min(800px, 90vw)";
|
|
28
|
+
protected modalHeight = "min(700px, 90vh)";
|
|
29
|
+
|
|
30
|
+
static async open(
|
|
31
|
+
provider: CustomProvider | undefined,
|
|
32
|
+
initialType: CustomProviderType | undefined,
|
|
33
|
+
onSave?: () => void,
|
|
34
|
+
) {
|
|
35
|
+
const dialog = new CustomProviderDialog();
|
|
36
|
+
dialog.provider = provider;
|
|
37
|
+
dialog.initialType = initialType;
|
|
38
|
+
dialog.onSaveCallback = onSave;
|
|
39
|
+
document.body.appendChild(dialog);
|
|
40
|
+
dialog.initializeFromProvider();
|
|
41
|
+
dialog.open();
|
|
42
|
+
dialog.requestUpdate();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private initializeFromProvider() {
|
|
46
|
+
if (this.provider) {
|
|
47
|
+
this.name = this.provider.name;
|
|
48
|
+
this.type = this.provider.type;
|
|
49
|
+
this.baseUrl = this.provider.baseUrl;
|
|
50
|
+
this.apiKey = this.provider.apiKey || "";
|
|
51
|
+
this.discoveredModels = this.provider.models || [];
|
|
52
|
+
} else {
|
|
53
|
+
this.name = "";
|
|
54
|
+
this.type = this.initialType || "openai-completions";
|
|
55
|
+
this.baseUrl = "";
|
|
56
|
+
this.updateDefaultBaseUrl();
|
|
57
|
+
this.apiKey = "";
|
|
58
|
+
this.discoveredModels = [];
|
|
59
|
+
}
|
|
60
|
+
this.testError = "";
|
|
61
|
+
this.testing = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private updateDefaultBaseUrl() {
|
|
65
|
+
if (this.baseUrl) return;
|
|
66
|
+
|
|
67
|
+
const defaults: Record<string, string> = {
|
|
68
|
+
ollama: "http://localhost:11434",
|
|
69
|
+
"llama.cpp": "http://localhost:8080",
|
|
70
|
+
vllm: "http://localhost:8000",
|
|
71
|
+
lmstudio: "http://localhost:1234",
|
|
72
|
+
"openai-completions": "",
|
|
73
|
+
"openai-responses": "",
|
|
74
|
+
"anthropic-messages": "",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
this.baseUrl = defaults[this.type] || "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private isAutoDiscoveryType(): boolean {
|
|
81
|
+
return this.type === "ollama" || this.type === "llama.cpp" || this.type === "vllm" || this.type === "lmstudio";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async testConnection() {
|
|
85
|
+
if (!this.isAutoDiscoveryType()) return;
|
|
86
|
+
|
|
87
|
+
this.testing = true;
|
|
88
|
+
this.testError = "";
|
|
89
|
+
this.discoveredModels = [];
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const models = await discoverModels(
|
|
93
|
+
this.type as "ollama" | "llama.cpp" | "vllm" | "lmstudio",
|
|
94
|
+
this.baseUrl,
|
|
95
|
+
this.apiKey || undefined,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
this.discoveredModels = models.map((model) => ({
|
|
99
|
+
...model,
|
|
100
|
+
provider: this.name || this.type,
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
this.testError = "";
|
|
104
|
+
} catch (error) {
|
|
105
|
+
this.testError = error instanceof Error ? error.message : String(error);
|
|
106
|
+
this.discoveredModels = [];
|
|
107
|
+
} finally {
|
|
108
|
+
this.testing = false;
|
|
109
|
+
this.requestUpdate();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private async save() {
|
|
114
|
+
if (!this.name || !this.baseUrl) {
|
|
115
|
+
alert(i18n("Please fill in all required fields"));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const storage = getAppStorage();
|
|
121
|
+
|
|
122
|
+
const provider: CustomProvider = {
|
|
123
|
+
id: this.provider?.id || crypto.randomUUID(),
|
|
124
|
+
name: this.name,
|
|
125
|
+
type: this.type,
|
|
126
|
+
baseUrl: this.baseUrl,
|
|
127
|
+
apiKey: this.apiKey || undefined,
|
|
128
|
+
models: this.isAutoDiscoveryType() ? undefined : this.provider?.models || [],
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
await storage.customProviders.set(provider);
|
|
132
|
+
|
|
133
|
+
if (this.onSaveCallback) {
|
|
134
|
+
this.onSaveCallback();
|
|
135
|
+
}
|
|
136
|
+
this.close();
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error("Failed to save provider:", error);
|
|
139
|
+
alert(i18n("Failed to save provider"));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
protected override renderContent(): TemplateResult {
|
|
144
|
+
const providerTypes = [
|
|
145
|
+
{ value: "ollama", label: "Ollama (auto-discovery)" },
|
|
146
|
+
{ value: "llama.cpp", label: "llama.cpp (auto-discovery)" },
|
|
147
|
+
{ value: "vllm", label: "vLLM (auto-discovery)" },
|
|
148
|
+
{ value: "lmstudio", label: "LM Studio (auto-discovery)" },
|
|
149
|
+
{ value: "openai-completions", label: "OpenAI Completions Compatible" },
|
|
150
|
+
{ value: "openai-responses", label: "OpenAI Responses Compatible" },
|
|
151
|
+
{ value: "anthropic-messages", label: "Anthropic Messages Compatible" },
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
return html`
|
|
155
|
+
<div class="flex flex-col h-full overflow-hidden">
|
|
156
|
+
<div class="p-6 flex-shrink-0 border-b border-border">
|
|
157
|
+
<h2 class="text-lg font-semibold text-foreground">
|
|
158
|
+
${this.provider ? i18n("Edit Provider") : i18n("Add Provider")}
|
|
159
|
+
</h2>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div class="flex-1 overflow-y-auto p-6">
|
|
163
|
+
<div class="flex flex-col gap-4">
|
|
164
|
+
<div class="flex flex-col gap-2">
|
|
165
|
+
${Label({ htmlFor: "provider-name", children: i18n("Provider Name") })}
|
|
166
|
+
${Input({
|
|
167
|
+
value: this.name,
|
|
168
|
+
placeholder: i18n("e.g., My Ollama Server"),
|
|
169
|
+
onInput: (e: Event) => {
|
|
170
|
+
this.name = (e.target as HTMLInputElement).value;
|
|
171
|
+
this.requestUpdate();
|
|
172
|
+
},
|
|
173
|
+
})}
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div class="flex flex-col gap-2">
|
|
177
|
+
${Label({ htmlFor: "provider-type", children: i18n("Provider Type") })}
|
|
178
|
+
${Select({
|
|
179
|
+
value: this.type,
|
|
180
|
+
options: providerTypes.map((pt) => ({
|
|
181
|
+
value: pt.value,
|
|
182
|
+
label: pt.label,
|
|
183
|
+
})),
|
|
184
|
+
onChange: (value: string) => {
|
|
185
|
+
this.type = value as CustomProviderType;
|
|
186
|
+
this.baseUrl = "";
|
|
187
|
+
this.updateDefaultBaseUrl();
|
|
188
|
+
this.requestUpdate();
|
|
189
|
+
},
|
|
190
|
+
width: "100%",
|
|
191
|
+
})}
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<div class="flex flex-col gap-2">
|
|
195
|
+
${Label({ htmlFor: "base-url", children: i18n("Base URL") })}
|
|
196
|
+
${Input({
|
|
197
|
+
value: this.baseUrl,
|
|
198
|
+
placeholder: i18n("e.g., http://localhost:11434"),
|
|
199
|
+
onInput: (e: Event) => {
|
|
200
|
+
this.baseUrl = (e.target as HTMLInputElement).value;
|
|
201
|
+
this.requestUpdate();
|
|
202
|
+
},
|
|
203
|
+
})}
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div class="flex flex-col gap-2">
|
|
207
|
+
${Label({ htmlFor: "api-key", children: i18n("API Key (Optional)") })}
|
|
208
|
+
${Input({
|
|
209
|
+
type: "password",
|
|
210
|
+
value: this.apiKey,
|
|
211
|
+
placeholder: i18n("Leave empty if not required"),
|
|
212
|
+
onInput: (e: Event) => {
|
|
213
|
+
this.apiKey = (e.target as HTMLInputElement).value;
|
|
214
|
+
this.requestUpdate();
|
|
215
|
+
},
|
|
216
|
+
})}
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
${
|
|
220
|
+
this.isAutoDiscoveryType()
|
|
221
|
+
? html`
|
|
222
|
+
<div class="flex flex-col gap-2">
|
|
223
|
+
${Button({
|
|
224
|
+
onClick: () => this.testConnection(),
|
|
225
|
+
variant: "outline",
|
|
226
|
+
disabled: this.testing || !this.baseUrl,
|
|
227
|
+
children: this.testing ? i18n("Testing...") : i18n("Test Connection"),
|
|
228
|
+
})}
|
|
229
|
+
${this.testError ? html` <div class="text-sm text-destructive">${this.testError}</div> ` : ""}
|
|
230
|
+
${
|
|
231
|
+
this.discoveredModels.length > 0
|
|
232
|
+
? html`
|
|
233
|
+
<div class="text-sm text-muted-foreground">
|
|
234
|
+
${i18n("Discovered")} ${this.discoveredModels.length} ${i18n("models")}:
|
|
235
|
+
<ul class="list-disc list-inside mt-2">
|
|
236
|
+
${this.discoveredModels.slice(0, 5).map((model) => html`<li>${model.name}</li>`)}
|
|
237
|
+
${
|
|
238
|
+
this.discoveredModels.length > 5
|
|
239
|
+
? html`<li>...${i18n("and")} ${this.discoveredModels.length - 5} ${i18n("more")}</li>`
|
|
240
|
+
: ""
|
|
241
|
+
}
|
|
242
|
+
</ul>
|
|
243
|
+
</div>
|
|
244
|
+
`
|
|
245
|
+
: ""
|
|
246
|
+
}
|
|
247
|
+
</div>
|
|
248
|
+
`
|
|
249
|
+
: html` <div class="text-sm text-muted-foreground">
|
|
250
|
+
${i18n("For manual provider types, add models after saving the provider.")}
|
|
251
|
+
</div>`
|
|
252
|
+
}
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<div class="p-6 flex-shrink-0 border-t border-border flex justify-end gap-2">
|
|
257
|
+
${Button({
|
|
258
|
+
onClick: () => this.close(),
|
|
259
|
+
variant: "ghost",
|
|
260
|
+
children: i18n("Cancel"),
|
|
261
|
+
})}
|
|
262
|
+
${Button({
|
|
263
|
+
onClick: () => this.save(),
|
|
264
|
+
variant: "default",
|
|
265
|
+
disabled: !this.name || !this.baseUrl,
|
|
266
|
+
children: i18n("Save"),
|
|
267
|
+
})}
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
`;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
customElements.define("custom-provider-dialog", CustomProviderDialog);
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
1
|
+
import { icon } from "@mariozechner/mini-lit";
|
|
2
|
+
import { Badge } from "@mariozechner/mini-lit/dist/Badge.js";
|
|
3
|
+
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
|
4
|
+
import { DialogHeader } from "@mariozechner/mini-lit/dist/Dialog.js";
|
|
5
|
+
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
|
6
|
+
import { getModels, getProviders, type Model } from "@mariozechner/pi-ai";
|
|
7
|
+
import { html, type PropertyValues, type TemplateResult } from "lit";
|
|
5
8
|
import { customElement, state } from "lit/decorators.js";
|
|
6
9
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
7
10
|
import { Brain, Image as ImageIcon } from "lucide";
|
|
8
|
-
import { Ollama } from "ollama/dist/browser.mjs";
|
|
9
11
|
import { Input } from "../components/Input.js";
|
|
12
|
+
import { getAppStorage } from "../storage/app-storage.js";
|
|
13
|
+
import type { AutoDiscoveryProviderType } from "../storage/stores/custom-providers-store.js";
|
|
10
14
|
import { formatModelCost } from "../utils/format.js";
|
|
11
15
|
import { i18n } from "../utils/i18n.js";
|
|
16
|
+
import { discoverModels } from "../utils/model-discovery.js";
|
|
12
17
|
|
|
13
18
|
@customElement("agent-model-selector")
|
|
14
19
|
export class ModelSelector extends DialogBase {
|
|
@@ -16,10 +21,10 @@ export class ModelSelector extends DialogBase {
|
|
|
16
21
|
@state() searchQuery = "";
|
|
17
22
|
@state() filterThinking = false;
|
|
18
23
|
@state() filterVision = false;
|
|
19
|
-
@state()
|
|
20
|
-
@state() ollamaError: string | null = null;
|
|
24
|
+
@state() customProvidersLoading = false;
|
|
21
25
|
@state() selectedIndex = 0;
|
|
22
26
|
@state() private navigationMode: "mouse" | "keyboard" = "mouse";
|
|
27
|
+
@state() private customProviderModels: Model<any>[] = [];
|
|
23
28
|
|
|
24
29
|
private onSelectCallback?: (model: Model<any>) => void;
|
|
25
30
|
private scrollContainerRef = createRef<HTMLDivElement>();
|
|
@@ -33,7 +38,7 @@ export class ModelSelector extends DialogBase {
|
|
|
33
38
|
selector.currentModel = currentModel;
|
|
34
39
|
selector.onSelectCallback = onSelect;
|
|
35
40
|
selector.open();
|
|
36
|
-
selector.
|
|
41
|
+
selector.loadCustomProviders();
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
override async firstUpdated(changedProperties: PropertyValues): Promise<void> {
|
|
@@ -91,67 +96,50 @@ export class ModelSelector extends DialogBase {
|
|
|
91
96
|
});
|
|
92
97
|
}
|
|
93
98
|
|
|
94
|
-
private async
|
|
99
|
+
private async loadCustomProviders() {
|
|
100
|
+
this.customProvidersLoading = true;
|
|
101
|
+
const allCustomModels: Model<any>[] = [];
|
|
102
|
+
|
|
95
103
|
try {
|
|
96
|
-
|
|
97
|
-
const
|
|
104
|
+
const storage = getAppStorage();
|
|
105
|
+
const customProviders = await storage.customProviders.getAll();
|
|
98
106
|
|
|
99
|
-
//
|
|
100
|
-
const
|
|
107
|
+
// Load models from custom providers
|
|
108
|
+
for (const provider of customProviders) {
|
|
109
|
+
const isAutoDiscovery: boolean =
|
|
110
|
+
provider.type === "ollama" ||
|
|
111
|
+
provider.type === "llama.cpp" ||
|
|
112
|
+
provider.type === "vllm" ||
|
|
113
|
+
provider.type === "lmstudio";
|
|
101
114
|
|
|
102
|
-
|
|
103
|
-
const ollamaModelPromises: Promise<Model<any> | null>[] = models
|
|
104
|
-
.map(async (model: any) => {
|
|
115
|
+
if (isAutoDiscovery) {
|
|
105
116
|
try {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// Some Ollama servers don't report capabilities; don't filter on them
|
|
112
|
-
|
|
113
|
-
// Extract model info
|
|
114
|
-
const modelInfo: any = details.model_info || {};
|
|
117
|
+
const models = await discoverModels(
|
|
118
|
+
provider.type as AutoDiscoveryProviderType,
|
|
119
|
+
provider.baseUrl,
|
|
120
|
+
provider.apiKey,
|
|
121
|
+
);
|
|
115
122
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const maxTokens = 4096; // Default max output tokens
|
|
123
|
+
const modelsWithProvider = models.map((model) => ({
|
|
124
|
+
...model,
|
|
125
|
+
provider: provider.name,
|
|
126
|
+
}));
|
|
121
127
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
name: model.name,
|
|
126
|
-
api: "openai-completions" as any,
|
|
127
|
-
provider: "ollama",
|
|
128
|
-
baseUrl: "http://localhost:11434/v1",
|
|
129
|
-
reasoning: false,
|
|
130
|
-
input: ["text"],
|
|
131
|
-
cost: {
|
|
132
|
-
input: 0,
|
|
133
|
-
output: 0,
|
|
134
|
-
cacheRead: 0,
|
|
135
|
-
cacheWrite: 0,
|
|
136
|
-
},
|
|
137
|
-
contextWindow: contextWindow,
|
|
138
|
-
maxTokens: maxTokens,
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
return ollamaModel;
|
|
142
|
-
} catch (err) {
|
|
143
|
-
console.error(`Failed to fetch details for model ${model.name}:`, err);
|
|
144
|
-
return null;
|
|
128
|
+
allCustomModels.push(...modelsWithProvider);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.debug(`Failed to load models from ${provider.name}:`, error);
|
|
145
131
|
}
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
} catch (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
this.
|
|
132
|
+
} else if (provider.models) {
|
|
133
|
+
// Manual provider - models already defined
|
|
134
|
+
allCustomModels.push(...provider.models);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error("Failed to load custom providers:", error);
|
|
139
|
+
} finally {
|
|
140
|
+
this.customProviderModels = allCustomModels;
|
|
141
|
+
this.customProvidersLoading = false;
|
|
142
|
+
this.requestUpdate();
|
|
155
143
|
}
|
|
156
144
|
}
|
|
157
145
|
|
|
@@ -169,21 +157,20 @@ export class ModelSelector extends DialogBase {
|
|
|
169
157
|
}
|
|
170
158
|
|
|
171
159
|
private getFilteredModels(): Array<{ provider: string; id: string; model: any }> {
|
|
172
|
-
// Collect all models from
|
|
160
|
+
// Collect all models from known providers
|
|
173
161
|
const allModels: Array<{ provider: string; id: string; model: any }> = [];
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
162
|
+
const knownProviders = getProviders();
|
|
163
|
+
|
|
164
|
+
for (const provider of knownProviders) {
|
|
165
|
+
const models = getModels(provider as any);
|
|
166
|
+
for (const model of models) {
|
|
167
|
+
allModels.push({ provider, id: model.id, model });
|
|
177
168
|
}
|
|
178
169
|
}
|
|
179
170
|
|
|
180
|
-
// Add
|
|
181
|
-
for (const
|
|
182
|
-
allModels.push({
|
|
183
|
-
id: ollamaModel.id,
|
|
184
|
-
provider: "ollama",
|
|
185
|
-
model: ollamaModel,
|
|
186
|
-
});
|
|
171
|
+
// Add custom provider models
|
|
172
|
+
for (const model of this.customProviderModels) {
|
|
173
|
+
allModels.push({ provider: model.provider, id: model.id, model });
|
|
187
174
|
}
|
|
188
175
|
|
|
189
176
|
// Filter models based on search and capability filters
|
|
@@ -283,8 +270,7 @@ export class ModelSelector extends DialogBase {
|
|
|
283
270
|
<!-- Scrollable model list -->
|
|
284
271
|
<div class="flex-1 overflow-y-auto" ${ref(this.scrollContainerRef)}>
|
|
285
272
|
${filteredModels.map(({ provider, id, model }, index) => {
|
|
286
|
-
|
|
287
|
-
const isCurrent = this.currentModel?.id === model.id;
|
|
273
|
+
const isCurrent = this.currentModel?.id === model.id && this.currentModel?.provider === model.provider;
|
|
288
274
|
const isSelected = index === this.selectedIndex;
|
|
289
275
|
return html`
|
|
290
276
|
<div
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { Button
|
|
1
|
+
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
|
2
|
+
import { DialogContent, DialogHeader } from "@mariozechner/mini-lit/dist/Dialog.js";
|
|
3
|
+
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
|
4
|
+
import { html } from "lit";
|
|
2
5
|
import { customElement, state } from "lit/decorators.js";
|
|
3
6
|
import { i18n } from "../utils/i18n.js";
|
|
4
7
|
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { i18n } from "@mariozechner/mini-lit";
|
|
2
|
+
import { Select } from "@mariozechner/mini-lit/dist/Select.js";
|
|
3
|
+
import { getProviders } from "@mariozechner/pi-ai";
|
|
4
|
+
import { html, type TemplateResult } from "lit";
|
|
5
|
+
import { customElement, state } from "lit/decorators.js";
|
|
6
|
+
import "../components/CustomProviderCard.js";
|
|
7
|
+
import "../components/ProviderKeyInput.js";
|
|
8
|
+
import { getAppStorage } from "../storage/app-storage.js";
|
|
9
|
+
import type {
|
|
10
|
+
AutoDiscoveryProviderType,
|
|
11
|
+
CustomProvider,
|
|
12
|
+
CustomProviderType,
|
|
13
|
+
} from "../storage/stores/custom-providers-store.js";
|
|
14
|
+
import { discoverModels } from "../utils/model-discovery.js";
|
|
15
|
+
import { CustomProviderDialog } from "./CustomProviderDialog.js";
|
|
16
|
+
import { SettingsTab } from "./SettingsDialog.js";
|
|
17
|
+
|
|
18
|
+
@customElement("providers-models-tab")
|
|
19
|
+
export class ProvidersModelsTab extends SettingsTab {
|
|
20
|
+
@state() private customProviders: CustomProvider[] = [];
|
|
21
|
+
@state() private providerStatus: Map<
|
|
22
|
+
string,
|
|
23
|
+
{ modelCount: number; status: "connected" | "disconnected" | "checking" }
|
|
24
|
+
> = new Map();
|
|
25
|
+
|
|
26
|
+
override async connectedCallback() {
|
|
27
|
+
super.connectedCallback();
|
|
28
|
+
await this.loadCustomProviders();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private async loadCustomProviders() {
|
|
32
|
+
try {
|
|
33
|
+
const storage = getAppStorage();
|
|
34
|
+
this.customProviders = await storage.customProviders.getAll();
|
|
35
|
+
|
|
36
|
+
// Check status for auto-discovery providers
|
|
37
|
+
for (const provider of this.customProviders) {
|
|
38
|
+
const isAutoDiscovery =
|
|
39
|
+
provider.type === "ollama" ||
|
|
40
|
+
provider.type === "llama.cpp" ||
|
|
41
|
+
provider.type === "vllm" ||
|
|
42
|
+
provider.type === "lmstudio";
|
|
43
|
+
if (isAutoDiscovery) {
|
|
44
|
+
this.checkProviderStatus(provider);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error("Failed to load custom providers:", error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getTabName(): string {
|
|
53
|
+
return "Providers & Models";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private async checkProviderStatus(provider: CustomProvider) {
|
|
57
|
+
this.providerStatus.set(provider.id, { modelCount: 0, status: "checking" });
|
|
58
|
+
this.requestUpdate();
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const models = await discoverModels(
|
|
62
|
+
provider.type as AutoDiscoveryProviderType,
|
|
63
|
+
provider.baseUrl,
|
|
64
|
+
provider.apiKey,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
this.providerStatus.set(provider.id, { modelCount: models.length, status: "connected" });
|
|
68
|
+
} catch (error) {
|
|
69
|
+
this.providerStatus.set(provider.id, { modelCount: 0, status: "disconnected" });
|
|
70
|
+
}
|
|
71
|
+
this.requestUpdate();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private renderKnownProviders(): TemplateResult {
|
|
75
|
+
const providers = getProviders();
|
|
76
|
+
|
|
77
|
+
return html`
|
|
78
|
+
<div class="flex flex-col gap-6">
|
|
79
|
+
<div>
|
|
80
|
+
<h3 class="text-sm font-semibold text-foreground mb-2">Cloud Providers</h3>
|
|
81
|
+
<p class="text-sm text-muted-foreground mb-4">
|
|
82
|
+
Cloud LLM providers with predefined models. API keys are stored locally in your browser.
|
|
83
|
+
</p>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="flex flex-col gap-6">
|
|
86
|
+
${providers.map((provider) => html` <provider-key-input .provider=${provider}></provider-key-input> `)}
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private renderCustomProviders(): TemplateResult {
|
|
93
|
+
const isAutoDiscovery = (type: string) =>
|
|
94
|
+
type === "ollama" || type === "llama.cpp" || type === "vllm" || type === "lmstudio";
|
|
95
|
+
|
|
96
|
+
return html`
|
|
97
|
+
<div class="flex flex-col gap-6">
|
|
98
|
+
<div class="flex items-center justify-between">
|
|
99
|
+
<div>
|
|
100
|
+
<h3 class="text-sm font-semibold text-foreground mb-2">Custom Providers</h3>
|
|
101
|
+
<p class="text-sm text-muted-foreground">
|
|
102
|
+
User-configured servers with auto-discovered or manually defined models.
|
|
103
|
+
</p>
|
|
104
|
+
</div>
|
|
105
|
+
${Select({
|
|
106
|
+
placeholder: i18n("Add Provider"),
|
|
107
|
+
options: [
|
|
108
|
+
{ value: "ollama", label: "Ollama" },
|
|
109
|
+
{ value: "llama.cpp", label: "llama.cpp" },
|
|
110
|
+
{ value: "vllm", label: "vLLM" },
|
|
111
|
+
{ value: "lmstudio", label: "LM Studio" },
|
|
112
|
+
{ value: "openai-completions", label: i18n("OpenAI Completions Compatible") },
|
|
113
|
+
{ value: "openai-responses", label: i18n("OpenAI Responses Compatible") },
|
|
114
|
+
{ value: "anthropic-messages", label: i18n("Anthropic Messages Compatible") },
|
|
115
|
+
],
|
|
116
|
+
onChange: (value: string) => this.addCustomProvider(value as CustomProviderType),
|
|
117
|
+
variant: "outline",
|
|
118
|
+
size: "sm",
|
|
119
|
+
})}
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
${
|
|
123
|
+
this.customProviders.length === 0
|
|
124
|
+
? html`
|
|
125
|
+
<div class="text-sm text-muted-foreground text-center py-8">
|
|
126
|
+
No custom providers configured. Click 'Add Provider' to get started.
|
|
127
|
+
</div>
|
|
128
|
+
`
|
|
129
|
+
: html`
|
|
130
|
+
<div class="flex flex-col gap-4">
|
|
131
|
+
${this.customProviders.map(
|
|
132
|
+
(provider) => html`
|
|
133
|
+
<custom-provider-card
|
|
134
|
+
.provider=${provider}
|
|
135
|
+
.isAutoDiscovery=${isAutoDiscovery(provider.type)}
|
|
136
|
+
.status=${this.providerStatus.get(provider.id)}
|
|
137
|
+
.onRefresh=${(p: CustomProvider) => this.refreshProvider(p)}
|
|
138
|
+
.onEdit=${(p: CustomProvider) => this.editProvider(p)}
|
|
139
|
+
.onDelete=${(p: CustomProvider) => this.deleteProvider(p)}
|
|
140
|
+
></custom-provider-card>
|
|
141
|
+
`,
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
`
|
|
145
|
+
}
|
|
146
|
+
</div>
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private async addCustomProvider(type: CustomProviderType) {
|
|
151
|
+
await CustomProviderDialog.open(undefined, type, async () => {
|
|
152
|
+
await this.loadCustomProviders();
|
|
153
|
+
this.requestUpdate();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async editProvider(provider: CustomProvider) {
|
|
158
|
+
await CustomProviderDialog.open(provider, undefined, async () => {
|
|
159
|
+
await this.loadCustomProviders();
|
|
160
|
+
this.requestUpdate();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private async refreshProvider(provider: CustomProvider) {
|
|
165
|
+
this.providerStatus.set(provider.id, { modelCount: 0, status: "checking" });
|
|
166
|
+
this.requestUpdate();
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const models = await discoverModels(
|
|
170
|
+
provider.type as AutoDiscoveryProviderType,
|
|
171
|
+
provider.baseUrl,
|
|
172
|
+
provider.apiKey,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
this.providerStatus.set(provider.id, { modelCount: models.length, status: "connected" });
|
|
176
|
+
this.requestUpdate();
|
|
177
|
+
|
|
178
|
+
console.log(`Refreshed ${models.length} models from ${provider.name}`);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
this.providerStatus.set(provider.id, { modelCount: 0, status: "disconnected" });
|
|
181
|
+
this.requestUpdate();
|
|
182
|
+
|
|
183
|
+
console.error(`Failed to refresh provider ${provider.name}:`, error);
|
|
184
|
+
alert(`Failed to refresh provider: ${error instanceof Error ? error.message : String(error)}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private async deleteProvider(provider: CustomProvider) {
|
|
189
|
+
if (!confirm("Are you sure you want to delete this provider?")) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const storage = getAppStorage();
|
|
195
|
+
await storage.customProviders.delete(provider.id);
|
|
196
|
+
await this.loadCustomProviders();
|
|
197
|
+
this.requestUpdate();
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error("Failed to delete provider:", error);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
render(): TemplateResult {
|
|
204
|
+
return html`
|
|
205
|
+
<div class="flex flex-col gap-8">
|
|
206
|
+
${this.renderKnownProviders()}
|
|
207
|
+
<div class="border-t border-border"></div>
|
|
208
|
+
${this.renderCustomProviders()}
|
|
209
|
+
</div>
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
}
|