@mariozechner/pi-web-ui 0.5.44

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 (265) hide show
  1. package/README.md +252 -0
  2. package/dist/ChatPanel.d.ts +23 -0
  3. package/dist/ChatPanel.d.ts.map +1 -0
  4. package/dist/ChatPanel.js +224 -0
  5. package/dist/ChatPanel.js.map +1 -0
  6. package/dist/app.css +2 -0
  7. package/dist/components/AgentInterface.d.ts +35 -0
  8. package/dist/components/AgentInterface.d.ts.map +1 -0
  9. package/dist/components/AgentInterface.js +308 -0
  10. package/dist/components/AgentInterface.js.map +1 -0
  11. package/dist/components/AttachmentTile.d.ts +12 -0
  12. package/dist/components/AttachmentTile.d.ts.map +1 -0
  13. package/dist/components/AttachmentTile.js +114 -0
  14. package/dist/components/AttachmentTile.js.map +1 -0
  15. package/dist/components/ConsoleBlock.d.ts +11 -0
  16. package/dist/components/ConsoleBlock.d.ts.map +1 -0
  17. package/dist/components/ConsoleBlock.js +77 -0
  18. package/dist/components/ConsoleBlock.js.map +1 -0
  19. package/dist/components/Input.d.ts +26 -0
  20. package/dist/components/Input.d.ts.map +1 -0
  21. package/dist/components/Input.js +56 -0
  22. package/dist/components/Input.js.map +1 -0
  23. package/dist/components/MessageEditor.d.ts +38 -0
  24. package/dist/components/MessageEditor.d.ts.map +1 -0
  25. package/dist/components/MessageEditor.js +296 -0
  26. package/dist/components/MessageEditor.js.map +1 -0
  27. package/dist/components/MessageList.d.ts +13 -0
  28. package/dist/components/MessageList.d.ts.map +1 -0
  29. package/dist/components/MessageList.js +88 -0
  30. package/dist/components/MessageList.js.map +1 -0
  31. package/dist/components/Messages.d.ts +53 -0
  32. package/dist/components/Messages.d.ts.map +1 -0
  33. package/dist/components/Messages.js +323 -0
  34. package/dist/components/Messages.js.map +1 -0
  35. package/dist/components/ProviderKeyInput.d.ts +16 -0
  36. package/dist/components/ProviderKeyInput.d.ts.map +1 -0
  37. package/dist/components/ProviderKeyInput.js +183 -0
  38. package/dist/components/ProviderKeyInput.js.map +1 -0
  39. package/dist/components/SandboxedIframe.d.ts +63 -0
  40. package/dist/components/SandboxedIframe.d.ts.map +1 -0
  41. package/dist/components/SandboxedIframe.js +435 -0
  42. package/dist/components/SandboxedIframe.js.map +1 -0
  43. package/dist/components/StreamingMessageContainer.d.ts +17 -0
  44. package/dist/components/StreamingMessageContainer.d.ts.map +1 -0
  45. package/dist/components/StreamingMessageContainer.js +114 -0
  46. package/dist/components/StreamingMessageContainer.js.map +1 -0
  47. package/dist/dialogs/ApiKeyPromptDialog.d.ts +15 -0
  48. package/dist/dialogs/ApiKeyPromptDialog.d.ts.map +1 -0
  49. package/dist/dialogs/ApiKeyPromptDialog.js +80 -0
  50. package/dist/dialogs/ApiKeyPromptDialog.js.map +1 -0
  51. package/dist/dialogs/AttachmentOverlay.d.ts +32 -0
  52. package/dist/dialogs/AttachmentOverlay.d.ts.map +1 -0
  53. package/dist/dialogs/AttachmentOverlay.js +575 -0
  54. package/dist/dialogs/AttachmentOverlay.js.map +1 -0
  55. package/dist/dialogs/ModelSelector.d.ts +27 -0
  56. package/dist/dialogs/ModelSelector.d.ts.map +1 -0
  57. package/dist/dialogs/ModelSelector.js +334 -0
  58. package/dist/dialogs/ModelSelector.js.map +1 -0
  59. package/dist/dialogs/SettingsDialog.d.ts +31 -0
  60. package/dist/dialogs/SettingsDialog.d.ts.map +1 -0
  61. package/dist/dialogs/SettingsDialog.js +228 -0
  62. package/dist/dialogs/SettingsDialog.js.map +1 -0
  63. package/dist/index.d.ts +46 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +51 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/state/agent-session.d.ts +58 -0
  68. package/dist/state/agent-session.d.ts.map +1 -0
  69. package/dist/state/agent-session.js +252 -0
  70. package/dist/state/agent-session.js.map +1 -0
  71. package/dist/state/transports/AppTransport.d.ts +13 -0
  72. package/dist/state/transports/AppTransport.d.ts.map +1 -0
  73. package/dist/state/transports/AppTransport.js +316 -0
  74. package/dist/state/transports/AppTransport.js.map +1 -0
  75. package/dist/state/transports/ProviderTransport.d.ts +12 -0
  76. package/dist/state/transports/ProviderTransport.d.ts.map +1 -0
  77. package/dist/state/transports/ProviderTransport.js +44 -0
  78. package/dist/state/transports/ProviderTransport.js.map +1 -0
  79. package/dist/state/transports/index.d.ts +4 -0
  80. package/dist/state/transports/index.d.ts.map +1 -0
  81. package/dist/state/transports/index.js +4 -0
  82. package/dist/state/transports/index.js.map +1 -0
  83. package/dist/state/transports/proxy-types.d.ts +48 -0
  84. package/dist/state/transports/proxy-types.d.ts.map +1 -0
  85. package/dist/state/transports/proxy-types.js +2 -0
  86. package/dist/state/transports/proxy-types.js.map +1 -0
  87. package/dist/state/transports/types.d.ts +11 -0
  88. package/dist/state/transports/types.d.ts.map +1 -0
  89. package/dist/state/transports/types.js +2 -0
  90. package/dist/state/transports/types.js.map +1 -0
  91. package/dist/state/types.d.ts +15 -0
  92. package/dist/state/types.d.ts.map +1 -0
  93. package/dist/state/types.js +2 -0
  94. package/dist/state/types.js.map +1 -0
  95. package/dist/storage/app-storage.d.ts +26 -0
  96. package/dist/storage/app-storage.d.ts.map +1 -0
  97. package/dist/storage/app-storage.js +44 -0
  98. package/dist/storage/app-storage.js.map +1 -0
  99. package/dist/storage/backends/chrome-storage-backend.d.ts +18 -0
  100. package/dist/storage/backends/chrome-storage-backend.d.ts.map +1 -0
  101. package/dist/storage/backends/chrome-storage-backend.js +67 -0
  102. package/dist/storage/backends/chrome-storage-backend.js.map +1 -0
  103. package/dist/storage/backends/indexeddb-backend.d.ts +20 -0
  104. package/dist/storage/backends/indexeddb-backend.d.ts.map +1 -0
  105. package/dist/storage/backends/indexeddb-backend.js +89 -0
  106. package/dist/storage/backends/indexeddb-backend.js.map +1 -0
  107. package/dist/storage/backends/local-storage-backend.d.ts +18 -0
  108. package/dist/storage/backends/local-storage-backend.d.ts.map +1 -0
  109. package/dist/storage/backends/local-storage-backend.js +69 -0
  110. package/dist/storage/backends/local-storage-backend.js.map +1 -0
  111. package/dist/storage/repositories/provider-keys-repository.d.ts +34 -0
  112. package/dist/storage/repositories/provider-keys-repository.d.ts.map +1 -0
  113. package/dist/storage/repositories/provider-keys-repository.js +50 -0
  114. package/dist/storage/repositories/provider-keys-repository.js.map +1 -0
  115. package/dist/storage/repositories/settings-repository.d.ts +34 -0
  116. package/dist/storage/repositories/settings-repository.d.ts.map +1 -0
  117. package/dist/storage/repositories/settings-repository.js +46 -0
  118. package/dist/storage/repositories/settings-repository.js.map +1 -0
  119. package/dist/storage/types.d.ts +43 -0
  120. package/dist/storage/types.d.ts.map +1 -0
  121. package/dist/storage/types.js +2 -0
  122. package/dist/storage/types.js.map +1 -0
  123. package/dist/tools/artifacts/ArtifactElement.d.ts +10 -0
  124. package/dist/tools/artifacts/ArtifactElement.d.ts.map +1 -0
  125. package/dist/tools/artifacts/ArtifactElement.js +12 -0
  126. package/dist/tools/artifacts/ArtifactElement.js.map +1 -0
  127. package/dist/tools/artifacts/HtmlArtifact.d.ts +30 -0
  128. package/dist/tools/artifacts/HtmlArtifact.d.ts.map +1 -0
  129. package/dist/tools/artifacts/HtmlArtifact.js +217 -0
  130. package/dist/tools/artifacts/HtmlArtifact.js.map +1 -0
  131. package/dist/tools/artifacts/MarkdownArtifact.d.ts +20 -0
  132. package/dist/tools/artifacts/MarkdownArtifact.d.ts.map +1 -0
  133. package/dist/tools/artifacts/MarkdownArtifact.js +84 -0
  134. package/dist/tools/artifacts/MarkdownArtifact.js.map +1 -0
  135. package/dist/tools/artifacts/SvgArtifact.d.ts +19 -0
  136. package/dist/tools/artifacts/SvgArtifact.d.ts.map +1 -0
  137. package/dist/tools/artifacts/SvgArtifact.js +80 -0
  138. package/dist/tools/artifacts/SvgArtifact.js.map +1 -0
  139. package/dist/tools/artifacts/TextArtifact.d.ts +20 -0
  140. package/dist/tools/artifacts/TextArtifact.d.ts.map +1 -0
  141. package/dist/tools/artifacts/TextArtifact.js +147 -0
  142. package/dist/tools/artifacts/TextArtifact.js.map +1 -0
  143. package/dist/tools/artifacts/artifacts.d.ts +67 -0
  144. package/dist/tools/artifacts/artifacts.d.ts.map +1 -0
  145. package/dist/tools/artifacts/artifacts.js +836 -0
  146. package/dist/tools/artifacts/artifacts.js.map +1 -0
  147. package/dist/tools/artifacts/index.d.ts +7 -0
  148. package/dist/tools/artifacts/index.d.ts.map +1 -0
  149. package/dist/tools/artifacts/index.js +7 -0
  150. package/dist/tools/artifacts/index.js.map +1 -0
  151. package/dist/tools/index.d.ts +14 -0
  152. package/dist/tools/index.d.ts.map +1 -0
  153. package/dist/tools/index.js +29 -0
  154. package/dist/tools/index.js.map +1 -0
  155. package/dist/tools/javascript-repl.d.ts +43 -0
  156. package/dist/tools/javascript-repl.d.ts.map +1 -0
  157. package/dist/tools/javascript-repl.js +252 -0
  158. package/dist/tools/javascript-repl.js.map +1 -0
  159. package/dist/tools/renderer-registry.d.ts +11 -0
  160. package/dist/tools/renderer-registry.d.ts.map +1 -0
  161. package/dist/tools/renderer-registry.js +15 -0
  162. package/dist/tools/renderer-registry.js.map +1 -0
  163. package/dist/tools/renderers/BashRenderer.d.ts +12 -0
  164. package/dist/tools/renderers/BashRenderer.d.ts.map +1 -0
  165. package/dist/tools/renderers/BashRenderer.js +35 -0
  166. package/dist/tools/renderers/BashRenderer.js.map +1 -0
  167. package/dist/tools/renderers/CalculateRenderer.d.ts +12 -0
  168. package/dist/tools/renderers/CalculateRenderer.d.ts.map +1 -0
  169. package/dist/tools/renderers/CalculateRenderer.js +38 -0
  170. package/dist/tools/renderers/CalculateRenderer.js.map +1 -0
  171. package/dist/tools/renderers/DefaultRenderer.d.ts +8 -0
  172. package/dist/tools/renderers/DefaultRenderer.d.ts.map +1 -0
  173. package/dist/tools/renderers/DefaultRenderer.js +31 -0
  174. package/dist/tools/renderers/DefaultRenderer.js.map +1 -0
  175. package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts +12 -0
  176. package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts.map +1 -0
  177. package/dist/tools/renderers/GetCurrentTimeRenderer.js +30 -0
  178. package/dist/tools/renderers/GetCurrentTimeRenderer.js.map +1 -0
  179. package/dist/tools/types.d.ts +7 -0
  180. package/dist/tools/types.d.ts.map +1 -0
  181. package/dist/tools/types.js +2 -0
  182. package/dist/tools/types.js.map +1 -0
  183. package/dist/utils/attachment-utils.d.ts +19 -0
  184. package/dist/utils/attachment-utils.d.ts.map +1 -0
  185. package/dist/utils/attachment-utils.js +415 -0
  186. package/dist/utils/attachment-utils.js.map +1 -0
  187. package/dist/utils/auth-token.d.ts +3 -0
  188. package/dist/utils/auth-token.d.ts.map +1 -0
  189. package/dist/utils/auth-token.js +19 -0
  190. package/dist/utils/auth-token.js.map +1 -0
  191. package/dist/utils/format.d.ts +6 -0
  192. package/dist/utils/format.d.ts.map +1 -0
  193. package/dist/utils/format.js +47 -0
  194. package/dist/utils/format.js.map +1 -0
  195. package/dist/utils/i18n.d.ts +111 -0
  196. package/dist/utils/i18n.d.ts.map +1 -0
  197. package/dist/utils/i18n.js +224 -0
  198. package/dist/utils/i18n.js.map +1 -0
  199. package/dist/utils/test-sessions.d.ts +347 -0
  200. package/dist/utils/test-sessions.d.ts.map +1 -0
  201. package/dist/utils/test-sessions.js +2215 -0
  202. package/dist/utils/test-sessions.js.map +1 -0
  203. package/example/README.md +61 -0
  204. package/example/index.html +13 -0
  205. package/example/package-lock.json +1965 -0
  206. package/example/package.json +22 -0
  207. package/example/src/app.css +1 -0
  208. package/example/src/main.ts +57 -0
  209. package/example/src/test-sessions.ts +104 -0
  210. package/example/tsconfig.json +15 -0
  211. package/example/vite.config.ts +6 -0
  212. package/package.json +45 -0
  213. package/src/ChatPanel.ts +214 -0
  214. package/src/app.css +44 -0
  215. package/src/components/AgentInterface.ts +316 -0
  216. package/src/components/AttachmentTile.ts +112 -0
  217. package/src/components/ConsoleBlock.ts +67 -0
  218. package/src/components/Input.ts +112 -0
  219. package/src/components/MessageEditor.ts +272 -0
  220. package/src/components/MessageList.ts +82 -0
  221. package/src/components/Messages.ts +310 -0
  222. package/src/components/ProviderKeyInput.ts +170 -0
  223. package/src/components/SandboxedIframe.ts +525 -0
  224. package/src/components/StreamingMessageContainer.ts +101 -0
  225. package/src/dialogs/ApiKeyPromptDialog.ts +76 -0
  226. package/src/dialogs/AttachmentOverlay.ts +635 -0
  227. package/src/dialogs/ModelSelector.ts +324 -0
  228. package/src/dialogs/SettingsDialog.ts +223 -0
  229. package/src/index.ts +63 -0
  230. package/src/state/agent-session.ts +311 -0
  231. package/src/state/transports/AppTransport.ts +363 -0
  232. package/src/state/transports/ProviderTransport.ts +49 -0
  233. package/src/state/transports/index.ts +3 -0
  234. package/src/state/transports/proxy-types.ts +15 -0
  235. package/src/state/transports/types.ts +16 -0
  236. package/src/state/types.ts +11 -0
  237. package/src/storage/app-storage.ts +53 -0
  238. package/src/storage/backends/chrome-storage-backend.ts +82 -0
  239. package/src/storage/backends/indexeddb-backend.ts +107 -0
  240. package/src/storage/backends/local-storage-backend.ts +74 -0
  241. package/src/storage/repositories/provider-keys-repository.ts +55 -0
  242. package/src/storage/repositories/settings-repository.ts +51 -0
  243. package/src/storage/types.ts +48 -0
  244. package/src/tools/artifacts/ArtifactElement.ts +15 -0
  245. package/src/tools/artifacts/HtmlArtifact.ts +221 -0
  246. package/src/tools/artifacts/MarkdownArtifact.ts +81 -0
  247. package/src/tools/artifacts/SvgArtifact.ts +77 -0
  248. package/src/tools/artifacts/TextArtifact.ts +148 -0
  249. package/src/tools/artifacts/artifacts.ts +888 -0
  250. package/src/tools/artifacts/index.ts +6 -0
  251. package/src/tools/index.ts +35 -0
  252. package/src/tools/javascript-repl.ts +309 -0
  253. package/src/tools/renderer-registry.ts +18 -0
  254. package/src/tools/renderers/BashRenderer.ts +45 -0
  255. package/src/tools/renderers/CalculateRenderer.ts +49 -0
  256. package/src/tools/renderers/DefaultRenderer.ts +36 -0
  257. package/src/tools/renderers/GetCurrentTimeRenderer.ts +39 -0
  258. package/src/tools/types.ts +7 -0
  259. package/src/utils/attachment-utils.ts +472 -0
  260. package/src/utils/auth-token.ts +22 -0
  261. package/src/utils/format.ts +42 -0
  262. package/src/utils/i18n.ts +343 -0
  263. package/src/utils/test-sessions.ts +2247 -0
  264. package/tsconfig.build.json +20 -0
  265. package/tsconfig.json +7 -0
@@ -0,0 +1,49 @@
1
+ import { type AgentContext, agentLoop, type Message, type PromptConfig, type UserMessage } from "@mariozechner/pi-ai";
2
+ import { getAppStorage } from "../../storage/app-storage.js";
3
+ import type { AgentRunConfig, AgentTransport } from "./types.js";
4
+
5
+ /**
6
+ * Transport that calls LLM providers directly.
7
+ * Optionally routes calls through a CORS proxy if enabled in settings.
8
+ */
9
+ export class ProviderTransport implements AgentTransport {
10
+ constructor(private readonly getMessages: () => Promise<Message[]>) {}
11
+
12
+ async *run(userMessage: Message, cfg: AgentRunConfig, signal?: AbortSignal) {
13
+ // Get API key from storage
14
+ const apiKey = await getAppStorage().providerKeys.getKey(cfg.model.provider);
15
+ if (!apiKey) {
16
+ throw new Error("no-api-key");
17
+ }
18
+
19
+ // Check if CORS proxy is enabled
20
+ const proxyEnabled = await getAppStorage().settings.get<boolean>("proxy.enabled");
21
+ const proxyUrl = await getAppStorage().settings.get<string>("proxy.url");
22
+
23
+ // Clone model and modify baseUrl if proxy is enabled
24
+ let model = cfg.model;
25
+ if (proxyEnabled && proxyUrl && cfg.model.baseUrl) {
26
+ model = {
27
+ ...cfg.model,
28
+ baseUrl: `${proxyUrl}/?url=${encodeURIComponent(cfg.model.baseUrl)}`,
29
+ };
30
+ }
31
+
32
+ const context: AgentContext = {
33
+ systemPrompt: cfg.systemPrompt,
34
+ messages: await this.getMessages(),
35
+ tools: cfg.tools,
36
+ };
37
+
38
+ const pc: PromptConfig = {
39
+ model,
40
+ reasoning: cfg.reasoning,
41
+ apiKey,
42
+ };
43
+
44
+ // Yield events from agentLoop
45
+ for await (const ev of agentLoop(userMessage as unknown as UserMessage, context, pc, signal)) {
46
+ yield ev;
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./AppTransport.js";
2
+ export * from "./ProviderTransport.js";
3
+ export * from "./types.js";
@@ -0,0 +1,15 @@
1
+ import type { StopReason, Usage } from "@mariozechner/pi-ai";
2
+
3
+ export type ProxyAssistantMessageEvent =
4
+ | { type: "start" }
5
+ | { type: "text_start"; contentIndex: number }
6
+ | { type: "text_delta"; contentIndex: number; delta: string }
7
+ | { type: "text_end"; contentIndex: number; contentSignature?: string }
8
+ | { type: "thinking_start"; contentIndex: number }
9
+ | { type: "thinking_delta"; contentIndex: number; delta: string }
10
+ | { type: "thinking_end"; contentIndex: number; contentSignature?: string }
11
+ | { type: "toolcall_start"; contentIndex: number; id: string; toolName: string }
12
+ | { type: "toolcall_delta"; contentIndex: number; delta: string }
13
+ | { type: "toolcall_end"; contentIndex: number }
14
+ | { type: "done"; reason: Extract<StopReason, "stop" | "length" | "toolUse">; usage: Usage }
15
+ | { type: "error"; reason: Extract<StopReason, "aborted" | "error">; errorMessage: string; usage: Usage };
@@ -0,0 +1,16 @@
1
+ import type { AgentEvent, AgentTool, Message, Model } from "@mariozechner/pi-ai";
2
+
3
+ // The minimal configuration needed to run a turn.
4
+ export interface AgentRunConfig {
5
+ systemPrompt: string;
6
+ tools: AgentTool<any>[];
7
+ model: Model<any>;
8
+ reasoning?: "low" | "medium" | "high";
9
+ }
10
+
11
+ // Events yielded by transports must match the @mariozechner/pi-ai prompt() events.
12
+ // We re-export the Message type above; consumers should use the upstream AgentEvent type.
13
+
14
+ export interface AgentTransport {
15
+ run(userMessage: Message, config: AgentRunConfig, signal?: AbortSignal): AsyncIterable<AgentEvent>; // passthrough of AgentEvent from upstream
16
+ }
@@ -0,0 +1,11 @@
1
+ import type { AssistantMessage, Context } from "@mariozechner/pi-ai";
2
+
3
+ export interface DebugLogEntry {
4
+ timestamp: string;
5
+ request: { provider: string; model: string; context: Context };
6
+ response?: AssistantMessage;
7
+ error?: unknown;
8
+ sseEvents: string[];
9
+ ttft?: number;
10
+ totalTime?: number;
11
+ }
@@ -0,0 +1,53 @@
1
+ import { LocalStorageBackend } from "./backends/local-storage-backend.js";
2
+ import { ProviderKeysRepository } from "./repositories/provider-keys-repository.js";
3
+ import { SettingsRepository } from "./repositories/settings-repository.js";
4
+ import type { AppStorageConfig } from "./types.js";
5
+
6
+ /**
7
+ * High-level storage API aggregating all repositories.
8
+ * Apps configure backends and use repositories through this interface.
9
+ */
10
+ export class AppStorage {
11
+ readonly settings: SettingsRepository;
12
+ readonly providerKeys: ProviderKeysRepository;
13
+
14
+ constructor(config: AppStorageConfig = {}) {
15
+ // Use LocalStorage with prefixes as defaults
16
+ const settingsBackend = config.settings ?? new LocalStorageBackend("settings");
17
+ const providerKeysBackend = config.providerKeys ?? new LocalStorageBackend("providerKeys");
18
+
19
+ this.settings = new SettingsRepository(settingsBackend);
20
+ this.providerKeys = new ProviderKeysRepository(providerKeysBackend);
21
+ }
22
+ }
23
+
24
+ // Global instance management
25
+ let globalAppStorage: AppStorage | null = null;
26
+
27
+ /**
28
+ * Get the global AppStorage instance.
29
+ * Throws if not initialized.
30
+ */
31
+ export function getAppStorage(): AppStorage {
32
+ if (!globalAppStorage) {
33
+ throw new Error("AppStorage not initialized. Call setAppStorage() first.");
34
+ }
35
+ return globalAppStorage;
36
+ }
37
+
38
+ /**
39
+ * Set the global AppStorage instance.
40
+ */
41
+ export function setAppStorage(storage: AppStorage): void {
42
+ globalAppStorage = storage;
43
+ }
44
+
45
+ /**
46
+ * Initialize AppStorage with default configuration if not already set.
47
+ */
48
+ export function initAppStorage(config: AppStorageConfig = {}): AppStorage {
49
+ if (!globalAppStorage) {
50
+ globalAppStorage = new AppStorage(config);
51
+ }
52
+ return globalAppStorage;
53
+ }
@@ -0,0 +1,82 @@
1
+ import type { StorageBackend } from "../types.js";
2
+
3
+ // Chrome extension API types (optional)
4
+ declare const chrome: any;
5
+
6
+ /**
7
+ * Storage backend using chrome.storage.local.
8
+ * Good for: Browser extensions, syncing across devices (with chrome.storage.sync).
9
+ * Limits: ~10MB for local, ~100KB for sync, async API.
10
+ */
11
+ export class ChromeStorageBackend implements StorageBackend {
12
+ constructor(private prefix: string = "") {}
13
+
14
+ private getKey(key: string): string {
15
+ return this.prefix ? `${this.prefix}:${key}` : key;
16
+ }
17
+
18
+ async get<T = unknown>(key: string): Promise<T | null> {
19
+ if (!chrome?.storage?.local) {
20
+ throw new Error("chrome.storage.local is not available");
21
+ }
22
+
23
+ const fullKey = this.getKey(key);
24
+ const result = await chrome.storage.local.get([fullKey]);
25
+ return result[fullKey] !== undefined ? (result[fullKey] as T) : null;
26
+ }
27
+
28
+ async set<T = unknown>(key: string, value: T): Promise<void> {
29
+ if (!chrome?.storage?.local) {
30
+ throw new Error("chrome.storage.local is not available");
31
+ }
32
+
33
+ const fullKey = this.getKey(key);
34
+ await chrome.storage.local.set({ [fullKey]: value });
35
+ }
36
+
37
+ async delete(key: string): Promise<void> {
38
+ if (!chrome?.storage?.local) {
39
+ throw new Error("chrome.storage.local is not available");
40
+ }
41
+
42
+ const fullKey = this.getKey(key);
43
+ await chrome.storage.local.remove(fullKey);
44
+ }
45
+
46
+ async keys(): Promise<string[]> {
47
+ if (!chrome?.storage?.local) {
48
+ throw new Error("chrome.storage.local is not available");
49
+ }
50
+
51
+ const allData = await chrome.storage.local.get(null);
52
+ const allKeys = Object.keys(allData);
53
+ const prefixWithColon = this.prefix ? `${this.prefix}:` : "";
54
+
55
+ if (this.prefix) {
56
+ return allKeys
57
+ .filter((key) => key.startsWith(prefixWithColon))
58
+ .map((key) => key.substring(prefixWithColon.length));
59
+ }
60
+
61
+ return allKeys;
62
+ }
63
+
64
+ async clear(): Promise<void> {
65
+ if (!chrome?.storage?.local) {
66
+ throw new Error("chrome.storage.local is not available");
67
+ }
68
+
69
+ if (this.prefix) {
70
+ const keysToRemove = await this.keys();
71
+ const fullKeys = keysToRemove.map((key) => this.getKey(key));
72
+ await chrome.storage.local.remove(fullKeys);
73
+ } else {
74
+ await chrome.storage.local.clear();
75
+ }
76
+ }
77
+
78
+ async has(key: string): Promise<boolean> {
79
+ const value = await this.get(key);
80
+ return value !== null;
81
+ }
82
+ }
@@ -0,0 +1,107 @@
1
+ import type { StorageBackend } from "../types.js";
2
+
3
+ /**
4
+ * Storage backend using IndexedDB.
5
+ * Good for: Large data, binary blobs, complex queries.
6
+ * Limits: ~50MB-unlimited (browser dependent), async API, more complex.
7
+ */
8
+ export class IndexedDBBackend implements StorageBackend {
9
+ private dbPromise: Promise<IDBDatabase> | null = null;
10
+
11
+ constructor(
12
+ private dbName: string,
13
+ private storeName: string = "keyvalue",
14
+ ) {}
15
+
16
+ private async getDB(): Promise<IDBDatabase> {
17
+ if (this.dbPromise) {
18
+ return this.dbPromise;
19
+ }
20
+
21
+ this.dbPromise = new Promise((resolve, reject) => {
22
+ const request = indexedDB.open(this.dbName, 1);
23
+
24
+ request.onerror = () => reject(request.error);
25
+ request.onsuccess = () => resolve(request.result);
26
+
27
+ request.onupgradeneeded = (event) => {
28
+ const db = (event.target as IDBOpenDBRequest).result;
29
+ if (!db.objectStoreNames.contains(this.storeName)) {
30
+ db.createObjectStore(this.storeName);
31
+ }
32
+ };
33
+ });
34
+
35
+ return this.dbPromise;
36
+ }
37
+
38
+ async get<T = unknown>(key: string): Promise<T | null> {
39
+ const db = await this.getDB();
40
+ return new Promise((resolve, reject) => {
41
+ const transaction = db.transaction(this.storeName, "readonly");
42
+ const store = transaction.objectStore(this.storeName);
43
+ const request = store.get(key);
44
+
45
+ request.onerror = () => reject(request.error);
46
+ request.onsuccess = () => {
47
+ const value = request.result;
48
+ resolve(value !== undefined ? (value as T) : null);
49
+ };
50
+ });
51
+ }
52
+
53
+ async set<T = unknown>(key: string, value: T): Promise<void> {
54
+ const db = await this.getDB();
55
+ return new Promise((resolve, reject) => {
56
+ const transaction = db.transaction(this.storeName, "readwrite");
57
+ const store = transaction.objectStore(this.storeName);
58
+ const request = store.put(value, key);
59
+
60
+ request.onerror = () => reject(request.error);
61
+ request.onsuccess = () => resolve();
62
+ });
63
+ }
64
+
65
+ async delete(key: string): Promise<void> {
66
+ const db = await this.getDB();
67
+ return new Promise((resolve, reject) => {
68
+ const transaction = db.transaction(this.storeName, "readwrite");
69
+ const store = transaction.objectStore(this.storeName);
70
+ const request = store.delete(key);
71
+
72
+ request.onerror = () => reject(request.error);
73
+ request.onsuccess = () => resolve();
74
+ });
75
+ }
76
+
77
+ async keys(): Promise<string[]> {
78
+ const db = await this.getDB();
79
+ return new Promise((resolve, reject) => {
80
+ const transaction = db.transaction(this.storeName, "readonly");
81
+ const store = transaction.objectStore(this.storeName);
82
+ const request = store.getAllKeys();
83
+
84
+ request.onerror = () => reject(request.error);
85
+ request.onsuccess = () => {
86
+ resolve(request.result.map((key) => String(key)));
87
+ };
88
+ });
89
+ }
90
+
91
+ async clear(): Promise<void> {
92
+ const db = await this.getDB();
93
+ return new Promise((resolve, reject) => {
94
+ const transaction = db.transaction(this.storeName, "readwrite");
95
+ const store = transaction.objectStore(this.storeName);
96
+ const request = store.clear();
97
+
98
+ request.onerror = () => reject(request.error);
99
+ request.onsuccess = () => resolve();
100
+ });
101
+ }
102
+
103
+ async has(key: string): Promise<boolean> {
104
+ const value = await this.get(key);
105
+ return value !== null;
106
+ }
107
+ }
@@ -0,0 +1,74 @@
1
+ import type { StorageBackend } from "../types.js";
2
+
3
+ /**
4
+ * Storage backend using browser localStorage.
5
+ * Good for: Simple settings, small data.
6
+ * Limits: ~5MB, synchronous API (wrapped in promises), string-only (JSON serialization).
7
+ */
8
+ export class LocalStorageBackend implements StorageBackend {
9
+ constructor(private prefix: string = "") {}
10
+
11
+ private getKey(key: string): string {
12
+ return this.prefix ? `${this.prefix}:${key}` : key;
13
+ }
14
+
15
+ async get<T = unknown>(key: string): Promise<T | null> {
16
+ const fullKey = this.getKey(key);
17
+ const value = localStorage.getItem(fullKey);
18
+ if (value === null) return null;
19
+
20
+ try {
21
+ return JSON.parse(value) as T;
22
+ } catch {
23
+ // If JSON parse fails, return as string
24
+ return value as T;
25
+ }
26
+ }
27
+
28
+ async set<T = unknown>(key: string, value: T): Promise<void> {
29
+ const fullKey = this.getKey(key);
30
+ const serialized = JSON.stringify(value);
31
+ localStorage.setItem(fullKey, serialized);
32
+ }
33
+
34
+ async delete(key: string): Promise<void> {
35
+ const fullKey = this.getKey(key);
36
+ localStorage.removeItem(fullKey);
37
+ }
38
+
39
+ async keys(): Promise<string[]> {
40
+ const allKeys: string[] = [];
41
+ const prefixWithColon = this.prefix ? `${this.prefix}:` : "";
42
+
43
+ for (let i = 0; i < localStorage.length; i++) {
44
+ const key = localStorage.key(i);
45
+ if (key) {
46
+ if (this.prefix) {
47
+ if (key.startsWith(prefixWithColon)) {
48
+ allKeys.push(key.substring(prefixWithColon.length));
49
+ }
50
+ } else {
51
+ allKeys.push(key);
52
+ }
53
+ }
54
+ }
55
+
56
+ return allKeys;
57
+ }
58
+
59
+ async clear(): Promise<void> {
60
+ if (this.prefix) {
61
+ const keysToRemove = await this.keys();
62
+ for (const key of keysToRemove) {
63
+ await this.delete(key);
64
+ }
65
+ } else {
66
+ localStorage.clear();
67
+ }
68
+ }
69
+
70
+ async has(key: string): Promise<boolean> {
71
+ const fullKey = this.getKey(key);
72
+ return localStorage.getItem(fullKey) !== null;
73
+ }
74
+ }
@@ -0,0 +1,55 @@
1
+ import type { StorageBackend } from "../types.js";
2
+
3
+ /**
4
+ * Repository for managing provider API keys.
5
+ * Provides domain-specific methods for key management.
6
+ */
7
+ export class ProviderKeysRepository {
8
+ constructor(private backend: StorageBackend) {}
9
+
10
+ /**
11
+ * Get the API key for a provider.
12
+ */
13
+ async getKey(provider: string): Promise<string | null> {
14
+ return this.backend.get<string>(`key:${provider}`);
15
+ }
16
+
17
+ /**
18
+ * Set the API key for a provider.
19
+ */
20
+ async setKey(provider: string, key: string): Promise<void> {
21
+ await this.backend.set(`key:${provider}`, key);
22
+ }
23
+
24
+ /**
25
+ * Remove the API key for a provider.
26
+ */
27
+ async removeKey(provider: string): Promise<void> {
28
+ await this.backend.delete(`key:${provider}`);
29
+ }
30
+
31
+ /**
32
+ * Get all providers that have keys stored.
33
+ */
34
+ async getProviders(): Promise<string[]> {
35
+ const allKeys = await this.backend.keys();
36
+ return allKeys.filter((key) => key.startsWith("key:")).map((key) => key.substring(4));
37
+ }
38
+
39
+ /**
40
+ * Check if a provider has a key stored.
41
+ */
42
+ async hasKey(provider: string): Promise<boolean> {
43
+ return this.backend.has(`key:${provider}`);
44
+ }
45
+
46
+ /**
47
+ * Clear all stored API keys.
48
+ */
49
+ async clearAll(): Promise<void> {
50
+ const providers = await this.getProviders();
51
+ for (const provider of providers) {
52
+ await this.removeKey(provider);
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,51 @@
1
+ import type { StorageBackend } from "../types.js";
2
+
3
+ /**
4
+ * Repository for simple application settings (proxy, theme, etc.).
5
+ * Uses a single backend for all settings.
6
+ */
7
+ export class SettingsRepository {
8
+ constructor(private backend: StorageBackend) {}
9
+
10
+ /**
11
+ * Get a setting value by key.
12
+ */
13
+ async get<T = unknown>(key: string): Promise<T | null> {
14
+ return this.backend.get<T>(key);
15
+ }
16
+
17
+ /**
18
+ * Set a setting value.
19
+ */
20
+ async set<T = unknown>(key: string, value: T): Promise<void> {
21
+ await this.backend.set(key, value);
22
+ }
23
+
24
+ /**
25
+ * Delete a setting.
26
+ */
27
+ async delete(key: string): Promise<void> {
28
+ await this.backend.delete(key);
29
+ }
30
+
31
+ /**
32
+ * Get all setting keys.
33
+ */
34
+ async keys(): Promise<string[]> {
35
+ return this.backend.keys();
36
+ }
37
+
38
+ /**
39
+ * Check if a setting exists.
40
+ */
41
+ async has(key: string): Promise<boolean> {
42
+ return this.backend.has(key);
43
+ }
44
+
45
+ /**
46
+ * Clear all settings.
47
+ */
48
+ async clear(): Promise<void> {
49
+ await this.backend.clear();
50
+ }
51
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Base interface for all storage backends.
3
+ * Provides a simple key-value storage abstraction that can be implemented
4
+ * by localStorage, IndexedDB, chrome.storage, or remote APIs.
5
+ */
6
+ export interface StorageBackend {
7
+ /**
8
+ * Get a value by key. Returns null if key doesn't exist.
9
+ */
10
+ get<T = unknown>(key: string): Promise<T | null>;
11
+
12
+ /**
13
+ * Set a value for a key.
14
+ */
15
+ set<T = unknown>(key: string, value: T): Promise<void>;
16
+
17
+ /**
18
+ * Delete a key.
19
+ */
20
+ delete(key: string): Promise<void>;
21
+
22
+ /**
23
+ * Get all keys.
24
+ */
25
+ keys(): Promise<string[]>;
26
+
27
+ /**
28
+ * Clear all data.
29
+ */
30
+ clear(): Promise<void>;
31
+
32
+ /**
33
+ * Check if a key exists.
34
+ */
35
+ has(key: string): Promise<boolean>;
36
+ }
37
+
38
+ /**
39
+ * Options for configuring AppStorage.
40
+ */
41
+ export interface AppStorageConfig {
42
+ /** Backend for simple settings (proxy, theme, etc.) */
43
+ settings?: StorageBackend;
44
+ /** Backend for provider API keys */
45
+ providerKeys?: StorageBackend;
46
+ /** Backend for sessions (chat history, attachments) */
47
+ sessions?: StorageBackend;
48
+ }
@@ -0,0 +1,15 @@
1
+ import { LitElement, type TemplateResult } from "lit";
2
+
3
+ export abstract class ArtifactElement extends LitElement {
4
+ public filename = "";
5
+ public displayTitle = "";
6
+
7
+ protected override createRenderRoot(): HTMLElement | DocumentFragment {
8
+ return this; // light DOM for shared styles
9
+ }
10
+
11
+ public abstract get content(): string;
12
+ public abstract set content(value: string);
13
+
14
+ abstract getHeaderButtons(): TemplateResult | HTMLElement;
15
+ }