@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.
Files changed (222) hide show
  1. package/dist/ChatPanel.d.ts +1 -0
  2. package/dist/ChatPanel.d.ts.map +1 -1
  3. package/dist/ChatPanel.js +3 -2
  4. package/dist/ChatPanel.js.map +1 -1
  5. package/dist/agent/transports/ProviderTransport.d.ts +1 -1
  6. package/dist/agent/transports/ProviderTransport.d.ts.map +1 -1
  7. package/dist/agent/transports/ProviderTransport.js +5 -10
  8. package/dist/agent/transports/ProviderTransport.js.map +1 -1
  9. package/dist/app.css +4188 -2
  10. package/dist/components/AgentInterface.d.ts +1 -0
  11. package/dist/components/AgentInterface.d.ts.map +1 -1
  12. package/dist/components/AgentInterface.js +13 -3
  13. package/dist/components/AgentInterface.js.map +1 -1
  14. package/dist/components/AttachmentTile.d.ts.map +1 -1
  15. package/dist/components/AttachmentTile.js +2 -1
  16. package/dist/components/AttachmentTile.js.map +1 -1
  17. package/dist/components/ConsoleBlock.d.ts.map +1 -1
  18. package/dist/components/ConsoleBlock.js +2 -1
  19. package/dist/components/ConsoleBlock.js.map +1 -1
  20. package/dist/components/CustomProviderCard.d.ts +17 -0
  21. package/dist/components/CustomProviderCard.d.ts.map +1 -0
  22. package/dist/components/CustomProviderCard.js +110 -0
  23. package/dist/components/CustomProviderCard.js.map +1 -0
  24. package/dist/components/Input.d.ts +2 -2
  25. package/dist/components/Input.d.ts.map +1 -1
  26. package/dist/components/Input.js +2 -1
  27. package/dist/components/Input.js.map +1 -1
  28. package/dist/components/MessageEditor.d.ts +1 -3
  29. package/dist/components/MessageEditor.d.ts.map +1 -1
  30. package/dist/components/MessageEditor.js +6 -31
  31. package/dist/components/MessageEditor.js.map +1 -1
  32. package/dist/components/MessageList.d.ts +1 -0
  33. package/dist/components/MessageList.d.ts.map +1 -1
  34. package/dist/components/MessageList.js +6 -3
  35. package/dist/components/MessageList.js.map +1 -1
  36. package/dist/components/Messages.d.ts +2 -0
  37. package/dist/components/Messages.d.ts.map +1 -1
  38. package/dist/components/Messages.js +25 -14
  39. package/dist/components/Messages.js.map +1 -1
  40. package/dist/components/ProviderKeyInput.d.ts +1 -1
  41. package/dist/components/ProviderKeyInput.d.ts.map +1 -1
  42. package/dist/components/ProviderKeyInput.js +22 -36
  43. package/dist/components/ProviderKeyInput.js.map +1 -1
  44. package/dist/components/StreamingMessageContainer.d.ts +1 -0
  45. package/dist/components/StreamingMessageContainer.d.ts.map +1 -1
  46. package/dist/components/StreamingMessageContainer.js +5 -2
  47. package/dist/components/StreamingMessageContainer.js.map +1 -1
  48. package/dist/components/ThinkingBlock.d.ts +11 -0
  49. package/dist/components/ThinkingBlock.d.ts.map +1 -0
  50. package/dist/components/ThinkingBlock.js +58 -0
  51. package/dist/components/ThinkingBlock.js.map +1 -0
  52. package/dist/dialogs/ApiKeyPromptDialog.d.ts +1 -1
  53. package/dist/dialogs/ApiKeyPromptDialog.d.ts.map +1 -1
  54. package/dist/dialogs/ApiKeyPromptDialog.js +3 -1
  55. package/dist/dialogs/ApiKeyPromptDialog.js.map +1 -1
  56. package/dist/dialogs/AttachmentOverlay.d.ts.map +1 -1
  57. package/dist/dialogs/AttachmentOverlay.js +3 -2
  58. package/dist/dialogs/AttachmentOverlay.js.map +1 -1
  59. package/dist/dialogs/CustomProviderDialog.d.ts +25 -0
  60. package/dist/dialogs/CustomProviderDialog.d.ts.map +1 -0
  61. package/dist/dialogs/CustomProviderDialog.js +270 -0
  62. package/dist/dialogs/CustomProviderDialog.js.map +1 -0
  63. package/dist/dialogs/ModelSelector.d.ts +6 -6
  64. package/dist/dialogs/ModelSelector.d.ts.map +1 -1
  65. package/dist/dialogs/ModelSelector.js +60 -74
  66. package/dist/dialogs/ModelSelector.js.map +1 -1
  67. package/dist/dialogs/PersistentStorageDialog.d.ts +1 -1
  68. package/dist/dialogs/PersistentStorageDialog.d.ts.map +1 -1
  69. package/dist/dialogs/PersistentStorageDialog.js +4 -1
  70. package/dist/dialogs/PersistentStorageDialog.js.map +1 -1
  71. package/dist/dialogs/ProvidersModelsTab.d.ts +20 -0
  72. package/dist/dialogs/ProvidersModelsTab.d.ts.map +1 -0
  73. package/dist/dialogs/ProvidersModelsTab.js +191 -0
  74. package/dist/dialogs/ProvidersModelsTab.js.map +1 -0
  75. package/dist/dialogs/SessionListDialog.d.ts +1 -1
  76. package/dist/dialogs/SessionListDialog.d.ts.map +1 -1
  77. package/dist/dialogs/SessionListDialog.js +3 -1
  78. package/dist/dialogs/SessionListDialog.js.map +1 -1
  79. package/dist/dialogs/SettingsDialog.d.ts +1 -2
  80. package/dist/dialogs/SettingsDialog.d.ts.map +1 -1
  81. package/dist/dialogs/SettingsDialog.js +10 -3
  82. package/dist/dialogs/SettingsDialog.js.map +1 -1
  83. package/dist/index.d.ts +4 -0
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +3 -0
  86. package/dist/index.js.map +1 -1
  87. package/dist/storage/app-storage.d.ts +3 -1
  88. package/dist/storage/app-storage.d.ts.map +1 -1
  89. package/dist/storage/app-storage.js +2 -1
  90. package/dist/storage/app-storage.js.map +1 -1
  91. package/dist/storage/stores/custom-providers-store.d.ts +25 -0
  92. package/dist/storage/stores/custom-providers-store.d.ts.map +1 -0
  93. package/dist/storage/stores/custom-providers-store.js +35 -0
  94. package/dist/storage/stores/custom-providers-store.js.map +1 -0
  95. package/dist/storage/stores/sessions-store.d.ts.map +1 -1
  96. package/dist/storage/stores/sessions-store.js +0 -1
  97. package/dist/storage/stores/sessions-store.js.map +1 -1
  98. package/dist/storage/types.d.ts +0 -2
  99. package/dist/storage/types.d.ts.map +1 -1
  100. package/dist/tools/artifacts/ArtifactPill.d.ts +1 -1
  101. package/dist/tools/artifacts/ArtifactPill.d.ts.map +1 -1
  102. package/dist/tools/artifacts/ArtifactPill.js +2 -1
  103. package/dist/tools/artifacts/ArtifactPill.js.map +1 -1
  104. package/dist/tools/artifacts/DocxArtifact.js +1 -1
  105. package/dist/tools/artifacts/DocxArtifact.js.map +1 -1
  106. package/dist/tools/artifacts/ExcelArtifact.js +1 -1
  107. package/dist/tools/artifacts/ExcelArtifact.js.map +1 -1
  108. package/dist/tools/artifacts/GenericArtifact.js +1 -1
  109. package/dist/tools/artifacts/GenericArtifact.js.map +1 -1
  110. package/dist/tools/artifacts/HtmlArtifact.d.ts.map +1 -1
  111. package/dist/tools/artifacts/HtmlArtifact.js +5 -1
  112. package/dist/tools/artifacts/HtmlArtifact.js.map +1 -1
  113. package/dist/tools/artifacts/ImageArtifact.js +1 -1
  114. package/dist/tools/artifacts/ImageArtifact.js.map +1 -1
  115. package/dist/tools/artifacts/MarkdownArtifact.d.ts.map +1 -1
  116. package/dist/tools/artifacts/MarkdownArtifact.js +3 -1
  117. package/dist/tools/artifacts/MarkdownArtifact.js.map +1 -1
  118. package/dist/tools/artifacts/PdfArtifact.js +1 -1
  119. package/dist/tools/artifacts/PdfArtifact.js.map +1 -1
  120. package/dist/tools/artifacts/SvgArtifact.d.ts.map +1 -1
  121. package/dist/tools/artifacts/SvgArtifact.js +3 -1
  122. package/dist/tools/artifacts/SvgArtifact.js.map +1 -1
  123. package/dist/tools/artifacts/TextArtifact.d.ts.map +1 -1
  124. package/dist/tools/artifacts/TextArtifact.js +2 -1
  125. package/dist/tools/artifacts/TextArtifact.js.map +1 -1
  126. package/dist/tools/artifacts/artifacts-tool-renderer.d.ts.map +1 -1
  127. package/dist/tools/artifacts/artifacts-tool-renderer.js +18 -8
  128. package/dist/tools/artifacts/artifacts-tool-renderer.js.map +1 -1
  129. package/dist/tools/artifacts/artifacts.d.ts.map +1 -1
  130. package/dist/tools/artifacts/artifacts.js +3 -2
  131. package/dist/tools/artifacts/artifacts.js.map +1 -1
  132. package/dist/tools/extract-document.d.ts.map +1 -1
  133. package/dist/tools/extract-document.js +78 -58
  134. package/dist/tools/extract-document.js.map +1 -1
  135. package/dist/tools/javascript-repl.d.ts.map +1 -1
  136. package/dist/tools/javascript-repl.js +7 -3
  137. package/dist/tools/javascript-repl.js.map +1 -1
  138. package/dist/tools/renderer-registry.d.ts +1 -1
  139. package/dist/tools/renderer-registry.d.ts.map +1 -1
  140. package/dist/tools/renderer-registry.js +20 -6
  141. package/dist/tools/renderer-registry.js.map +1 -1
  142. package/dist/tools/renderers/BashRenderer.d.ts.map +1 -1
  143. package/dist/tools/renderers/BashRenderer.js +5 -2
  144. package/dist/tools/renderers/BashRenderer.js.map +1 -1
  145. package/dist/tools/renderers/CalculateRenderer.d.ts.map +1 -1
  146. package/dist/tools/renderers/CalculateRenderer.js +5 -2
  147. package/dist/tools/renderers/CalculateRenderer.js.map +1 -1
  148. package/dist/tools/renderers/DefaultRenderer.d.ts.map +1 -1
  149. package/dist/tools/renderers/DefaultRenderer.js +5 -2
  150. package/dist/tools/renderers/DefaultRenderer.js.map +1 -1
  151. package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts.map +1 -1
  152. package/dist/tools/renderers/GetCurrentTimeRenderer.js +9 -3
  153. package/dist/tools/renderers/GetCurrentTimeRenderer.js.map +1 -1
  154. package/dist/utils/auth-token.js +1 -1
  155. package/dist/utils/auth-token.js.map +1 -1
  156. package/dist/utils/i18n.d.ts +105 -3
  157. package/dist/utils/i18n.d.ts.map +1 -1
  158. package/dist/utils/i18n.js +72 -2
  159. package/dist/utils/i18n.js.map +1 -1
  160. package/dist/utils/model-discovery.d.ts +38 -0
  161. package/dist/utils/model-discovery.d.ts.map +1 -0
  162. package/dist/utils/model-discovery.js +243 -0
  163. package/dist/utils/model-discovery.js.map +1 -0
  164. package/dist/utils/proxy-utils.d.ts +37 -0
  165. package/dist/utils/proxy-utils.d.ts.map +1 -0
  166. package/dist/utils/proxy-utils.js +97 -0
  167. package/dist/utils/proxy-utils.js.map +1 -0
  168. package/example/package.json +2 -2
  169. package/example/src/custom-messages.ts +1 -1
  170. package/example/src/main.ts +17 -6
  171. package/package.json +9 -8
  172. package/src/ChatPanel.ts +4 -2
  173. package/src/agent/transports/ProviderTransport.ts +5 -10
  174. package/src/app.css +24 -0
  175. package/src/components/AgentInterface.ts +14 -3
  176. package/src/components/AttachmentTile.ts +2 -1
  177. package/src/components/ConsoleBlock.ts +2 -1
  178. package/src/components/CustomProviderCard.ts +100 -0
  179. package/src/components/Input.ts +2 -1
  180. package/src/components/MessageEditor.ts +6 -33
  181. package/src/components/MessageList.ts +4 -3
  182. package/src/components/Messages.ts +32 -20
  183. package/src/components/ProviderKeyInput.ts +19 -38
  184. package/src/components/StreamingMessageContainer.ts +3 -2
  185. package/src/components/ThinkingBlock.ts +43 -0
  186. package/src/dialogs/ApiKeyPromptDialog.ts +3 -1
  187. package/src/dialogs/AttachmentOverlay.ts +3 -2
  188. package/src/dialogs/CustomProviderDialog.ts +274 -0
  189. package/src/dialogs/ModelSelector.ts +61 -75
  190. package/src/dialogs/PersistentStorageDialog.ts +4 -1
  191. package/src/dialogs/ProvidersModelsTab.ts +212 -0
  192. package/src/dialogs/SessionListDialog.ts +3 -1
  193. package/src/dialogs/SettingsDialog.ts +10 -13
  194. package/src/index.ts +8 -0
  195. package/src/storage/app-storage.ts +4 -0
  196. package/src/storage/stores/custom-providers-store.ts +62 -0
  197. package/src/storage/stores/sessions-store.ts +0 -1
  198. package/src/storage/types.ts +0 -3
  199. package/src/tools/artifacts/ArtifactPill.ts +2 -1
  200. package/src/tools/artifacts/DocxArtifact.ts +1 -1
  201. package/src/tools/artifacts/ExcelArtifact.ts +1 -1
  202. package/src/tools/artifacts/GenericArtifact.ts +1 -1
  203. package/src/tools/artifacts/HtmlArtifact.ts +5 -1
  204. package/src/tools/artifacts/ImageArtifact.ts +1 -1
  205. package/src/tools/artifacts/MarkdownArtifact.ts +3 -1
  206. package/src/tools/artifacts/PdfArtifact.ts +1 -1
  207. package/src/tools/artifacts/SvgArtifact.ts +3 -1
  208. package/src/tools/artifacts/TextArtifact.ts +2 -1
  209. package/src/tools/artifacts/artifacts-tool-renderer.ts +20 -8
  210. package/src/tools/artifacts/artifacts.ts +3 -2
  211. package/src/tools/extract-document.ts +82 -61
  212. package/src/tools/javascript-repl.ts +8 -3
  213. package/src/tools/renderer-registry.ts +20 -6
  214. package/src/tools/renderers/BashRenderer.ts +6 -2
  215. package/src/tools/renderers/CalculateRenderer.ts +6 -2
  216. package/src/tools/renderers/DefaultRenderer.ts +6 -2
  217. package/src/tools/renderers/GetCurrentTimeRenderer.ts +11 -3
  218. package/src/utils/auth-token.ts +1 -1
  219. package/src/utils/i18n.ts +120 -5
  220. package/src/utils/model-discovery.ts +277 -0
  221. package/src/utils/proxy-utils.ts +112 -0
  222. package/example/package-lock.json +0 -1965
@@ -1,7 +1,8 @@
1
- import { html, icon, type TemplateResult } from "@mariozechner/mini-lit";
1
+ import { icon } from "@mariozechner/mini-lit";
2
+ import { html, type TemplateResult } from "lit";
2
3
  import type { Ref } from "lit/directives/ref.js";
3
4
  import { ref } from "lit/directives/ref.js";
4
- import { ChevronRight, Loader } from "lucide";
5
+ import { ChevronsUpDown, ChevronUp, Loader } from "lucide";
5
6
  import type { ToolRenderer } from "./types.js";
6
7
 
7
8
  // Registry of tool renderers
@@ -85,11 +86,23 @@ export function renderCollapsibleHeader(
85
86
  if (isCollapsed) {
86
87
  content.classList.remove("max-h-0");
87
88
  content.classList.add("max-h-[2000px]", "mt-3");
88
- chevron.classList.add("rotate-90");
89
+ // Show ChevronUp, hide ChevronsUpDown
90
+ const upIcon = chevron.querySelector(".chevron-up");
91
+ const downIcon = chevron.querySelector(".chevrons-up-down");
92
+ if (upIcon && downIcon) {
93
+ upIcon.classList.remove("hidden");
94
+ downIcon.classList.add("hidden");
95
+ }
89
96
  } else {
90
97
  content.classList.remove("max-h-[2000px]", "mt-3");
91
98
  content.classList.add("max-h-0");
92
- chevron.classList.remove("rotate-90");
99
+ // Show ChevronsUpDown, hide ChevronUp
100
+ const upIcon = chevron.querySelector(".chevron-up");
101
+ const downIcon = chevron.querySelector(".chevrons-up-down");
102
+ if (upIcon && downIcon) {
103
+ upIcon.classList.add("hidden");
104
+ downIcon.classList.remove("hidden");
105
+ }
93
106
  }
94
107
  }
95
108
  };
@@ -108,8 +121,9 @@ export function renderCollapsibleHeader(
108
121
  ${statusIcon(toolIcon, toolIconColor)}
109
122
  ${text}
110
123
  </div>
111
- <span class="inline-block text-muted-foreground transition-transform duration-300 ${defaultExpanded ? "rotate-90" : ""}" ${ref(chevronRef)}>
112
- ${icon(ChevronRight, "sm")}
124
+ <span class="inline-block text-muted-foreground" ${ref(chevronRef)}>
125
+ <span class="chevron-up ${defaultExpanded ? "" : "hidden"}">${icon(ChevronUp, "sm")}</span>
126
+ <span class="chevrons-up-down ${defaultExpanded ? "hidden" : ""}">${icon(ChevronsUpDown, "sm")}</span>
113
127
  </span>
114
128
  </button>
115
129
  `;
@@ -1,5 +1,5 @@
1
- import { html } from "@mariozechner/mini-lit";
2
1
  import type { ToolResultMessage } from "@mariozechner/pi-ai";
2
+ import { html } from "lit";
3
3
  import { SquareTerminal } from "lucide";
4
4
  import { i18n } from "../../utils/i18n.js";
5
5
  import { renderHeader } from "../renderer-registry.js";
@@ -16,7 +16,11 @@ export class BashRenderer implements ToolRenderer<BashParams, undefined> {
16
16
 
17
17
  // With result: show command + output
18
18
  if (result && params?.command) {
19
- const output = result.output || "";
19
+ const output =
20
+ result.content
21
+ ?.filter((c) => c.type === "text")
22
+ .map((c: any) => c.text)
23
+ .join("\n") || "";
20
24
  const combined = output ? `> ${params.command}\n\n${output}` : `> ${params.command}`;
21
25
  return {
22
26
  content: html`
@@ -1,5 +1,5 @@
1
- import { html } from "@mariozechner/mini-lit";
2
1
  import type { ToolResultMessage } from "@mariozechner/pi-ai";
2
+ import { html } from "lit";
3
3
  import { Calculator } from "lucide";
4
4
  import { i18n } from "../../utils/i18n.js";
5
5
  import { renderHeader } from "../renderer-registry.js";
@@ -16,7 +16,11 @@ export class CalculateRenderer implements ToolRenderer<CalculateParams, undefine
16
16
 
17
17
  // Full params + full result
18
18
  if (result && params?.expression) {
19
- const output = result.output || "";
19
+ const output =
20
+ result.content
21
+ ?.filter((c) => c.type === "text")
22
+ .map((c: any) => c.text)
23
+ .join("\n") || "";
20
24
 
21
25
  // Error: show expression in header, error below
22
26
  if (result.isError) {
@@ -1,5 +1,5 @@
1
- import { html } from "@mariozechner/mini-lit";
2
1
  import type { ToolResultMessage } from "@mariozechner/pi-ai";
2
+ import { html } from "lit";
3
3
  import { Code } from "lucide";
4
4
  import { i18n } from "../../utils/i18n.js";
5
5
  import { renderHeader } from "../renderer-registry.js";
@@ -25,7 +25,11 @@ export class DefaultRenderer implements ToolRenderer {
25
25
 
26
26
  // With result: show header + params + result
27
27
  if (result) {
28
- let outputJson = result.output || i18n("(no output)");
28
+ let outputJson =
29
+ result.content
30
+ ?.filter((c) => c.type === "text")
31
+ .map((c: any) => c.text)
32
+ .join("\n") || i18n("(no output)");
29
33
  let outputLanguage = "text";
30
34
 
31
35
  // Try to parse and pretty-print if it's valid JSON
@@ -1,5 +1,5 @@
1
- import { html } from "@mariozechner/mini-lit";
2
1
  import type { ToolResultMessage } from "@mariozechner/pi-ai";
2
+ import { html } from "lit";
3
3
  import { Clock } from "lucide";
4
4
  import { i18n } from "../../utils/i18n.js";
5
5
  import { renderHeader } from "../renderer-registry.js";
@@ -19,7 +19,11 @@ export class GetCurrentTimeRenderer implements ToolRenderer<GetCurrentTimeParams
19
19
 
20
20
  // Full params + full result
21
21
  if (result && params) {
22
- const output = result.output || "";
22
+ const output =
23
+ result.content
24
+ ?.filter((c) => c.type === "text")
25
+ .map((c: any) => c.text)
26
+ .join("\n") || "";
23
27
  const headerText = params.timezone
24
28
  ? `${i18n("Getting current time in")} ${params.timezone}`
25
29
  : i18n("Getting current date and time");
@@ -43,7 +47,11 @@ export class GetCurrentTimeRenderer implements ToolRenderer<GetCurrentTimeParams
43
47
 
44
48
  // Full result, no params
45
49
  if (result) {
46
- const output = result.output || "";
50
+ const output =
51
+ result.content
52
+ ?.filter((c) => c.type === "text")
53
+ .map((c: any) => c.text)
54
+ .join("\n") || "";
47
55
 
48
56
  // Error: show header, error below
49
57
  if (result.isError) {
@@ -1,4 +1,4 @@
1
- import { PromptDialog } from "@mariozechner/mini-lit";
1
+ import PromptDialog from "@mariozechner/mini-lit/dist/PromptDialog.js";
2
2
  import { i18n } from "./i18n.js";
3
3
 
4
4
  export async function getAuthToken(): Promise<string | undefined> {
package/src/utils/i18n.ts CHANGED
@@ -137,11 +137,12 @@ declare module "@mariozechner/mini-lit" {
137
137
  Proxy: string;
138
138
  "Use CORS Proxy": string;
139
139
  "Proxy URL": string;
140
+ "Format: The proxy must accept requests as <proxy-url>/?url=<target-url>": string;
140
141
  "Settings are stored locally in your browser": string;
141
142
  Clear: string;
142
143
  "API Key Required": string;
143
144
  "Enter your API key for {provider}": string;
144
- "The CORS proxy strips CORS headers from API responses, allowing browser-based apps to make direct calls to LLM providers without CORS restrictions. It forwards requests to providers while removing headers that would otherwise block cross-origin requests.": string;
145
+ "Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.": string;
145
146
  Off: string;
146
147
  Minimal: string;
147
148
  Low: string;
@@ -169,6 +170,40 @@ declare module "@mariozechner/mini-lit" {
169
170
  messages: string;
170
171
  tokens: string;
171
172
  "Drop files here": string;
173
+ // Providers & Models
174
+ "Providers & Models": string;
175
+ "Cloud Providers": string;
176
+ "Cloud LLM providers with predefined models. API keys are stored locally in your browser.": string;
177
+ "Custom Providers": string;
178
+ "User-configured servers with auto-discovered or manually defined models.": string;
179
+ "Add Provider": string;
180
+ "No custom providers configured. Click 'Add Provider' to get started.": string;
181
+ Models: string;
182
+ "auto-discovered": string;
183
+ Refresh: string;
184
+ Edit: string;
185
+ "Are you sure you want to delete this provider?": string;
186
+ "Edit Provider": string;
187
+ "Provider Name": string;
188
+ "e.g., My Ollama Server": string;
189
+ "Provider Type": string;
190
+ "Base URL": string;
191
+ "e.g., http://localhost:11434": string;
192
+ "API Key (Optional)": string;
193
+ "Leave empty if not required": string;
194
+ "Test Connection": string;
195
+ Discovered: string;
196
+ models: string;
197
+ and: string;
198
+ more: string;
199
+ "For manual provider types, add models after saving the provider.": string;
200
+ "Please fill in all required fields": string;
201
+ "Failed to save provider": string;
202
+ "OpenAI Completions Compatible": string;
203
+ "OpenAI Responses Compatible": string;
204
+ "Anthropic Messages Compatible": string;
205
+ "Checking...": string;
206
+ Disconnected: string;
172
207
  }
173
208
  }
174
209
 
@@ -312,12 +347,14 @@ export const translations = {
312
347
  Proxy: "Proxy",
313
348
  "Use CORS Proxy": "Use CORS Proxy",
314
349
  "Proxy URL": "Proxy URL",
350
+ "Format: The proxy must accept requests as <proxy-url>/?url=<target-url>":
351
+ "Format: The proxy must accept requests as <proxy-url>/?url=<target-url>",
315
352
  "Settings are stored locally in your browser": "Settings are stored locally in your browser",
316
353
  Clear: "Clear",
317
354
  "API Key Required": "API Key Required",
318
355
  "Enter your API key for {provider}": "Enter your API key for {provider}",
319
- "The CORS proxy strips CORS headers from API responses, allowing browser-based apps to make direct calls to LLM providers without CORS restrictions. It forwards requests to providers while removing headers that would otherwise block cross-origin requests.":
320
- "The CORS proxy strips CORS headers from API responses, allowing browser-based apps to make direct calls to LLM providers without CORS restrictions. It forwards requests to providers while removing headers that would otherwise block cross-origin requests.",
356
+ "Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.":
357
+ "Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.",
321
358
  Off: "Off",
322
359
  Minimal: "Minimal",
323
360
  Low: "Low",
@@ -351,6 +388,44 @@ export const translations = {
351
388
  Delete: "Delete",
352
389
  "Drop files here": "Drop files here",
353
390
  "Command failed:": "Command failed:",
391
+ // Providers & Models
392
+ "Providers & Models": "Providers & Models",
393
+ "Cloud Providers": "Cloud Providers",
394
+ "Cloud LLM providers with predefined models. API keys are stored locally in your browser.":
395
+ "Cloud LLM providers with predefined models. API keys are stored locally in your browser.",
396
+ "Custom Providers": "Custom Providers",
397
+ "User-configured servers with auto-discovered or manually defined models.":
398
+ "User-configured servers with auto-discovered or manually defined models.",
399
+ "Add Provider": "Add Provider",
400
+ "No custom providers configured. Click 'Add Provider' to get started.":
401
+ "No custom providers configured. Click 'Add Provider' to get started.",
402
+ "auto-discovered": "auto-discovered",
403
+ Refresh: "Refresh",
404
+ Edit: "Edit",
405
+ "Are you sure you want to delete this provider?": "Are you sure you want to delete this provider?",
406
+ "Edit Provider": "Edit Provider",
407
+ "Provider Name": "Provider Name",
408
+ "e.g., My Ollama Server": "e.g., My Ollama Server",
409
+ "Provider Type": "Provider Type",
410
+ "Base URL": "Base URL",
411
+ "e.g., http://localhost:11434": "e.g., http://localhost:11434",
412
+ "API Key (Optional)": "API Key (Optional)",
413
+ "Leave empty if not required": "Leave empty if not required",
414
+ "Test Connection": "Test Connection",
415
+ Discovered: "Discovered",
416
+ Models: "Models",
417
+ models: "models",
418
+ and: "and",
419
+ more: "more",
420
+ "For manual provider types, add models after saving the provider.":
421
+ "For manual provider types, add models after saving the provider.",
422
+ "Please fill in all required fields": "Please fill in all required fields",
423
+ "Failed to save provider": "Failed to save provider",
424
+ "OpenAI Completions Compatible": "OpenAI Completions Compatible",
425
+ "OpenAI Responses Compatible": "OpenAI Responses Compatible",
426
+ "Anthropic Messages Compatible": "Anthropic Messages Compatible",
427
+ "Checking...": "Checking...",
428
+ Disconnected: "Disconnected",
354
429
  },
355
430
  de: {
356
431
  ...defaultGerman,
@@ -491,12 +566,14 @@ export const translations = {
491
566
  Proxy: "Proxy",
492
567
  "Use CORS Proxy": "CORS-Proxy verwenden",
493
568
  "Proxy URL": "Proxy-URL",
569
+ "Format: The proxy must accept requests as <proxy-url>/?url=<target-url>":
570
+ "Format: Der Proxy muss Anfragen als <proxy-url>/?url=<ziel-url> akzeptieren",
494
571
  "Settings are stored locally in your browser": "Einstellungen werden lokal in Ihrem Browser gespeichert",
495
572
  Clear: "Löschen",
496
573
  "API Key Required": "API-Schlüssel erforderlich",
497
574
  "Enter your API key for {provider}": "Geben Sie Ihren API-Schlüssel für {provider} ein",
498
- "The CORS proxy strips CORS headers from API responses, allowing browser-based apps to make direct calls to LLM providers without CORS restrictions. It forwards requests to providers while removing headers that would otherwise block cross-origin requests.":
499
- "Der CORS-Proxy entfernt CORS-Header aus API-Antworten und ermöglicht browserbasierte Anwendungen, direkte Aufrufe an LLM-Anbieter ohne CORS-Einschränkungen durchzuführen. Er leitet Anfragen an Anbieter weiter und entfernt Header, die sonst Cross-Origin-Anfragen blockieren würden.",
575
+ "Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.":
576
+ "Ermöglicht browserbasierten Anwendungen, CORS-Einschränkungen beim Aufruf von LLM-Anbietern zu umgehen. Erforderlich für Z-AI und Anthropic mit OAuth-Token.",
500
577
  Off: "Aus",
501
578
  Minimal: "Minimal",
502
579
  Low: "Niedrig",
@@ -530,6 +607,44 @@ export const translations = {
530
607
  Delete: "Löschen",
531
608
  "Drop files here": "Dateien hier ablegen",
532
609
  "Command failed:": "Befehl fehlgeschlagen:",
610
+ // Providers & Models
611
+ "Providers & Models": "Anbieter & Modelle",
612
+ "Cloud Providers": "Cloud-Anbieter",
613
+ "Cloud LLM providers with predefined models. API keys are stored locally in your browser.":
614
+ "Cloud-LLM-Anbieter mit vordefinierten Modellen. API-Schlüssel werden lokal in Ihrem Browser gespeichert.",
615
+ "Custom Providers": "Benutzerdefinierte Anbieter",
616
+ "User-configured servers with auto-discovered or manually defined models.":
617
+ "Benutzerkonfigurierte Server mit automatisch erkannten oder manuell definierten Modellen.",
618
+ "Add Provider": "Anbieter hinzufügen",
619
+ "No custom providers configured. Click 'Add Provider' to get started.":
620
+ "Keine benutzerdefinierten Anbieter konfiguriert. Klicken Sie auf 'Anbieter hinzufügen', um zu beginnen.",
621
+ "auto-discovered": "automatisch erkannt",
622
+ Refresh: "Aktualisieren",
623
+ Edit: "Bearbeiten",
624
+ "Are you sure you want to delete this provider?": "Sind Sie sicher, dass Sie diesen Anbieter löschen möchten?",
625
+ "Edit Provider": "Anbieter bearbeiten",
626
+ "Provider Name": "Anbietername",
627
+ "e.g., My Ollama Server": "z.B. Mein Ollama Server",
628
+ "Provider Type": "Anbietertyp",
629
+ "Base URL": "Basis-URL",
630
+ "e.g., http://localhost:11434": "z.B. http://localhost:11434",
631
+ "API Key (Optional)": "API-Schlüssel (Optional)",
632
+ "Leave empty if not required": "Leer lassen, falls nicht erforderlich",
633
+ "Test Connection": "Verbindung testen",
634
+ Discovered: "Erkannt",
635
+ Models: "Modelle",
636
+ models: "Modelle",
637
+ and: "und",
638
+ more: "mehr",
639
+ "For manual provider types, add models after saving the provider.":
640
+ "Für manuelle Anbietertypen fügen Sie Modelle nach dem Speichern des Anbieters hinzu.",
641
+ "Please fill in all required fields": "Bitte füllen Sie alle erforderlichen Felder aus",
642
+ "Failed to save provider": "Fehler beim Speichern des Anbieters",
643
+ "OpenAI Completions Compatible": "OpenAI Completions Kompatibel",
644
+ "OpenAI Responses Compatible": "OpenAI Responses Kompatibel",
645
+ "Anthropic Messages Compatible": "Anthropic Messages Kompatibel",
646
+ "Checking...": "Überprüfe...",
647
+ Disconnected: "Getrennt",
533
648
  },
534
649
  };
535
650
 
@@ -0,0 +1,277 @@
1
+ import { LMStudioClient } from "@lmstudio/sdk";
2
+ import type { Model } from "@mariozechner/pi-ai";
3
+ import { Ollama } from "ollama/browser";
4
+
5
+ /**
6
+ * Discover models from an Ollama server.
7
+ * @param baseUrl - Base URL of the Ollama server (e.g., "http://localhost:11434")
8
+ * @param apiKey - Optional API key (currently unused by Ollama)
9
+ * @returns Array of discovered models
10
+ */
11
+ export async function discoverOllamaModels(baseUrl: string, _apiKey?: string): Promise<Model<any>[]> {
12
+ try {
13
+ // Create Ollama client
14
+ const ollama = new Ollama({ host: baseUrl });
15
+
16
+ // Get list of available models
17
+ const { models } = await ollama.list();
18
+
19
+ // Fetch details for each model and convert to Model format
20
+ const ollamaModelPromises: Promise<Model<any> | null>[] = models.map(async (model: any) => {
21
+ try {
22
+ // Get model details
23
+ const details = await ollama.show({
24
+ model: model.name,
25
+ });
26
+
27
+ // Check capabilities - filter out models that don't support tools
28
+ const capabilities: string[] = (details as any).capabilities || [];
29
+ if (!capabilities.includes("tools")) {
30
+ console.debug(`Skipping model ${model.name}: does not support tools`);
31
+ return null;
32
+ }
33
+
34
+ // Extract model info
35
+ const modelInfo: any = details.model_info || {};
36
+
37
+ // Get context window size - look for architecture-specific keys
38
+ const architecture = modelInfo["general.architecture"] || "";
39
+ const contextKey = `${architecture}.context_length`;
40
+ const contextWindow = parseInt(modelInfo[contextKey] || "8192", 10);
41
+
42
+ // Ollama caps max tokens at 10x context length
43
+ const maxTokens = contextWindow * 10;
44
+
45
+ // Ollama only supports completions API
46
+ const ollamaModel: Model<any> = {
47
+ id: model.name,
48
+ name: model.name,
49
+ api: "openai-completions" as any,
50
+ provider: "", // Will be set by caller
51
+ baseUrl: `${baseUrl}/v1`,
52
+ reasoning: capabilities.includes("thinking"),
53
+ input: ["text"],
54
+ cost: {
55
+ input: 0,
56
+ output: 0,
57
+ cacheRead: 0,
58
+ cacheWrite: 0,
59
+ },
60
+ contextWindow: contextWindow,
61
+ maxTokens: maxTokens,
62
+ };
63
+
64
+ return ollamaModel;
65
+ } catch (err) {
66
+ console.error(`Failed to fetch details for model ${model.name}:`, err);
67
+ return null;
68
+ }
69
+ });
70
+
71
+ const results = await Promise.all(ollamaModelPromises);
72
+ return results.filter((m): m is Model<any> => m !== null);
73
+ } catch (err) {
74
+ console.error("Failed to discover Ollama models:", err);
75
+ throw new Error(`Ollama discovery failed: ${err instanceof Error ? err.message : String(err)}`);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Discover models from a llama.cpp server via OpenAI-compatible /v1/models endpoint.
81
+ * @param baseUrl - Base URL of the llama.cpp server (e.g., "http://localhost:8080")
82
+ * @param apiKey - Optional API key
83
+ * @returns Array of discovered models
84
+ */
85
+ export async function discoverLlamaCppModels(baseUrl: string, apiKey?: string): Promise<Model<any>[]> {
86
+ try {
87
+ const headers: HeadersInit = {
88
+ "Content-Type": "application/json",
89
+ };
90
+
91
+ if (apiKey) {
92
+ headers.Authorization = `Bearer ${apiKey}`;
93
+ }
94
+
95
+ const response = await fetch(`${baseUrl}/v1/models`, {
96
+ method: "GET",
97
+ headers,
98
+ });
99
+
100
+ if (!response.ok) {
101
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
102
+ }
103
+
104
+ const data = await response.json();
105
+
106
+ if (!data.data || !Array.isArray(data.data)) {
107
+ throw new Error("Invalid response format from llama.cpp server");
108
+ }
109
+
110
+ return data.data.map((model: any) => {
111
+ // llama.cpp doesn't always provide context window info
112
+ const contextWindow = model.context_length || 8192;
113
+ const maxTokens = model.max_tokens || 4096;
114
+
115
+ const llamaModel: Model<any> = {
116
+ id: model.id,
117
+ name: model.id,
118
+ api: "openai-completions" as any,
119
+ provider: "", // Will be set by caller
120
+ baseUrl: `${baseUrl}/v1`,
121
+ reasoning: false,
122
+ input: ["text"],
123
+ cost: {
124
+ input: 0,
125
+ output: 0,
126
+ cacheRead: 0,
127
+ cacheWrite: 0,
128
+ },
129
+ contextWindow: contextWindow,
130
+ maxTokens: maxTokens,
131
+ };
132
+
133
+ return llamaModel;
134
+ });
135
+ } catch (err) {
136
+ console.error("Failed to discover llama.cpp models:", err);
137
+ throw new Error(`llama.cpp discovery failed: ${err instanceof Error ? err.message : String(err)}`);
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Discover models from a vLLM server via OpenAI-compatible /v1/models endpoint.
143
+ * @param baseUrl - Base URL of the vLLM server (e.g., "http://localhost:8000")
144
+ * @param apiKey - Optional API key
145
+ * @returns Array of discovered models
146
+ */
147
+ export async function discoverVLLMModels(baseUrl: string, apiKey?: string): Promise<Model<any>[]> {
148
+ try {
149
+ const headers: HeadersInit = {
150
+ "Content-Type": "application/json",
151
+ };
152
+
153
+ if (apiKey) {
154
+ headers.Authorization = `Bearer ${apiKey}`;
155
+ }
156
+
157
+ const response = await fetch(`${baseUrl}/v1/models`, {
158
+ method: "GET",
159
+ headers,
160
+ });
161
+
162
+ if (!response.ok) {
163
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
164
+ }
165
+
166
+ const data = await response.json();
167
+
168
+ if (!data.data || !Array.isArray(data.data)) {
169
+ throw new Error("Invalid response format from vLLM server");
170
+ }
171
+
172
+ return data.data.map((model: any) => {
173
+ // vLLM provides max_model_len which is the context window
174
+ const contextWindow = model.max_model_len || 8192;
175
+ const maxTokens = Math.min(contextWindow, 4096); // Cap max tokens
176
+
177
+ const vllmModel: Model<any> = {
178
+ id: model.id,
179
+ name: model.id,
180
+ api: "openai-completions" as any,
181
+ provider: "", // Will be set by caller
182
+ baseUrl: `${baseUrl}/v1`,
183
+ reasoning: false,
184
+ input: ["text"],
185
+ cost: {
186
+ input: 0,
187
+ output: 0,
188
+ cacheRead: 0,
189
+ cacheWrite: 0,
190
+ },
191
+ contextWindow: contextWindow,
192
+ maxTokens: maxTokens,
193
+ };
194
+
195
+ return vllmModel;
196
+ });
197
+ } catch (err) {
198
+ console.error("Failed to discover vLLM models:", err);
199
+ throw new Error(`vLLM discovery failed: ${err instanceof Error ? err.message : String(err)}`);
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Discover models from an LM Studio server using the LM Studio SDK.
205
+ * @param baseUrl - Base URL of the LM Studio server (e.g., "http://localhost:1234")
206
+ * @param apiKey - Optional API key (unused for LM Studio SDK)
207
+ * @returns Array of discovered models
208
+ */
209
+ export async function discoverLMStudioModels(baseUrl: string, _apiKey?: string): Promise<Model<any>[]> {
210
+ try {
211
+ // Extract host and port from baseUrl
212
+ const url = new URL(baseUrl);
213
+ const port = url.port ? parseInt(url.port, 10) : 1234;
214
+
215
+ // Create LM Studio client
216
+ const client = new LMStudioClient({ baseUrl: `ws://${url.hostname}:${port}` });
217
+
218
+ // List all downloaded models
219
+ const models = await client.system.listDownloadedModels();
220
+
221
+ // Filter to only LLM models and map to our Model format
222
+ return models
223
+ .filter((model) => model.type === "llm")
224
+ .map((model) => {
225
+ const contextWindow = model.maxContextLength;
226
+ // Use 10x context length like Ollama does
227
+ const maxTokens = contextWindow;
228
+
229
+ const lmStudioModel: Model<any> = {
230
+ id: model.path,
231
+ name: model.displayName || model.path,
232
+ api: "openai-completions" as any,
233
+ provider: "", // Will be set by caller
234
+ baseUrl: `${baseUrl}/v1`,
235
+ reasoning: model.trainedForToolUse || false,
236
+ input: model.vision ? ["text", "image"] : ["text"],
237
+ cost: {
238
+ input: 0,
239
+ output: 0,
240
+ cacheRead: 0,
241
+ cacheWrite: 0,
242
+ },
243
+ contextWindow: contextWindow,
244
+ maxTokens: maxTokens,
245
+ };
246
+
247
+ return lmStudioModel;
248
+ });
249
+ } catch (err) {
250
+ console.error("Failed to discover LM Studio models:", err);
251
+ throw new Error(`LM Studio discovery failed: ${err instanceof Error ? err.message : String(err)}`);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Convenience function to discover models based on provider type.
257
+ * @param type - Provider type
258
+ * @param baseUrl - Base URL of the server
259
+ * @param apiKey - Optional API key
260
+ * @returns Array of discovered models
261
+ */
262
+ export async function discoverModels(
263
+ type: "ollama" | "llama.cpp" | "vllm" | "lmstudio",
264
+ baseUrl: string,
265
+ apiKey?: string,
266
+ ): Promise<Model<any>[]> {
267
+ switch (type) {
268
+ case "ollama":
269
+ return discoverOllamaModels(baseUrl, apiKey);
270
+ case "llama.cpp":
271
+ return discoverLlamaCppModels(baseUrl, apiKey);
272
+ case "vllm":
273
+ return discoverVLLMModels(baseUrl, apiKey);
274
+ case "lmstudio":
275
+ return discoverLMStudioModels(baseUrl, apiKey);
276
+ }
277
+ }