From 3188c66ae23ac1a5fbd6978354cb8369e32de8d4 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 17:06:07 +0200 Subject: [PATCH 01/20] Update dependencies and API keys in worker - Upgraded wrangler from version 3.0.0 to 4.81.0 in package.json. - Changed ANTHROPIC_API_KEY to LM_STUDIO_API_KEY in index.ts to reflect new API usage. - Updated ELEVENLABS_VOICE_ID and added ASSEMBLYAI_API_KEY and LM_STUDIO_API_KEY in wrangler.toml. --- leanring-buddy.xcodeproj/project.pbxproj | 4 +- .../xcschemes/xcschememanagement.plist | 14 + ...mblyAIStreamingTranscriptionProvider.swift | 2 +- leanring-buddy/CompanionManager.swift | 18 +- leanring-buddy/CompanionPanelView.swift | 4 +- leanring-buddy/OpenAIAPI.swift | 107 +- .../miniflare-CacheObject/metadata.sqlite | Bin 0 -> 4096 bytes .../miniflare-CacheObject/metadata.sqlite-shm | Bin 0 -> 32768 bytes .../miniflare-CacheObject/metadata.sqlite-wal | Bin 0 -> 8272 bytes .../tmp/bundle-5Kq2AW/checked-fetch.js | 28 + .../middleware-insertion-facade.js | 11 + .../bundle-5Kq2AW/middleware-loader.entry.ts | 134 ++ .../tmp/bundle-OtdA08/checked-fetch.js | 28 + .../middleware-insertion-facade.js | 11 + .../bundle-OtdA08/middleware-loader.entry.ts | 134 ++ worker/.wrangler/tmp/dev-GcNSH4/index.js | 315 +++++ worker/.wrangler/tmp/dev-GcNSH4/index.js.map | 8 + worker/.wrangler/tmp/dev-efgXP0/index.js | 315 +++++ worker/.wrangler/tmp/dev-efgXP0/index.js.map | 8 + worker/package-lock.json | 1145 ++++++++--------- worker/package.json | 2 +- worker/src/index.ts | 4 +- worker/wrangler.toml | 4 +- 23 files changed, 1668 insertions(+), 628 deletions(-) create mode 100644 leanring-buddy.xcodeproj/xcuserdata/zer.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite create mode 100644 worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm create mode 100644 worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal create mode 100644 worker/.wrangler/tmp/bundle-5Kq2AW/checked-fetch.js create mode 100644 worker/.wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js create mode 100644 worker/.wrangler/tmp/bundle-5Kq2AW/middleware-loader.entry.ts create mode 100644 worker/.wrangler/tmp/bundle-OtdA08/checked-fetch.js create mode 100644 worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js create mode 100644 worker/.wrangler/tmp/bundle-OtdA08/middleware-loader.entry.ts create mode 100644 worker/.wrangler/tmp/dev-GcNSH4/index.js create mode 100644 worker/.wrangler/tmp/dev-GcNSH4/index.js.map create mode 100644 worker/.wrangler/tmp/dev-efgXP0/index.js create mode 100644 worker/.wrangler/tmp/dev-efgXP0/index.js.map diff --git a/leanring-buddy.xcodeproj/project.pbxproj b/leanring-buddy.xcodeproj/project.pbxproj index 9d88d4b9..8ca76b97 100644 --- a/leanring-buddy.xcodeproj/project.pbxproj +++ b/leanring-buddy.xcodeproj/project.pbxproj @@ -411,7 +411,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 2UDAY4J48G; + DEVELOPMENT_TEAM = ZY7Q367B6W; ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; @@ -452,7 +452,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 2UDAY4J48G; + DEVELOPMENT_TEAM = ZY7Q367B6W; ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; diff --git a/leanring-buddy.xcodeproj/xcuserdata/zer.xcuserdatad/xcschemes/xcschememanagement.plist b/leanring-buddy.xcodeproj/xcuserdata/zer.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..6c13490a --- /dev/null +++ b/leanring-buddy.xcodeproj/xcuserdata/zer.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + leanring-buddy.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift b/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift index d21286b6..29bde66b 100644 --- a/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift +++ b/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift @@ -19,7 +19,7 @@ struct AssemblyAIStreamingTranscriptionProviderError: LocalizedError { final class AssemblyAIStreamingTranscriptionProvider: BuddyTranscriptionProvider { /// URL for the Cloudflare Worker endpoint that returns a short-lived /// AssemblyAI streaming token. The real API key never leaves the server. - private static let tokenProxyURL = "https://your-worker-name.your-subdomain.workers.dev/transcribe-token" + private static let tokenProxyURL = "http://localhost:8787/transcribe-token" let displayName = "AssemblyAI" let requiresSpeechRecognitionPermission = false diff --git a/leanring-buddy/CompanionManager.swift b/leanring-buddy/CompanionManager.swift index 0234cf19..09c9a24b 100644 --- a/leanring-buddy/CompanionManager.swift +++ b/leanring-buddy/CompanionManager.swift @@ -70,10 +70,10 @@ final class CompanionManager: ObservableObject { /// Base URL for the Cloudflare Worker proxy. All API requests route /// through this so keys never ship in the app binary. - private static let workerBaseURL = "https://your-worker-name.your-subdomain.workers.dev" + private static let workerBaseURL = "http://localhost:8787" - private lazy var claudeAPI: ClaudeAPI = { - return ClaudeAPI(proxyURL: "\(Self.workerBaseURL)/chat", model: selectedModel) + private lazy var openAIAPI: OpenAIAPI = { + return OpenAIAPI(model: selectedModel) }() private lazy var elevenLabsTTSClient: ElevenLabsTTSClient = { @@ -108,12 +108,12 @@ final class CompanionManager: ObservableObject { @Published private(set) var isOverlayVisible: Bool = false /// The Claude model used for voice responses. Persisted to UserDefaults. - @Published var selectedModel: String = UserDefaults.standard.string(forKey: "selectedClaudeModel") ?? "claude-sonnet-4-6" + @Published var selectedModel: String = UserDefaults.standard.string(forKey: "selectedGemmaModel") ?? "gemma-4-e4b-uncensored-hauhaucs-aggressive" func setSelectedModel(_ model: String) { selectedModel = model - UserDefaults.standard.set(model, forKey: "selectedClaudeModel") - claudeAPI.model = model + UserDefaults.standard.set(model, forKey: "selectedGemmaModel") + openAIAPI.model = model } /// User preference for whether the Clicky cursor should be shown. @@ -181,7 +181,7 @@ final class CompanionManager: ObservableObject { bindShortcutTransitions() // Eagerly touch the Claude API so its TLS warmup handshake completes // well before the onboarding demo fires at ~40s into the video. - _ = claudeAPI + _ = openAIAPI // If the user already completed onboarding AND all permissions are // still granted, show the cursor overlay immediately. If permissions @@ -610,7 +610,7 @@ final class CompanionManager: ObservableObject { (userPlaceholder: entry.userTranscript, assistantResponse: entry.assistantResponse) } - let (fullResponseText, _) = try await claudeAPI.analyzeImageStreaming( + let (fullResponseText, _) = try await openAIAPI.analyzeImageStreaming( images: labeledImages, systemPrompt: Self.companionVoiceResponseSystemPrompt, conversationHistory: historyForAPI, @@ -982,7 +982,7 @@ final class CompanionManager: ObservableObject { let dimensionInfo = " (image dimensions: \(cursorScreenCapture.screenshotWidthInPixels)x\(cursorScreenCapture.screenshotHeightInPixels) pixels)" let labeledImages = [(data: cursorScreenCapture.imageData, label: cursorScreenCapture.label + dimensionInfo)] - let (fullResponseText, _) = try await claudeAPI.analyzeImageStreaming( + let (fullResponseText, _) = try await openAIAPI.analyzeImageStreaming( images: labeledImages, systemPrompt: Self.onboardingDemoSystemPrompt, userPrompt: "look around my screen and find something interesting to point at", diff --git a/leanring-buddy/CompanionPanelView.swift b/leanring-buddy/CompanionPanelView.swift index 76789b4c..4d779dcd 100644 --- a/leanring-buddy/CompanionPanelView.swift +++ b/leanring-buddy/CompanionPanelView.swift @@ -607,8 +607,8 @@ struct CompanionPanelView: View { Spacer() HStack(spacing: 0) { - modelOptionButton(label: "Sonnet", modelID: "claude-sonnet-4-6") - modelOptionButton(label: "Opus", modelID: "claude-opus-4-6") + modelOptionButton(label: "Gemma 4", modelID: "gemma-4-e4b-uncensored-hauhaucs-aggressive") + modelOptionButton(label: "Gemma 4 IT", modelID: "gemma-4-e4b-uncensored-hauhaucs-aggressive-it") } .background( RoundedRectangle(cornerRadius: 6, style: .continuous) diff --git a/leanring-buddy/OpenAIAPI.swift b/leanring-buddy/OpenAIAPI.swift index d0c3f2ae..46031e66 100644 --- a/leanring-buddy/OpenAIAPI.swift +++ b/leanring-buddy/OpenAIAPI.swift @@ -9,12 +9,12 @@ import Foundation class OpenAIAPI { private let apiKey: String private let apiURL: URL - private let model: String + var model: String private let session: URLSession - init(apiKey: String, model: String = "gpt-5.2-2025-12-11") { + init(apiKey: String = "sk-lm-aAxaxZte:mOc432tNRd7CWCOh57g3", model: String = "gemma-4-e4b-uncensored-hauhaucs-aggressive") { self.apiKey = apiKey - self.apiURL = URL(string: "https://api.openai.com/v1/chat/completions")! + self.apiURL = URL(string: "http://127.0.0.1:1234/v1/chat/completions")! self.model = model // Use .default instead of .ephemeral so TLS session tickets are cached. @@ -32,7 +32,8 @@ class OpenAIAPI { // Fire a lightweight HEAD request in the background to pre-establish the TLS // connection. This caches the TLS session ticket so the first real API call // (which carries a large image payload) doesn't need a cold TLS handshake. - warmUpTLSConnection() + // Disabled for LM Studio as it causes "Unexpected endpoint or method" errors. + // warmUpTLSConnection() } /// Sends a no-op HEAD request to the API host to establish and cache a TLS session. @@ -139,4 +140,102 @@ class OpenAIAPI { let duration = Date().timeIntervalSince(startTime) return (text: text, duration: duration) } + + /// Send a request to OpenAI with one or more labeled images progressively, via SSE. + func analyzeImageStreaming( + images: [(data: Data, label: String)], + systemPrompt: String, + conversationHistory: [(userPlaceholder: String, assistantResponse: String)] = [], + userPrompt: String, + onTextChunk: @MainActor @Sendable (String) -> Void + ) async throws -> (text: String, duration: TimeInterval) { + let startTime = Date() + + // Build request + var request = URLRequest(url: apiURL) + request.httpMethod = "POST" + request.timeoutInterval = 120 + request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization") + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + // Build messages array + var messages: [[String: Any]] = [] + + // Add system message first + messages.append([ + "role": "system", + "content": systemPrompt + ]) + + // Add conversation history + for (userPlaceholder, assistantResponse) in conversationHistory { + messages.append(["role": "user", "content": userPlaceholder]) + messages.append(["role": "assistant", "content": assistantResponse]) + } + + // Build current message with all labeled images + prompt + var contentBlocks: [[String: Any]] = [] + for image in images { + contentBlocks.append([ + "type": "text", + "text": image.label + ]) + contentBlocks.append([ + "type": "image_url", + "image_url": [ + "url": "data:image/jpeg;base64,\(image.data.base64EncodedString())" + ] + ]) + } + contentBlocks.append([ + "type": "text", + "text": userPrompt + ]) + messages.append(["role": "user", "content": contentBlocks]) + + // Build request body + let body: [String: Any] = [ + "model": model, + "max_tokens": 600, // some local models still require max_tokens instead of max_completion_tokens + "messages": messages, + "stream": true + ] + + let bodyData = try JSONSerialization.data(withJSONObject: body) + request.httpBody = bodyData + let payloadMB = Double(bodyData.count) / 1_048_576.0 + print("🌐 OpenAI streaming request: \(String(format: "%.1f", payloadMB))MB, \(images.count) image(s)") + + let (result, response) = try await session.bytes(for: request) + + guard let httpResponse = response as? HTTPURLResponse, + (200...299).contains(httpResponse.statusCode) else { + throw NSError( + domain: "OpenAIAPI", + code: (response as? HTTPURLResponse)?.statusCode ?? -1, + userInfo: [NSLocalizedDescriptionKey: "API Error: Invalid response"] + ) + } + + var fullText = "" + + for try await line in result.lines { + guard line.hasPrefix("data: ") else { continue } + let jsonString = line.dropFirst(6).trimmingCharacters(in: .whitespaces) + guard jsonString != "[DONE]" else { break } + guard let data = jsonString.data(using: .utf8) else { continue } + + if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let choices = json["choices"] as? [[String: Any]], + let firstChoice = choices.first, + let delta = firstChoice["delta"] as? [String: Any], + let contentChunk = delta["content"] as? String { + fullText += contentChunk + await onTextChunk(contentChunk) + } + } + + let duration = Date().timeIntervalSince(startTime) + return (text: fullText, duration: duration) + } } diff --git a/worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite b/worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..0b58f0dca7d6ce95907cb0b322dc4802f04688c0 GIT binary patch literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WYDXN;00+HAlr;ljiVtj n8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O6ovo**AfQj literal 0 HcmV?d00001 diff --git a/worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm b/worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm new file mode 100644 index 0000000000000000000000000000000000000000..82bb89fa3391a089f34b5a7b97ffde0e9b5e6346 GIT binary patch literal 32768 zcmeI)u?fOJ6b9f^NH11dB4iLd3wN-wvaqpDoe5H=6kNa!?8E`w!Ybwo2Wjnud_Np_ z_c-2zZ-6UbRmxeUv|5grSjYALb{RI;)4l7OXWj1Jk8N={_vMf8bMNRmOfs#zz0c# BC~*J) literal 0 HcmV?d00001 diff --git a/worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal b/worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal new file mode 100644 index 0000000000000000000000000000000000000000..ce7f048f041e1df2ff83cfdcf4bdc64d423a8952 GIT binary patch literal 8272 zcmXr7XKP~6eI&uaAiw|u6=&zoYrCdDp}6%z_wKoM+kirh$m08!3MvGt7CZ~ z!DtAKhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#lBzXyjyM7nhf3Y)mXkOv*`( zPfm+>^l=RGg)^9(gIpa$TopnboqSvs;KB+TTnb>2m|T)y6rY)*5aJpUq7V?|>FXF2 zso?DzsRLC|oSc!GQks*R5?_*;o2uaH7vk#f8U&WqR0#JB@$e4~Q3&!6_jCaUI=G(? z8!y {} + ); + return worker.scheduled(controller, env, ctx); + } + }; + return __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher); + }, + }; +} + +function wrapWorkerEntrypoint( + klass: WorkerEntrypointConstructor +): WorkerEntrypointConstructor { + // If we don't have any middleware defined, just return the handler as is + if ( + __INTERNAL_WRANGLER_MIDDLEWARE__ === undefined || + __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0 + ) { + return klass; + } + // Otherwise, register all middleware once + for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { + __facade_register__(middleware); + } + + // `extend`ing `klass` here so other RPC methods remain callable + return class extends klass { + #fetchDispatcher: ExportedHandlerFetchHandler> = ( + request, + env, + ctx + ) => { + this.env = env; + this.ctx = ctx; + if (super.fetch === undefined) { + throw new Error("Entrypoint class does not define a fetch() function."); + } + return super.fetch(request); + }; + + #dispatcher: Dispatcher = (type, init) => { + if (type === "scheduled" && super.scheduled !== undefined) { + const controller = new __Facade_ScheduledController__( + Date.now(), + init.cron ?? "", + () => {} + ); + return super.scheduled(controller); + } + }; + + fetch(request: Request) { + return __facade_invoke__( + request, + this.env, + this.ctx, + this.#dispatcher, + this.#fetchDispatcher + ); + } + }; +} + +let WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined; +if (typeof ENTRY === "object") { + WRAPPED_ENTRY = wrapExportedHandler(ENTRY); +} else if (typeof ENTRY === "function") { + WRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY); +} +export default WRAPPED_ENTRY; diff --git a/worker/.wrangler/tmp/bundle-OtdA08/checked-fetch.js b/worker/.wrangler/tmp/bundle-OtdA08/checked-fetch.js new file mode 100644 index 00000000..e5e026d3 --- /dev/null +++ b/worker/.wrangler/tmp/bundle-OtdA08/checked-fetch.js @@ -0,0 +1,28 @@ +const urls = new Set(); + +function checkURL(request, init) { + const url = + request instanceof URL + ? request + : new URL( + (typeof request === "string" ? new Request(request, init) : request) + .url + ); + if (url.port && url.port !== "443" && url.protocol === "https:") { + if (!urls.has(url.toString())) { + urls.add(url.toString()); + console.warn( + `WARNING: known issue with \`fetch()\` requests to custom HTTPS ports in published Workers:\n` + + ` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \`wrangler deploy\` command.\n` + ); + } + } +} + +globalThis.fetch = new Proxy(globalThis.fetch, { + apply(target, thisArg, argArray) { + const [request, init] = argArray; + checkURL(request, init); + return Reflect.apply(target, thisArg, argArray); + }, +}); diff --git a/worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js b/worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js new file mode 100644 index 00000000..88b249ed --- /dev/null +++ b/worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js @@ -0,0 +1,11 @@ + import worker, * as OTHER_EXPORTS from "/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts"; + import * as __MIDDLEWARE_0__ from "/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts"; +import * as __MIDDLEWARE_1__ from "/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts"; + + export * from "/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts"; + const MIDDLEWARE_TEST_INJECT = "__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__"; + export const __INTERNAL_WRANGLER_MIDDLEWARE__ = [ + + __MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default + ] + export default worker; \ No newline at end of file diff --git a/worker/.wrangler/tmp/bundle-OtdA08/middleware-loader.entry.ts b/worker/.wrangler/tmp/bundle-OtdA08/middleware-loader.entry.ts new file mode 100644 index 00000000..a3bbcbbc --- /dev/null +++ b/worker/.wrangler/tmp/bundle-OtdA08/middleware-loader.entry.ts @@ -0,0 +1,134 @@ +// This loads all middlewares exposed on the middleware object and then starts +// the invocation chain. The big idea is that we can add these to the middleware +// export dynamically through wrangler, or we can potentially let users directly +// add them as a sort of "plugin" system. + +import ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js"; +import { __facade_invoke__, __facade_register__, Dispatcher } from "/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts"; +import type { WorkerEntrypointConstructor } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js"; + +// Preserve all the exports from the worker +export * from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js"; + +class __Facade_ScheduledController__ implements ScheduledController { + readonly #noRetry: ScheduledController["noRetry"]; + + constructor( + readonly scheduledTime: number, + readonly cron: string, + noRetry: ScheduledController["noRetry"] + ) { + this.#noRetry = noRetry; + } + + noRetry() { + if (!(this instanceof __Facade_ScheduledController__)) { + throw new TypeError("Illegal invocation"); + } + // Need to call native method immediately in case uncaught error thrown + this.#noRetry(); + } +} + +function wrapExportedHandler(worker: ExportedHandler): ExportedHandler { + // If we don't have any middleware defined, just return the handler as is + if ( + __INTERNAL_WRANGLER_MIDDLEWARE__ === undefined || + __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0 + ) { + return worker; + } + // Otherwise, register all middleware once + for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { + __facade_register__(middleware); + } + + const fetchDispatcher: ExportedHandlerFetchHandler = function ( + request, + env, + ctx + ) { + if (worker.fetch === undefined) { + throw new Error("Handler does not export a fetch() function."); + } + return worker.fetch(request, env, ctx); + }; + + return { + ...worker, + fetch(request, env, ctx) { + const dispatcher: Dispatcher = function (type, init) { + if (type === "scheduled" && worker.scheduled !== undefined) { + const controller = new __Facade_ScheduledController__( + Date.now(), + init.cron ?? "", + () => {} + ); + return worker.scheduled(controller, env, ctx); + } + }; + return __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher); + }, + }; +} + +function wrapWorkerEntrypoint( + klass: WorkerEntrypointConstructor +): WorkerEntrypointConstructor { + // If we don't have any middleware defined, just return the handler as is + if ( + __INTERNAL_WRANGLER_MIDDLEWARE__ === undefined || + __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0 + ) { + return klass; + } + // Otherwise, register all middleware once + for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { + __facade_register__(middleware); + } + + // `extend`ing `klass` here so other RPC methods remain callable + return class extends klass { + #fetchDispatcher: ExportedHandlerFetchHandler> = ( + request, + env, + ctx + ) => { + this.env = env; + this.ctx = ctx; + if (super.fetch === undefined) { + throw new Error("Entrypoint class does not define a fetch() function."); + } + return super.fetch(request); + }; + + #dispatcher: Dispatcher = (type, init) => { + if (type === "scheduled" && super.scheduled !== undefined) { + const controller = new __Facade_ScheduledController__( + Date.now(), + init.cron ?? "", + () => {} + ); + return super.scheduled(controller); + } + }; + + fetch(request: Request) { + return __facade_invoke__( + request, + this.env, + this.ctx, + this.#dispatcher, + this.#fetchDispatcher + ); + } + }; +} + +let WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined; +if (typeof ENTRY === "object") { + WRAPPED_ENTRY = wrapExportedHandler(ENTRY); +} else if (typeof ENTRY === "function") { + WRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY); +} +export default WRAPPED_ENTRY; diff --git a/worker/.wrangler/tmp/dev-GcNSH4/index.js b/worker/.wrangler/tmp/dev-GcNSH4/index.js new file mode 100644 index 00000000..619e7043 --- /dev/null +++ b/worker/.wrangler/tmp/dev-GcNSH4/index.js @@ -0,0 +1,315 @@ +var __defProp = Object.defineProperty; +var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); + +// .wrangler/tmp/bundle-5Kq2AW/checked-fetch.js +var urls = /* @__PURE__ */ new Set(); +function checkURL(request, init) { + const url = request instanceof URL ? request : new URL( + (typeof request === "string" ? new Request(request, init) : request).url + ); + if (url.port && url.port !== "443" && url.protocol === "https:") { + if (!urls.has(url.toString())) { + urls.add(url.toString()); + console.warn( + `WARNING: known issue with \`fetch()\` requests to custom HTTPS ports in published Workers: + - ${url.toString()} - the custom port will be ignored when the Worker is published using the \`wrangler deploy\` command. +` + ); + } + } +} +__name(checkURL, "checkURL"); +globalThis.fetch = new Proxy(globalThis.fetch, { + apply(target, thisArg, argArray) { + const [request, init] = argArray; + checkURL(request, init); + return Reflect.apply(target, thisArg, argArray); + } +}); + +// src/index.ts +var src_default = { + async fetch(request, env) { + const url = new URL(request.url); + if (request.method !== "POST") { + return new Response("Method not allowed", { status: 405 }); + } + try { + if (url.pathname === "/chat") { + return await handleChat(request, env); + } + if (url.pathname === "/tts") { + return await handleTTS(request, env); + } + if (url.pathname === "/transcribe-token") { + return await handleTranscribeToken(env); + } + } catch (error) { + console.error(`[${url.pathname}] Unhandled error:`, error); + return new Response( + JSON.stringify({ error: String(error) }), + { status: 500, headers: { "content-type": "application/json" } } + ); + } + return new Response("Not found", { status: 404 }); + } +}; +async function handleChat(request, env) { + const body = await request.text(); + const response = await fetch("https://api.anthropic.com/v1/messages", { + method: "POST", + headers: { + "x-api-key": env.LM_STUDIO_API_KEY, + "anthropic-version": "2023-06-01", + "content-type": "application/json" + }, + body + }); + if (!response.ok) { + const errorBody = await response.text(); + console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`); + return new Response(errorBody, { + status: response.status, + headers: { "content-type": "application/json" } + }); + } + return new Response(response.body, { + status: response.status, + headers: { + "content-type": response.headers.get("content-type") || "text/event-stream", + "cache-control": "no-cache" + } + }); +} +__name(handleChat, "handleChat"); +async function handleTranscribeToken(env) { + const response = await fetch( + "https://streaming.assemblyai.com/v3/token?expires_in_seconds=480", + { + method: "GET", + headers: { + authorization: env.ASSEMBLYAI_API_KEY + } + } + ); + if (!response.ok) { + const errorBody = await response.text(); + console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`); + return new Response(errorBody, { + status: response.status, + headers: { "content-type": "application/json" } + }); + } + const data = await response.text(); + return new Response(data, { + status: 200, + headers: { "content-type": "application/json" } + }); +} +__name(handleTranscribeToken, "handleTranscribeToken"); +async function handleTTS(request, env) { + const body = await request.text(); + const voiceId = env.ELEVENLABS_VOICE_ID; + const response = await fetch( + `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, + { + method: "POST", + headers: { + "xi-api-key": env.ELEVENLABS_API_KEY, + "content-type": "application/json", + accept: "audio/mpeg" + }, + body + } + ); + if (!response.ok) { + const errorBody = await response.text(); + console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`); + return new Response(errorBody, { + status: response.status, + headers: { "content-type": "application/json" } + }); + } + return new Response(response.body, { + status: response.status, + headers: { + "content-type": response.headers.get("content-type") || "audio/mpeg" + } + }); +} +__name(handleTTS, "handleTTS"); + +// node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts +var drainBody = /* @__PURE__ */ __name(async (request, env, _ctx, middlewareCtx) => { + try { + return await middlewareCtx.next(request, env); + } finally { + try { + if (request.body !== null && !request.bodyUsed) { + const reader = request.body.getReader(); + while (!(await reader.read()).done) { + } + } + } catch (e) { + console.error("Failed to drain the unused request body.", e); + } + } +}, "drainBody"); +var middleware_ensure_req_body_drained_default = drainBody; + +// node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts +function reduceError(e) { + return { + name: e?.name, + message: e?.message ?? String(e), + stack: e?.stack, + cause: e?.cause === void 0 ? void 0 : reduceError(e.cause) + }; +} +__name(reduceError, "reduceError"); +var jsonError = /* @__PURE__ */ __name(async (request, env, _ctx, middlewareCtx) => { + try { + return await middlewareCtx.next(request, env); + } catch (e) { + const error = reduceError(e); + return Response.json(error, { + status: 500, + headers: { "MF-Experimental-Error-Stack": "true" } + }); + } +}, "jsonError"); +var middleware_miniflare3_json_error_default = jsonError; + +// .wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js +var __INTERNAL_WRANGLER_MIDDLEWARE__ = [ + middleware_ensure_req_body_drained_default, + middleware_miniflare3_json_error_default +]; +var middleware_insertion_facade_default = src_default; + +// node_modules/wrangler/templates/middleware/common.ts +var __facade_middleware__ = []; +function __facade_register__(...args) { + __facade_middleware__.push(...args.flat()); +} +__name(__facade_register__, "__facade_register__"); +function __facade_invokeChain__(request, env, ctx, dispatch, middlewareChain) { + const [head, ...tail] = middlewareChain; + const middlewareCtx = { + dispatch, + next(newRequest, newEnv) { + return __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail); + } + }; + return head(request, env, ctx, middlewareCtx); +} +__name(__facade_invokeChain__, "__facade_invokeChain__"); +function __facade_invoke__(request, env, ctx, dispatch, finalMiddleware) { + return __facade_invokeChain__(request, env, ctx, dispatch, [ + ...__facade_middleware__, + finalMiddleware + ]); +} +__name(__facade_invoke__, "__facade_invoke__"); + +// .wrangler/tmp/bundle-5Kq2AW/middleware-loader.entry.ts +var __Facade_ScheduledController__ = class ___Facade_ScheduledController__ { + constructor(scheduledTime, cron, noRetry) { + this.scheduledTime = scheduledTime; + this.cron = cron; + this.#noRetry = noRetry; + } + static { + __name(this, "__Facade_ScheduledController__"); + } + #noRetry; + noRetry() { + if (!(this instanceof ___Facade_ScheduledController__)) { + throw new TypeError("Illegal invocation"); + } + this.#noRetry(); + } +}; +function wrapExportedHandler(worker) { + if (__INTERNAL_WRANGLER_MIDDLEWARE__ === void 0 || __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0) { + return worker; + } + for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { + __facade_register__(middleware); + } + const fetchDispatcher = /* @__PURE__ */ __name(function(request, env, ctx) { + if (worker.fetch === void 0) { + throw new Error("Handler does not export a fetch() function."); + } + return worker.fetch(request, env, ctx); + }, "fetchDispatcher"); + return { + ...worker, + fetch(request, env, ctx) { + const dispatcher = /* @__PURE__ */ __name(function(type, init) { + if (type === "scheduled" && worker.scheduled !== void 0) { + const controller = new __Facade_ScheduledController__( + Date.now(), + init.cron ?? "", + () => { + } + ); + return worker.scheduled(controller, env, ctx); + } + }, "dispatcher"); + return __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher); + } + }; +} +__name(wrapExportedHandler, "wrapExportedHandler"); +function wrapWorkerEntrypoint(klass) { + if (__INTERNAL_WRANGLER_MIDDLEWARE__ === void 0 || __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0) { + return klass; + } + for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { + __facade_register__(middleware); + } + return class extends klass { + #fetchDispatcher = /* @__PURE__ */ __name((request, env, ctx) => { + this.env = env; + this.ctx = ctx; + if (super.fetch === void 0) { + throw new Error("Entrypoint class does not define a fetch() function."); + } + return super.fetch(request); + }, "#fetchDispatcher"); + #dispatcher = /* @__PURE__ */ __name((type, init) => { + if (type === "scheduled" && super.scheduled !== void 0) { + const controller = new __Facade_ScheduledController__( + Date.now(), + init.cron ?? "", + () => { + } + ); + return super.scheduled(controller); + } + }, "#dispatcher"); + fetch(request) { + return __facade_invoke__( + request, + this.env, + this.ctx, + this.#dispatcher, + this.#fetchDispatcher + ); + } + }; +} +__name(wrapWorkerEntrypoint, "wrapWorkerEntrypoint"); +var WRAPPED_ENTRY; +if (typeof middleware_insertion_facade_default === "object") { + WRAPPED_ENTRY = wrapExportedHandler(middleware_insertion_facade_default); +} else if (typeof middleware_insertion_facade_default === "function") { + WRAPPED_ENTRY = wrapWorkerEntrypoint(middleware_insertion_facade_default); +} +var middleware_loader_entry_default = WRAPPED_ENTRY; +export { + __INTERNAL_WRANGLER_MIDDLEWARE__, + middleware_loader_entry_default as default +}; +//# sourceMappingURL=index.js.map diff --git a/worker/.wrangler/tmp/dev-GcNSH4/index.js.map b/worker/.wrangler/tmp/dev-GcNSH4/index.js.map new file mode 100644 index 00000000..4bc37cb6 --- /dev/null +++ b/worker/.wrangler/tmp/dev-GcNSH4/index.js.map @@ -0,0 +1,8 @@ +{ + "version": 3, + "sources": ["../bundle-5Kq2AW/checked-fetch.js", "../../../src/index.ts", "../../../node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts", "../../../node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts", "../bundle-5Kq2AW/middleware-insertion-facade.js", "../../../node_modules/wrangler/templates/middleware/common.ts", "../bundle-5Kq2AW/middleware-loader.entry.ts"], + "sourceRoot": "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/dev-GcNSH4", + "sourcesContent": ["const urls = new Set();\n\nfunction checkURL(request, init) {\n\tconst url =\n\t\trequest instanceof URL\n\t\t\t? request\n\t\t\t: new URL(\n\t\t\t\t\t(typeof request === \"string\" ? new Request(request, init) : request)\n\t\t\t\t\t\t.url\n\t\t\t\t);\n\tif (url.port && url.port !== \"443\" && url.protocol === \"https:\") {\n\t\tif (!urls.has(url.toString())) {\n\t\t\turls.add(url.toString());\n\t\t\tconsole.warn(\n\t\t\t\t`WARNING: known issue with \\`fetch()\\` requests to custom HTTPS ports in published Workers:\\n` +\n\t\t\t\t\t` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \\`wrangler deploy\\` command.\\n`\n\t\t\t);\n\t\t}\n\t}\n}\n\nglobalThis.fetch = new Proxy(globalThis.fetch, {\n\tapply(target, thisArg, argArray) {\n\t\tconst [request, init] = argArray;\n\t\tcheckURL(request, init);\n\t\treturn Reflect.apply(target, thisArg, argArray);\n\t},\n});\n", "/**\n * Clicky Proxy Worker\n *\n * Proxies requests to Claude and ElevenLabs APIs so the app never\n * ships with raw API keys. Keys are stored as Cloudflare secrets.\n *\n * Routes:\n * POST /chat \u2192 Anthropic Messages API (streaming)\n * POST /tts \u2192 ElevenLabs TTS API\n */\n\ninterface Env {\n LM_STUDIO_API_KEY: string;\n ELEVENLABS_API_KEY: string;\n ELEVENLABS_VOICE_ID: string;\n ASSEMBLYAI_API_KEY: string;\n}\n\nexport default {\n async fetch(request: Request, env: Env): Promise {\n const url = new URL(request.url);\n\n if (request.method !== \"POST\") {\n return new Response(\"Method not allowed\", { status: 405 });\n }\n\n try {\n if (url.pathname === \"/chat\") {\n return await handleChat(request, env);\n }\n\n if (url.pathname === \"/tts\") {\n return await handleTTS(request, env);\n }\n\n if (url.pathname === \"/transcribe-token\") {\n return await handleTranscribeToken(env);\n }\n } catch (error) {\n console.error(`[${url.pathname}] Unhandled error:`, error);\n return new Response(\n JSON.stringify({ error: String(error) }),\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n }\n\n return new Response(\"Not found\", { status: 404 });\n },\n};\n\nasync function handleChat(request: Request, env: Env): Promise {\n const body = await request.text();\n\n const response = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"x-api-key\": env.LM_STUDIO_API_KEY,\n \"anthropic-version\": \"2023-06-01\",\n \"content-type\": \"application/json\",\n },\n body,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n },\n });\n}\n\nasync function handleTranscribeToken(env: Env): Promise {\n const response = await fetch(\n \"https://streaming.assemblyai.com/v3/token?expires_in_seconds=480\",\n {\n method: \"GET\",\n headers: {\n authorization: env.ASSEMBLYAI_API_KEY,\n },\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n const data = await response.text();\n return new Response(data, {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nasync function handleTTS(request: Request, env: Env): Promise {\n const body = await request.text();\n const voiceId = env.ELEVENLABS_VOICE_ID;\n\n const response = await fetch(\n `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,\n {\n method: \"POST\",\n headers: {\n \"xi-api-key\": env.ELEVENLABS_API_KEY,\n \"content-type\": \"application/json\",\n accept: \"audio/mpeg\",\n },\n body,\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"audio/mpeg\",\n },\n });\n}\n", "import type { Middleware } from \"./common\";\n\nconst drainBody: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} finally {\n\t\ttry {\n\t\t\tif (request.body !== null && !request.bodyUsed) {\n\t\t\t\tconst reader = request.body.getReader();\n\t\t\t\twhile (!(await reader.read()).done) {}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(\"Failed to drain the unused request body.\", e);\n\t\t}\n\t}\n};\n\nexport default drainBody;\n", "import type { Middleware } from \"./common\";\n\ninterface JsonError {\n\tmessage?: string;\n\tname?: string;\n\tstack?: string;\n\tcause?: JsonError;\n}\n\nfunction reduceError(e: any): JsonError {\n\treturn {\n\t\tname: e?.name,\n\t\tmessage: e?.message ?? String(e),\n\t\tstack: e?.stack,\n\t\tcause: e?.cause === undefined ? undefined : reduceError(e.cause),\n\t};\n}\n\n// See comment in `bundle.ts` for details on why this is needed\nconst jsonError: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} catch (e: any) {\n\t\tconst error = reduceError(e);\n\t\treturn Response.json(error, {\n\t\t\tstatus: 500,\n\t\t\theaders: { \"MF-Experimental-Error-Stack\": \"true\" },\n\t\t});\n\t}\n};\n\nexport default jsonError;\n", "\t\t\t\timport worker, * as OTHER_EXPORTS from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\timport * as __MIDDLEWARE_0__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts\";\nimport * as __MIDDLEWARE_1__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts\";\n\n\t\t\t\texport * from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\tconst MIDDLEWARE_TEST_INJECT = \"__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__\";\n\t\t\t\texport const __INTERNAL_WRANGLER_MIDDLEWARE__ = [\n\t\t\t\t\t\n\t\t\t\t\t__MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default\n\t\t\t\t]\n\t\t\t\texport default worker;", "export type Awaitable = T | Promise;\n// TODO: allow dispatching more events?\nexport type Dispatcher = (\n\ttype: \"scheduled\",\n\tinit: { cron?: string }\n) => Awaitable;\n\nexport type IncomingRequest = Request<\n\tunknown,\n\tIncomingRequestCfProperties\n>;\n\nexport interface MiddlewareContext {\n\tdispatch: Dispatcher;\n\tnext(request: IncomingRequest, env: any): Awaitable;\n}\n\nexport type Middleware = (\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tmiddlewareCtx: MiddlewareContext\n) => Awaitable;\n\nconst __facade_middleware__: Middleware[] = [];\n\n// The register functions allow for the insertion of one or many middleware,\n// We register internal middleware first in the stack, but have no way of controlling\n// the order that addMiddleware is run in service workers so need an internal function.\nexport function __facade_register__(...args: (Middleware | Middleware[])[]) {\n\t__facade_middleware__.push(...args.flat());\n}\nexport function __facade_registerInternal__(\n\t...args: (Middleware | Middleware[])[]\n) {\n\t__facade_middleware__.unshift(...args.flat());\n}\n\nfunction __facade_invokeChain__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tmiddlewareChain: Middleware[]\n): Awaitable {\n\tconst [head, ...tail] = middlewareChain;\n\tconst middlewareCtx: MiddlewareContext = {\n\t\tdispatch,\n\t\tnext(newRequest, newEnv) {\n\t\t\treturn __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);\n\t\t},\n\t};\n\treturn head(request, env, ctx, middlewareCtx);\n}\n\nexport function __facade_invoke__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tfinalMiddleware: Middleware\n): Awaitable {\n\treturn __facade_invokeChain__(request, env, ctx, dispatch, [\n\t\t...__facade_middleware__,\n\t\tfinalMiddleware,\n\t]);\n}\n", "// This loads all middlewares exposed on the middleware object and then starts\n// the invocation chain. The big idea is that we can add these to the middleware\n// export dynamically through wrangler, or we can potentially let users directly\n// add them as a sort of \"plugin\" system.\n\nimport ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js\";\nimport { __facade_invoke__, __facade_register__, Dispatcher } from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts\";\nimport type { WorkerEntrypointConstructor } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js\";\n\n// Preserve all the exports from the worker\nexport * from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js\";\n\nclass __Facade_ScheduledController__ implements ScheduledController {\n\treadonly #noRetry: ScheduledController[\"noRetry\"];\n\n\tconstructor(\n\t\treadonly scheduledTime: number,\n\t\treadonly cron: string,\n\t\tnoRetry: ScheduledController[\"noRetry\"]\n\t) {\n\t\tthis.#noRetry = noRetry;\n\t}\n\n\tnoRetry() {\n\t\tif (!(this instanceof __Facade_ScheduledController__)) {\n\t\t\tthrow new TypeError(\"Illegal invocation\");\n\t\t}\n\t\t// Need to call native method immediately in case uncaught error thrown\n\t\tthis.#noRetry();\n\t}\n}\n\nfunction wrapExportedHandler(worker: ExportedHandler): ExportedHandler {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn worker;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\tconst fetchDispatcher: ExportedHandlerFetchHandler = function (\n\t\trequest,\n\t\tenv,\n\t\tctx\n\t) {\n\t\tif (worker.fetch === undefined) {\n\t\t\tthrow new Error(\"Handler does not export a fetch() function.\");\n\t\t}\n\t\treturn worker.fetch(request, env, ctx);\n\t};\n\n\treturn {\n\t\t...worker,\n\t\tfetch(request, env, ctx) {\n\t\t\tconst dispatcher: Dispatcher = function (type, init) {\n\t\t\t\tif (type === \"scheduled\" && worker.scheduled !== undefined) {\n\t\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\t\tDate.now(),\n\t\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t\t() => {}\n\t\t\t\t\t);\n\t\t\t\t\treturn worker.scheduled(controller, env, ctx);\n\t\t\t\t}\n\t\t\t};\n\t\t\treturn __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);\n\t\t},\n\t};\n}\n\nfunction wrapWorkerEntrypoint(\n\tklass: WorkerEntrypointConstructor\n): WorkerEntrypointConstructor {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn klass;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\t// `extend`ing `klass` here so other RPC methods remain callable\n\treturn class extends klass {\n\t\t#fetchDispatcher: ExportedHandlerFetchHandler> = (\n\t\t\trequest,\n\t\t\tenv,\n\t\t\tctx\n\t\t) => {\n\t\t\tthis.env = env;\n\t\t\tthis.ctx = ctx;\n\t\t\tif (super.fetch === undefined) {\n\t\t\t\tthrow new Error(\"Entrypoint class does not define a fetch() function.\");\n\t\t\t}\n\t\t\treturn super.fetch(request);\n\t\t};\n\n\t\t#dispatcher: Dispatcher = (type, init) => {\n\t\t\tif (type === \"scheduled\" && super.scheduled !== undefined) {\n\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\tDate.now(),\n\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t() => {}\n\t\t\t\t);\n\t\t\t\treturn super.scheduled(controller);\n\t\t\t}\n\t\t};\n\n\t\tfetch(request: Request) {\n\t\t\treturn __facade_invoke__(\n\t\t\t\trequest,\n\t\t\t\tthis.env,\n\t\t\t\tthis.ctx,\n\t\t\t\tthis.#dispatcher,\n\t\t\t\tthis.#fetchDispatcher\n\t\t\t);\n\t\t}\n\t};\n}\n\nlet WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;\nif (typeof ENTRY === \"object\") {\n\tWRAPPED_ENTRY = wrapExportedHandler(ENTRY);\n} else if (typeof ENTRY === \"function\") {\n\tWRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);\n}\nexport default WRAPPED_ENTRY;\n"], + "mappings": "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", + "names": [] +} diff --git a/worker/.wrangler/tmp/dev-efgXP0/index.js b/worker/.wrangler/tmp/dev-efgXP0/index.js new file mode 100644 index 00000000..6213bd10 --- /dev/null +++ b/worker/.wrangler/tmp/dev-efgXP0/index.js @@ -0,0 +1,315 @@ +var __defProp = Object.defineProperty; +var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); + +// .wrangler/tmp/bundle-OtdA08/checked-fetch.js +var urls = /* @__PURE__ */ new Set(); +function checkURL(request, init) { + const url = request instanceof URL ? request : new URL( + (typeof request === "string" ? new Request(request, init) : request).url + ); + if (url.port && url.port !== "443" && url.protocol === "https:") { + if (!urls.has(url.toString())) { + urls.add(url.toString()); + console.warn( + `WARNING: known issue with \`fetch()\` requests to custom HTTPS ports in published Workers: + - ${url.toString()} - the custom port will be ignored when the Worker is published using the \`wrangler deploy\` command. +` + ); + } + } +} +__name(checkURL, "checkURL"); +globalThis.fetch = new Proxy(globalThis.fetch, { + apply(target, thisArg, argArray) { + const [request, init] = argArray; + checkURL(request, init); + return Reflect.apply(target, thisArg, argArray); + } +}); + +// src/index.ts +var src_default = { + async fetch(request, env) { + const url = new URL(request.url); + if (request.method !== "POST") { + return new Response("Method not allowed", { status: 405 }); + } + try { + if (url.pathname === "/chat") { + return await handleChat(request, env); + } + if (url.pathname === "/tts") { + return await handleTTS(request, env); + } + if (url.pathname === "/transcribe-token") { + return await handleTranscribeToken(env); + } + } catch (error) { + console.error(`[${url.pathname}] Unhandled error:`, error); + return new Response( + JSON.stringify({ error: String(error) }), + { status: 500, headers: { "content-type": "application/json" } } + ); + } + return new Response("Not found", { status: 404 }); + } +}; +async function handleChat(request, env) { + const body = await request.text(); + const response = await fetch("https://api.anthropic.com/v1/messages", { + method: "POST", + headers: { + "x-api-key": env.LM_STUDIO_API_KEY, + "anthropic-version": "2023-06-01", + "content-type": "application/json" + }, + body + }); + if (!response.ok) { + const errorBody = await response.text(); + console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`); + return new Response(errorBody, { + status: response.status, + headers: { "content-type": "application/json" } + }); + } + return new Response(response.body, { + status: response.status, + headers: { + "content-type": response.headers.get("content-type") || "text/event-stream", + "cache-control": "no-cache" + } + }); +} +__name(handleChat, "handleChat"); +async function handleTranscribeToken(env) { + const response = await fetch( + "https://streaming.assemblyai.com/v3/token?expires_in_seconds=480", + { + method: "GET", + headers: { + authorization: env.ASSEMBLYAI_API_KEY + } + } + ); + if (!response.ok) { + const errorBody = await response.text(); + console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`); + return new Response(errorBody, { + status: response.status, + headers: { "content-type": "application/json" } + }); + } + const data = await response.text(); + return new Response(data, { + status: 200, + headers: { "content-type": "application/json" } + }); +} +__name(handleTranscribeToken, "handleTranscribeToken"); +async function handleTTS(request, env) { + const body = await request.text(); + const voiceId = env.ELEVENLABS_VOICE_ID; + const response = await fetch( + `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, + { + method: "POST", + headers: { + "xi-api-key": env.ELEVENLABS_API_KEY, + "content-type": "application/json", + accept: "audio/mpeg" + }, + body + } + ); + if (!response.ok) { + const errorBody = await response.text(); + console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`); + return new Response(errorBody, { + status: response.status, + headers: { "content-type": "application/json" } + }); + } + return new Response(response.body, { + status: response.status, + headers: { + "content-type": response.headers.get("content-type") || "audio/mpeg" + } + }); +} +__name(handleTTS, "handleTTS"); + +// node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts +var drainBody = /* @__PURE__ */ __name(async (request, env, _ctx, middlewareCtx) => { + try { + return await middlewareCtx.next(request, env); + } finally { + try { + if (request.body !== null && !request.bodyUsed) { + const reader = request.body.getReader(); + while (!(await reader.read()).done) { + } + } + } catch (e) { + console.error("Failed to drain the unused request body.", e); + } + } +}, "drainBody"); +var middleware_ensure_req_body_drained_default = drainBody; + +// node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts +function reduceError(e) { + return { + name: e?.name, + message: e?.message ?? String(e), + stack: e?.stack, + cause: e?.cause === void 0 ? void 0 : reduceError(e.cause) + }; +} +__name(reduceError, "reduceError"); +var jsonError = /* @__PURE__ */ __name(async (request, env, _ctx, middlewareCtx) => { + try { + return await middlewareCtx.next(request, env); + } catch (e) { + const error = reduceError(e); + return Response.json(error, { + status: 500, + headers: { "MF-Experimental-Error-Stack": "true" } + }); + } +}, "jsonError"); +var middleware_miniflare3_json_error_default = jsonError; + +// .wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js +var __INTERNAL_WRANGLER_MIDDLEWARE__ = [ + middleware_ensure_req_body_drained_default, + middleware_miniflare3_json_error_default +]; +var middleware_insertion_facade_default = src_default; + +// node_modules/wrangler/templates/middleware/common.ts +var __facade_middleware__ = []; +function __facade_register__(...args) { + __facade_middleware__.push(...args.flat()); +} +__name(__facade_register__, "__facade_register__"); +function __facade_invokeChain__(request, env, ctx, dispatch, middlewareChain) { + const [head, ...tail] = middlewareChain; + const middlewareCtx = { + dispatch, + next(newRequest, newEnv) { + return __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail); + } + }; + return head(request, env, ctx, middlewareCtx); +} +__name(__facade_invokeChain__, "__facade_invokeChain__"); +function __facade_invoke__(request, env, ctx, dispatch, finalMiddleware) { + return __facade_invokeChain__(request, env, ctx, dispatch, [ + ...__facade_middleware__, + finalMiddleware + ]); +} +__name(__facade_invoke__, "__facade_invoke__"); + +// .wrangler/tmp/bundle-OtdA08/middleware-loader.entry.ts +var __Facade_ScheduledController__ = class ___Facade_ScheduledController__ { + constructor(scheduledTime, cron, noRetry) { + this.scheduledTime = scheduledTime; + this.cron = cron; + this.#noRetry = noRetry; + } + static { + __name(this, "__Facade_ScheduledController__"); + } + #noRetry; + noRetry() { + if (!(this instanceof ___Facade_ScheduledController__)) { + throw new TypeError("Illegal invocation"); + } + this.#noRetry(); + } +}; +function wrapExportedHandler(worker) { + if (__INTERNAL_WRANGLER_MIDDLEWARE__ === void 0 || __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0) { + return worker; + } + for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { + __facade_register__(middleware); + } + const fetchDispatcher = /* @__PURE__ */ __name(function(request, env, ctx) { + if (worker.fetch === void 0) { + throw new Error("Handler does not export a fetch() function."); + } + return worker.fetch(request, env, ctx); + }, "fetchDispatcher"); + return { + ...worker, + fetch(request, env, ctx) { + const dispatcher = /* @__PURE__ */ __name(function(type, init) { + if (type === "scheduled" && worker.scheduled !== void 0) { + const controller = new __Facade_ScheduledController__( + Date.now(), + init.cron ?? "", + () => { + } + ); + return worker.scheduled(controller, env, ctx); + } + }, "dispatcher"); + return __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher); + } + }; +} +__name(wrapExportedHandler, "wrapExportedHandler"); +function wrapWorkerEntrypoint(klass) { + if (__INTERNAL_WRANGLER_MIDDLEWARE__ === void 0 || __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0) { + return klass; + } + for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { + __facade_register__(middleware); + } + return class extends klass { + #fetchDispatcher = /* @__PURE__ */ __name((request, env, ctx) => { + this.env = env; + this.ctx = ctx; + if (super.fetch === void 0) { + throw new Error("Entrypoint class does not define a fetch() function."); + } + return super.fetch(request); + }, "#fetchDispatcher"); + #dispatcher = /* @__PURE__ */ __name((type, init) => { + if (type === "scheduled" && super.scheduled !== void 0) { + const controller = new __Facade_ScheduledController__( + Date.now(), + init.cron ?? "", + () => { + } + ); + return super.scheduled(controller); + } + }, "#dispatcher"); + fetch(request) { + return __facade_invoke__( + request, + this.env, + this.ctx, + this.#dispatcher, + this.#fetchDispatcher + ); + } + }; +} +__name(wrapWorkerEntrypoint, "wrapWorkerEntrypoint"); +var WRAPPED_ENTRY; +if (typeof middleware_insertion_facade_default === "object") { + WRAPPED_ENTRY = wrapExportedHandler(middleware_insertion_facade_default); +} else if (typeof middleware_insertion_facade_default === "function") { + WRAPPED_ENTRY = wrapWorkerEntrypoint(middleware_insertion_facade_default); +} +var middleware_loader_entry_default = WRAPPED_ENTRY; +export { + __INTERNAL_WRANGLER_MIDDLEWARE__, + middleware_loader_entry_default as default +}; +//# sourceMappingURL=index.js.map diff --git a/worker/.wrangler/tmp/dev-efgXP0/index.js.map b/worker/.wrangler/tmp/dev-efgXP0/index.js.map new file mode 100644 index 00000000..cbce7d5c --- /dev/null +++ b/worker/.wrangler/tmp/dev-efgXP0/index.js.map @@ -0,0 +1,8 @@ +{ + "version": 3, + "sources": ["../bundle-OtdA08/checked-fetch.js", "../../../src/index.ts", "../../../node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts", "../../../node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts", "../bundle-OtdA08/middleware-insertion-facade.js", "../../../node_modules/wrangler/templates/middleware/common.ts", "../bundle-OtdA08/middleware-loader.entry.ts"], + "sourceRoot": "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/dev-efgXP0", + "sourcesContent": ["const urls = new Set();\n\nfunction checkURL(request, init) {\n\tconst url =\n\t\trequest instanceof URL\n\t\t\t? request\n\t\t\t: new URL(\n\t\t\t\t\t(typeof request === \"string\" ? new Request(request, init) : request)\n\t\t\t\t\t\t.url\n\t\t\t\t);\n\tif (url.port && url.port !== \"443\" && url.protocol === \"https:\") {\n\t\tif (!urls.has(url.toString())) {\n\t\t\turls.add(url.toString());\n\t\t\tconsole.warn(\n\t\t\t\t`WARNING: known issue with \\`fetch()\\` requests to custom HTTPS ports in published Workers:\\n` +\n\t\t\t\t\t` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \\`wrangler deploy\\` command.\\n`\n\t\t\t);\n\t\t}\n\t}\n}\n\nglobalThis.fetch = new Proxy(globalThis.fetch, {\n\tapply(target, thisArg, argArray) {\n\t\tconst [request, init] = argArray;\n\t\tcheckURL(request, init);\n\t\treturn Reflect.apply(target, thisArg, argArray);\n\t},\n});\n", "/**\n * Clicky Proxy Worker\n *\n * Proxies requests to Claude and ElevenLabs APIs so the app never\n * ships with raw API keys. Keys are stored as Cloudflare secrets.\n *\n * Routes:\n * POST /chat \u2192 Anthropic Messages API (streaming)\n * POST /tts \u2192 ElevenLabs TTS API\n */\n\ninterface Env {\n LM_STUDIO_API_KEY: string;\n ELEVENLABS_API_KEY: string;\n ELEVENLABS_VOICE_ID: string;\n ASSEMBLYAI_API_KEY: string;\n}\n\nexport default {\n async fetch(request: Request, env: Env): Promise {\n const url = new URL(request.url);\n\n if (request.method !== \"POST\") {\n return new Response(\"Method not allowed\", { status: 405 });\n }\n\n try {\n if (url.pathname === \"/chat\") {\n return await handleChat(request, env);\n }\n\n if (url.pathname === \"/tts\") {\n return await handleTTS(request, env);\n }\n\n if (url.pathname === \"/transcribe-token\") {\n return await handleTranscribeToken(env);\n }\n } catch (error) {\n console.error(`[${url.pathname}] Unhandled error:`, error);\n return new Response(\n JSON.stringify({ error: String(error) }),\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n }\n\n return new Response(\"Not found\", { status: 404 });\n },\n};\n\nasync function handleChat(request: Request, env: Env): Promise {\n const body = await request.text();\n\n const response = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"x-api-key\": env.LM_STUDIO_API_KEY,\n \"anthropic-version\": \"2023-06-01\",\n \"content-type\": \"application/json\",\n },\n body,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n },\n });\n}\n\nasync function handleTranscribeToken(env: Env): Promise {\n const response = await fetch(\n \"https://streaming.assemblyai.com/v3/token?expires_in_seconds=480\",\n {\n method: \"GET\",\n headers: {\n authorization: env.ASSEMBLYAI_API_KEY,\n },\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n const data = await response.text();\n return new Response(data, {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nasync function handleTTS(request: Request, env: Env): Promise {\n const body = await request.text();\n const voiceId = env.ELEVENLABS_VOICE_ID;\n\n const response = await fetch(\n `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,\n {\n method: \"POST\",\n headers: {\n \"xi-api-key\": env.ELEVENLABS_API_KEY,\n \"content-type\": \"application/json\",\n accept: \"audio/mpeg\",\n },\n body,\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"audio/mpeg\",\n },\n });\n}\n", "import type { Middleware } from \"./common\";\n\nconst drainBody: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} finally {\n\t\ttry {\n\t\t\tif (request.body !== null && !request.bodyUsed) {\n\t\t\t\tconst reader = request.body.getReader();\n\t\t\t\twhile (!(await reader.read()).done) {}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(\"Failed to drain the unused request body.\", e);\n\t\t}\n\t}\n};\n\nexport default drainBody;\n", "import type { Middleware } from \"./common\";\n\ninterface JsonError {\n\tmessage?: string;\n\tname?: string;\n\tstack?: string;\n\tcause?: JsonError;\n}\n\nfunction reduceError(e: any): JsonError {\n\treturn {\n\t\tname: e?.name,\n\t\tmessage: e?.message ?? String(e),\n\t\tstack: e?.stack,\n\t\tcause: e?.cause === undefined ? undefined : reduceError(e.cause),\n\t};\n}\n\n// See comment in `bundle.ts` for details on why this is needed\nconst jsonError: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} catch (e: any) {\n\t\tconst error = reduceError(e);\n\t\treturn Response.json(error, {\n\t\t\tstatus: 500,\n\t\t\theaders: { \"MF-Experimental-Error-Stack\": \"true\" },\n\t\t});\n\t}\n};\n\nexport default jsonError;\n", "\t\t\t\timport worker, * as OTHER_EXPORTS from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\timport * as __MIDDLEWARE_0__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts\";\nimport * as __MIDDLEWARE_1__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts\";\n\n\t\t\t\texport * from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\tconst MIDDLEWARE_TEST_INJECT = \"__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__\";\n\t\t\t\texport const __INTERNAL_WRANGLER_MIDDLEWARE__ = [\n\t\t\t\t\t\n\t\t\t\t\t__MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default\n\t\t\t\t]\n\t\t\t\texport default worker;", "export type Awaitable = T | Promise;\n// TODO: allow dispatching more events?\nexport type Dispatcher = (\n\ttype: \"scheduled\",\n\tinit: { cron?: string }\n) => Awaitable;\n\nexport type IncomingRequest = Request<\n\tunknown,\n\tIncomingRequestCfProperties\n>;\n\nexport interface MiddlewareContext {\n\tdispatch: Dispatcher;\n\tnext(request: IncomingRequest, env: any): Awaitable;\n}\n\nexport type Middleware = (\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tmiddlewareCtx: MiddlewareContext\n) => Awaitable;\n\nconst __facade_middleware__: Middleware[] = [];\n\n// The register functions allow for the insertion of one or many middleware,\n// We register internal middleware first in the stack, but have no way of controlling\n// the order that addMiddleware is run in service workers so need an internal function.\nexport function __facade_register__(...args: (Middleware | Middleware[])[]) {\n\t__facade_middleware__.push(...args.flat());\n}\nexport function __facade_registerInternal__(\n\t...args: (Middleware | Middleware[])[]\n) {\n\t__facade_middleware__.unshift(...args.flat());\n}\n\nfunction __facade_invokeChain__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tmiddlewareChain: Middleware[]\n): Awaitable {\n\tconst [head, ...tail] = middlewareChain;\n\tconst middlewareCtx: MiddlewareContext = {\n\t\tdispatch,\n\t\tnext(newRequest, newEnv) {\n\t\t\treturn __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);\n\t\t},\n\t};\n\treturn head(request, env, ctx, middlewareCtx);\n}\n\nexport function __facade_invoke__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tfinalMiddleware: Middleware\n): Awaitable {\n\treturn __facade_invokeChain__(request, env, ctx, dispatch, [\n\t\t...__facade_middleware__,\n\t\tfinalMiddleware,\n\t]);\n}\n", "// This loads all middlewares exposed on the middleware object and then starts\n// the invocation chain. The big idea is that we can add these to the middleware\n// export dynamically through wrangler, or we can potentially let users directly\n// add them as a sort of \"plugin\" system.\n\nimport ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js\";\nimport { __facade_invoke__, __facade_register__, Dispatcher } from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts\";\nimport type { WorkerEntrypointConstructor } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js\";\n\n// Preserve all the exports from the worker\nexport * from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js\";\n\nclass __Facade_ScheduledController__ implements ScheduledController {\n\treadonly #noRetry: ScheduledController[\"noRetry\"];\n\n\tconstructor(\n\t\treadonly scheduledTime: number,\n\t\treadonly cron: string,\n\t\tnoRetry: ScheduledController[\"noRetry\"]\n\t) {\n\t\tthis.#noRetry = noRetry;\n\t}\n\n\tnoRetry() {\n\t\tif (!(this instanceof __Facade_ScheduledController__)) {\n\t\t\tthrow new TypeError(\"Illegal invocation\");\n\t\t}\n\t\t// Need to call native method immediately in case uncaught error thrown\n\t\tthis.#noRetry();\n\t}\n}\n\nfunction wrapExportedHandler(worker: ExportedHandler): ExportedHandler {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn worker;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\tconst fetchDispatcher: ExportedHandlerFetchHandler = function (\n\t\trequest,\n\t\tenv,\n\t\tctx\n\t) {\n\t\tif (worker.fetch === undefined) {\n\t\t\tthrow new Error(\"Handler does not export a fetch() function.\");\n\t\t}\n\t\treturn worker.fetch(request, env, ctx);\n\t};\n\n\treturn {\n\t\t...worker,\n\t\tfetch(request, env, ctx) {\n\t\t\tconst dispatcher: Dispatcher = function (type, init) {\n\t\t\t\tif (type === \"scheduled\" && worker.scheduled !== undefined) {\n\t\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\t\tDate.now(),\n\t\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t\t() => {}\n\t\t\t\t\t);\n\t\t\t\t\treturn worker.scheduled(controller, env, ctx);\n\t\t\t\t}\n\t\t\t};\n\t\t\treturn __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);\n\t\t},\n\t};\n}\n\nfunction wrapWorkerEntrypoint(\n\tklass: WorkerEntrypointConstructor\n): WorkerEntrypointConstructor {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn klass;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\t// `extend`ing `klass` here so other RPC methods remain callable\n\treturn class extends klass {\n\t\t#fetchDispatcher: ExportedHandlerFetchHandler> = (\n\t\t\trequest,\n\t\t\tenv,\n\t\t\tctx\n\t\t) => {\n\t\t\tthis.env = env;\n\t\t\tthis.ctx = ctx;\n\t\t\tif (super.fetch === undefined) {\n\t\t\t\tthrow new Error(\"Entrypoint class does not define a fetch() function.\");\n\t\t\t}\n\t\t\treturn super.fetch(request);\n\t\t};\n\n\t\t#dispatcher: Dispatcher = (type, init) => {\n\t\t\tif (type === \"scheduled\" && super.scheduled !== undefined) {\n\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\tDate.now(),\n\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t() => {}\n\t\t\t\t);\n\t\t\t\treturn super.scheduled(controller);\n\t\t\t}\n\t\t};\n\n\t\tfetch(request: Request) {\n\t\t\treturn __facade_invoke__(\n\t\t\t\trequest,\n\t\t\t\tthis.env,\n\t\t\t\tthis.ctx,\n\t\t\t\tthis.#dispatcher,\n\t\t\t\tthis.#fetchDispatcher\n\t\t\t);\n\t\t}\n\t};\n}\n\nlet WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;\nif (typeof ENTRY === \"object\") {\n\tWRAPPED_ENTRY = wrapExportedHandler(ENTRY);\n} else if (typeof ENTRY === \"function\") {\n\tWRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);\n}\nexport default WRAPPED_ENTRY;\n"], + "mappings": "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", + "names": [] +} diff --git a/worker/package-lock.json b/worker/package-lock.json index c2383cc1..c3752f47 100644 --- a/worker/package-lock.json +++ b/worker/package-lock.json @@ -6,31 +6,28 @@ "": { "name": "clicky-proxy", "devDependencies": { - "wrangler": "^3.0.0" + "wrangler": "^4.81.0" } }, "node_modules/@cloudflare/kv-asset-handler": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz", - "integrity": "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz", + "integrity": "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==", "dev": true, "license": "MIT OR Apache-2.0", - "dependencies": { - "mime": "^3.0.0" - }, "engines": { - "node": ">=16.13" + "node": ">=18.0.0" } }, "node_modules/@cloudflare/unenv-preset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.0.2.tgz", - "integrity": "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.0.tgz", + "integrity": "sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg==", "dev": true, "license": "MIT OR Apache-2.0", "peerDependencies": { - "unenv": "2.0.0-rc.14", - "workerd": "^1.20250124.0" + "unenv": "2.0.0-rc.24", + "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" }, "peerDependenciesMeta": { "workerd": { @@ -39,9 +36,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250718.0.tgz", - "integrity": "sha512-FHf4t7zbVN8yyXgQ/r/GqLPaYZSGUVzeR7RnL28Mwj2djyw2ZergvytVc7fdGcczl6PQh+VKGfZCfUqpJlbi9g==", + "version": "1.20260405.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260405.1.tgz", + "integrity": "sha512-EbmdBcmeIGogKG4V1odSWQe7z4rHssUD4iaXv0cXA22/MFrzH3iQT0R+FJFyhucGtih/9B9E+6j0QbSQD8xT3w==", "cpu": [ "x64" ], @@ -56,9 +53,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250718.0.tgz", - "integrity": "sha512-fUiyUJYyqqp4NqJ0YgGtp4WJh/II/YZsUnEb6vVy5Oeas8lUOxnN+ZOJ8N/6/5LQCVAtYCChRiIrBbfhTn5Z8Q==", + "version": "1.20260405.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260405.1.tgz", + "integrity": "sha512-r44r418bOQtoP+Odu+L/BQM9q5cRSXRd1N167PgZQIo4MlqzTwHO4L0wwXhxbcV/PF46rrQre/uTFS8R0R+xSQ==", "cpu": [ "arm64" ], @@ -73,9 +70,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250718.0.tgz", - "integrity": "sha512-5+eb3rtJMiEwp08Kryqzzu8d1rUcK+gdE442auo5eniMpT170Dz0QxBrqkg2Z48SFUPYbj+6uknuA5tzdRSUSg==", + "version": "1.20260405.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260405.1.tgz", + "integrity": "sha512-Aaq3RWnaTCzMBo77wC8fjOx+SFdO/rlcXa6HAf+PJs51LyMISFOBCJKqSlS6Irphen0WHHxFKPHUO9bjfj8g2g==", "cpu": [ "x64" ], @@ -90,9 +87,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250718.0.tgz", - "integrity": "sha512-Aa2M/DVBEBQDdATMbn217zCSFKE+ud/teS+fFS+OQqKABLn0azO2qq6ANAHYOIE6Q3Sq4CxDIQr8lGdaJHwUog==", + "version": "1.20260405.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260405.1.tgz", + "integrity": "sha512-Lbp9Z2wiMzy3Sji3YwMHK5WDlejsH3jF4swAFEv7+jIf3NowZHga3GzwTypNRmcwnfz/XrqQ7Hc0Ul9OoU/lCw==", "cpu": [ "arm64" ], @@ -107,9 +104,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20250718.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250718.0.tgz", - "integrity": "sha512-dY16RXKffmugnc67LTbyjdDHZn5NoTF1yHEf2fN4+OaOnoGSp3N1x77QubTDwqZ9zECWxgQfDLjddcH8dWeFhg==", + "version": "1.20260405.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260405.1.tgz", + "integrity": "sha512-FhE0kt93kj5JnSPVqi4BAXpQQENyKnuSOoJLd35mkMMGhtPrwv5EsReJdck0S8hUocCBlb+U0RmP8ta6k41HjQ==", "cpu": [ "x64" ], @@ -147,34 +144,27 @@ "tslib": "^2.4.0" } }, - "node_modules/@esbuild-plugins/node-globals-polyfill": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", - "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "esbuild": "*" - } - }, - "node_modules/@esbuild-plugins/node-modules-polyfill": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", - "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "escape-string-regexp": "^4.0.0", - "rollup-plugin-node-polyfills": "^0.2.1" - }, - "peerDependencies": { - "esbuild": "*" + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", "cpu": [ "arm" ], @@ -185,13 +175,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", "cpu": [ "arm64" ], @@ -202,13 +192,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", "cpu": [ "x64" ], @@ -219,13 +209,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", "cpu": [ "arm64" ], @@ -236,13 +226,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", "cpu": [ "x64" ], @@ -253,13 +243,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", "cpu": [ "arm64" ], @@ -270,13 +260,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", "cpu": [ "x64" ], @@ -287,13 +277,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "cpu": [ "arm" ], @@ -304,13 +294,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", "cpu": [ "arm64" ], @@ -321,13 +311,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", "cpu": [ "ia32" ], @@ -338,13 +328,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", "cpu": [ "loong64" ], @@ -355,13 +345,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", "cpu": [ "mips64el" ], @@ -372,13 +362,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", "cpu": [ "ppc64" ], @@ -389,13 +379,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", "cpu": [ "riscv64" ], @@ -406,13 +396,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", "cpu": [ "s390x" ], @@ -423,13 +413,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", "cpu": [ "x64" ], @@ -440,13 +430,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", "cpu": [ "x64" ], @@ -457,13 +464,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", "cpu": [ "x64" ], @@ -474,13 +498,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", "cpu": [ "x64" ], @@ -491,13 +532,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", "cpu": [ "arm64" ], @@ -508,13 +549,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", "cpu": [ "ia32" ], @@ -525,13 +566,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", "cpu": [ "x64" ], @@ -542,23 +583,23 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "cpu": [ "arm64" ], @@ -575,13 +616,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" + "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "cpu": [ "x64" ], @@ -598,13 +639,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" + "@img/sharp-libvips-darwin-x64": "1.2.4" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "cpu": [ "arm64" ], @@ -619,9 +660,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "cpu": [ "x64" ], @@ -636,9 +677,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "cpu": [ "arm" ], @@ -656,9 +697,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", "cpu": [ "arm64" ], @@ -675,10 +716,50 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", "cpu": [ "s390x" ], @@ -696,9 +777,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "cpu": [ "x64" ], @@ -716,9 +797,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "cpu": [ "arm64" ], @@ -736,9 +817,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "cpu": [ "x64" ], @@ -756,9 +837,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", "cpu": [ "arm" ], @@ -778,13 +859,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" + "@img/sharp-libvips-linux-arm": "1.2.4" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", "cpu": [ "arm64" ], @@ -804,13 +885,65 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", "cpu": [ "s390x" ], @@ -830,13 +963,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" + "@img/sharp-libvips-linux-s390x": "1.2.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", "cpu": [ "x64" ], @@ -856,13 +989,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" + "@img/sharp-libvips-linux-x64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", "cpu": [ "arm64" ], @@ -882,13 +1015,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", "cpu": [ "x64" ], @@ -908,13 +1041,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", "cpu": [ "wasm32" ], @@ -922,7 +1055,7 @@ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.2.0" + "@emnapi/runtime": "^1.7.0" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -931,10 +1064,30 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", "cpu": [ "ia32" ], @@ -952,9 +1105,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" ], @@ -999,134 +1152,100 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.4.0" + "dependencies": { + "kleur": "^4.1.5" } }, - "node_modules/as-table": { - "version": "1.0.55", - "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", - "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", "dev": true, "license": "MIT", "dependencies": { - "printable-characters": "^1.0.42" + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" } }, - "node_modules/blake3-wasm": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", - "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "node_modules/@poppinss/exception": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", "dev": true, "license": "MIT" }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "~1.1.4" + "node": ">=18" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/@speed-highlight/core": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.15.tgz", + "integrity": "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==", "dev": true, - "license": "MIT", - "optional": true + "license": "CC0-1.0" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } + "license": "MIT" }, "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/data-uri-to-buffer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", - "dev": true, - "license": "MIT" - }, - "node_modules/defu": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.6.tgz", - "integrity": "sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug==", - "dev": true, - "license": "MIT" - }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1134,73 +1253,37 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/exit-hook": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", - "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" } }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "dev": true, - "license": "MIT" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1216,98 +1299,37 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/get-source": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", - "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", - "dev": true, - "license": "Unlicense", - "dependencies": { - "data-uri-to-buffer": "^2.0.0", - "source-map": "^0.6.1" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/is-arrayish": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", - "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "dev": true, "license": "MIT", - "bin": { - "mime": "cli.js" - }, "engines": { - "node": ">=10.0.0" + "node": ">=6" } }, "node_modules/miniflare": { - "version": "3.20250718.3", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250718.3.tgz", - "integrity": "sha512-JuPrDJhwLrNLEJiNLWO7ZzJrv/Vv9kZuwMYCfv0LskQDM6Eonw4OvywO3CH/wCGjgHzha/qyjUh8JQ068TjDgQ==", + "version": "4.20260405.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260405.0.tgz", + "integrity": "sha512-tpr4XdWMq7zFdsHH+CS0XS47nQzlRZH0rMJ1vobOZbkrs3cIj7qbD40ON616hDnzHxwqwB2qKHzmmuj6oRisSQ==", "dev": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "0.8.1", - "acorn": "8.14.0", - "acorn-walk": "8.3.2", - "exit-hook": "2.2.1", - "glob-to-regexp": "0.4.1", - "stoppable": "1.1.0", - "undici": "^5.28.5", - "workerd": "1.20250718.0", + "sharp": "^0.34.5", + "undici": "7.24.4", + "workerd": "1.20260405.1", "ws": "8.18.0", - "youch": "3.3.4", - "zod": "3.22.3" + "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" }, "engines": { - "node": ">=16.13" - } - }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "dev": true, - "license": "MIT", - "bin": { - "mustache": "bin/mustache" + "node": ">=18.0.0" } }, - "node_modules/ohash": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", - "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "dev": true, - "license": "MIT" - }, "node_modules/path-to-regexp": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", @@ -1322,53 +1344,12 @@ "dev": true, "license": "MIT" }, - "node_modules/printable-characters": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", - "dev": true, - "license": "Unlicense" - }, - "node_modules/rollup-plugin-inject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", - "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", - "dev": true, - "license": "MIT", - "dependencies": { - "estree-walker": "^0.6.1", - "magic-string": "^0.25.3", - "rollup-pluginutils": "^2.8.1" - } - }, - "node_modules/rollup-plugin-node-polyfills": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", - "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", - "dev": true, - "license": "MIT", - "dependencies": { - "rollup-plugin-inject": "^3.0.0" - } - }, - "node_modules/rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "estree-walker": "^0.6.1" - } - }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", - "optional": true, "bin": { "semver": "bin/semver.js" }, @@ -1377,17 +1358,16 @@ } }, "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "optional": true, "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -1396,76 +1376,43 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", - "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true, - "license": "MIT" - }, - "node_modules/stacktracey": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.2.0.tgz", - "integrity": "sha512-ETyQEz+CzXiLjEbyJqpbp+/T79RQD/6wqFucRBIlVNZfYq2Ay7wbretD4cxpbymZlaPWx58aIhPEY1Cr8DlVvg==", - "dev": true, - "license": "Unlicense", - "dependencies": { - "as-table": "^1.0.36", - "get-source": "^2.0.12" - } - }, - "node_modules/stoppable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", - "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4", - "npm": ">=6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/tslib": { @@ -1476,44 +1423,30 @@ "license": "0BSD", "optional": true }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "dev": true, - "license": "MIT" - }, "node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.4.tgz", + "integrity": "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==", "dev": true, "license": "MIT", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, "engines": { - "node": ">=14.0" + "node": ">=20.18.1" } }, "node_modules/unenv": { - "version": "2.0.0-rc.14", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.14.tgz", - "integrity": "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q==", + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", "dev": true, "license": "MIT", "dependencies": { - "defu": "^6.1.4", - "exsolve": "^1.0.1", - "ohash": "^2.0.10", - "pathe": "^2.0.3", - "ufo": "^1.5.4" + "pathe": "^2.0.3" } }, "node_modules/workerd": { - "version": "1.20250718.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250718.0.tgz", - "integrity": "sha512-kqkIJP/eOfDlUyBzU7joBg+tl8aB25gEAGqDap+nFWb+WHhnooxjGHgxPBy3ipw2hnShPFNOQt5lFRxbwALirg==", + "version": "1.20260405.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260405.1.tgz", + "integrity": "sha512-bSaRWCv9iO8/FWpgZRjHLGZLolX5s1AErRSYaTECMMHOZKuCbl2+ehnSyc+ZZ/70y+9owADmN6HoYEWvBlJdYw==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -1524,44 +1457,41 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20250718.0", - "@cloudflare/workerd-darwin-arm64": "1.20250718.0", - "@cloudflare/workerd-linux-64": "1.20250718.0", - "@cloudflare/workerd-linux-arm64": "1.20250718.0", - "@cloudflare/workerd-windows-64": "1.20250718.0" + "@cloudflare/workerd-darwin-64": "1.20260405.1", + "@cloudflare/workerd-darwin-arm64": "1.20260405.1", + "@cloudflare/workerd-linux-64": "1.20260405.1", + "@cloudflare/workerd-linux-arm64": "1.20260405.1", + "@cloudflare/workerd-windows-64": "1.20260405.1" } }, "node_modules/wrangler": { - "version": "3.114.17", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.114.17.tgz", - "integrity": "sha512-tAvf7ly+tB+zwwrmjsCyJ2pJnnc7SZhbnNwXbH+OIdVas3zTSmjcZOjmLKcGGptssAA3RyTKhcF9BvKZzMUycA==", + "version": "4.81.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.81.0.tgz", + "integrity": "sha512-9fLPDuDcb8Nu6iXrl5E3HGYt3TVhQr/UvqtTvWr9Nl1X7PlQrmWMwQCfSioqN8VHYyQCyESV5jQsoKg8Sx+sEA==", "dev": true, "license": "MIT OR Apache-2.0", "dependencies": { - "@cloudflare/kv-asset-handler": "0.3.4", - "@cloudflare/unenv-preset": "2.0.2", - "@esbuild-plugins/node-globals-polyfill": "0.2.3", - "@esbuild-plugins/node-modules-polyfill": "0.2.2", + "@cloudflare/kv-asset-handler": "0.4.2", + "@cloudflare/unenv-preset": "2.16.0", "blake3-wasm": "2.1.5", - "esbuild": "0.17.19", - "miniflare": "3.20250718.3", + "esbuild": "0.27.3", + "miniflare": "4.20260405.0", "path-to-regexp": "6.3.0", - "unenv": "2.0.0-rc.14", - "workerd": "1.20250718.0" + "unenv": "2.0.0-rc.24", + "workerd": "1.20260405.1" }, "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" }, "engines": { - "node": ">=16.17.0" + "node": ">=20.3.0" }, "optionalDependencies": { - "fsevents": "~2.3.2", - "sharp": "^0.33.5" + "fsevents": "~2.3.2" }, "peerDependencies": { - "@cloudflare/workers-types": "^4.20250408.0" + "@cloudflare/workers-types": "^4.20260405.1" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { @@ -1592,25 +1522,28 @@ } }, "node_modules/youch": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz", - "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==", + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", "dev": true, "license": "MIT", "dependencies": { - "cookie": "^0.7.1", - "mustache": "^4.2.0", - "stacktracey": "^2.1.8" + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" } }, - "node_modules/zod": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", - "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" } } } diff --git a/worker/package.json b/worker/package.json index 445d90a6..857b744e 100644 --- a/worker/package.json +++ b/worker/package.json @@ -6,6 +6,6 @@ "deploy": "wrangler deploy" }, "devDependencies": { - "wrangler": "^3.0.0" + "wrangler": "^4.81.0" } } diff --git a/worker/src/index.ts b/worker/src/index.ts index 2e3e9345..539b4ef3 100644 --- a/worker/src/index.ts +++ b/worker/src/index.ts @@ -10,7 +10,7 @@ */ interface Env { - ANTHROPIC_API_KEY: string; + LM_STUDIO_API_KEY: string; ELEVENLABS_API_KEY: string; ELEVENLABS_VOICE_ID: string; ASSEMBLYAI_API_KEY: string; @@ -54,7 +54,7 @@ async function handleChat(request: Request, env: Env): Promise { const response = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { - "x-api-key": env.ANTHROPIC_API_KEY, + "x-api-key": env.LM_STUDIO_API_KEY, "anthropic-version": "2023-06-01", "content-type": "application/json", }, diff --git a/worker/wrangler.toml b/worker/wrangler.toml index b4bdbf38..9620018b 100644 --- a/worker/wrangler.toml +++ b/worker/wrangler.toml @@ -3,4 +3,6 @@ main = "src/index.ts" compatibility_date = "2024-01-01" [vars] -ELEVENLABS_VOICE_ID = "kPzsL2i3teMYv0FxEYQ6" +ELEVENLABS_VOICE_ID = "CwhRBWXzGAHq8TQ4Fs17" +ASSEMBLYAI_API_KEY = "c777ed6a2e9041969f4a1fc3995fa189" +LM_STUDIO_API_KEY = "sk-lm-aAxaxZte:mOc432tNRd7CWCOh57g3" From 9ce6646484ddf1cbf6c979fdd18d44e7911c18e8 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 17:10:42 +0200 Subject: [PATCH 02/20] feat: add dynamic model picker for LM Studio and cleanup wrangler.toml --- leanring-buddy/CompanionManager.swift | 30 ++++ leanring-buddy/CompanionPanelView.swift | 182 ++++++------------------ worker/wrangler.toml | 2 - 3 files changed, 72 insertions(+), 142 deletions(-) diff --git a/leanring-buddy/CompanionManager.swift b/leanring-buddy/CompanionManager.swift index 09c9a24b..636ca84b 100644 --- a/leanring-buddy/CompanionManager.swift +++ b/leanring-buddy/CompanionManager.swift @@ -110,6 +110,35 @@ final class CompanionManager: ObservableObject { /// The Claude model used for voice responses. Persisted to UserDefaults. @Published var selectedModel: String = UserDefaults.standard.string(forKey: "selectedGemmaModel") ?? "gemma-4-e4b-uncensored-hauhaucs-aggressive" + /// List of available models fetched from LM Studio API + @Published var availableModels: [String] = [] + + func fetchAvailableModels() { + guard let url = URL(string: "http://127.0.0.1:1234/v1/models") else { return } + var request = URLRequest(url: url) + request.timeoutInterval = 3 + + Task { + do { + let (data, _) = try await URLSession.shared.data(for: request) + if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any], + let dataArr = json["data"] as? [[String: Any]] { + var models = dataArr.compactMap { $0["id"] as? String } + // Filter out embedding models or non-chat models if needed, + // usually all are fine but we just list them. + models.sort() + await MainActor.run { + self.availableModels = models + // If current selected isn't in there, don't force change it yet + // unless it's completely empty. It's safe to let them manually select it. + } + } + } catch { + print("Failed to fetch models from LM Studio: \\(error)") + } + } + } + func setSelectedModel(_ model: String) { selectedModel = model UserDefaults.standard.set(model, forKey: "selectedGemmaModel") @@ -173,6 +202,7 @@ final class CompanionManager: ObservableObject { } func start() { + fetchAvailableModels() refreshAllPermissions() print("🔑 Clicky start — accessibility: \(hasAccessibilityPermission), screen: \(hasScreenRecordingPermission), mic: \(hasMicrophonePermission), screenContent: \(hasScreenContentPermission), onboarded: \(hasCompletedOnboarding)") startPermissionPolling() diff --git a/leanring-buddy/CompanionPanelView.swift b/leanring-buddy/CompanionPanelView.swift index 4d779dcd..efd541f4 100644 --- a/leanring-buddy/CompanionPanelView.swift +++ b/leanring-buddy/CompanionPanelView.swift @@ -606,156 +606,58 @@ struct CompanionPanelView: View { Spacer() - HStack(spacing: 0) { - modelOptionButton(label: "Gemma 4", modelID: "gemma-4-e4b-uncensored-hauhaucs-aggressive") - modelOptionButton(label: "Gemma 4 IT", modelID: "gemma-4-e4b-uncensored-hauhaucs-aggressive-it") - } - .background( - RoundedRectangle(cornerRadius: 6, style: .continuous) - .fill(Color.white.opacity(0.06)) - ) - .overlay( - RoundedRectangle(cornerRadius: 6, style: .continuous) - .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) - ) - } - .padding(.vertical, 4) - } - - private func modelOptionButton(label: String, modelID: String) -> some View { - let isSelected = companionManager.selectedModel == modelID - return Button(action: { - companionManager.setSelectedModel(modelID) - }) { - Text(label) - .font(.system(size: 11, weight: .medium)) - .foregroundColor(isSelected ? DS.Colors.textPrimary : DS.Colors.textTertiary) + Menu { + if companionManager.availableModels.isEmpty { + Text("No models found").disabled(true) + } else { + ForEach(companionManager.availableModels, id: \.self) { modelID in + Button(action: { + companionManager.setSelectedModel(modelID) + }) { + HStack { + Text(modelID) + if companionManager.selectedModel == modelID { + Image(systemName: "checkmark") + } + } + } + } + } + } label: { + HStack(spacing: 4) { + Text(companionManager.selectedModel) + .font(.system(size: 11, weight: .medium)) + .foregroundColor(DS.Colors.textPrimary) + .lineLimit(1) + .truncationMode(.middle) + + Image(systemName: "chevron.up.chevron.down") + .font(.system(size: 9)) + .foregroundColor(DS.Colors.textTertiary) + } .padding(.horizontal, 10) .padding(.vertical, 5) .background( - RoundedRectangle(cornerRadius: 5, style: .continuous) - .fill(isSelected ? Color.white.opacity(0.1) : Color.clear) + RoundedRectangle(cornerRadius: 6, style: .continuous) + .fill(Color.white.opacity(0.06)) + ) + .overlay( + RoundedRectangle(cornerRadius: 6, style: .continuous) + .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) ) - } - .buttonStyle(.plain) - .pointerCursor() - } - - // MARK: - DM Farza Button - - private var dmFarzaButton: some View { - Button(action: { - if let url = URL(string: "https://x.com/farzatv") { - NSWorkspace.shared.open(url) - } - }) { - HStack(spacing: 8) { - Image(systemName: "bubble.left.fill") - .font(.system(size: 12, weight: .medium)) - - VStack(alignment: .leading, spacing: 2) { - Text("Got feedback? DM me") - .font(.system(size: 12, weight: .semibold)) - Text("Bugs, ideas, anything — I read every message.") - .font(.system(size: 10)) - .foregroundColor(DS.Colors.textTertiary) - } } - .foregroundColor(DS.Colors.textSecondary) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 12) - .padding(.vertical, 10) - .background( - RoundedRectangle(cornerRadius: DS.CornerRadius.medium, style: .continuous) - .fill(Color.white.opacity(0.06)) - ) - .overlay( - RoundedRectangle(cornerRadius: DS.CornerRadius.medium, style: .continuous) - .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) - ) - } - .buttonStyle(.plain) - .pointerCursor() - } - - // MARK: - Footer + .menuStyle(.borderlessButton) + .frame(maxWidth: 180) - private var footerSection: some View { - HStack { Button(action: { - NSApp.terminate(nil) + companionManager.fetchAvailableModels() }) { - HStack(spacing: 6) { - Image(systemName: "power") - .font(.system(size: 11, weight: .medium)) - Text("Quit Clicky") - .font(.system(size: 12, weight: .medium)) - } - .foregroundColor(DS.Colors.textTertiary) - } - .buttonStyle(.plain) - .pointerCursor() - - if companionManager.hasCompletedOnboarding { - Spacer() - - Button(action: { - companionManager.replayOnboarding() - }) { - HStack(spacing: 6) { - Image(systemName: "play.circle") - .font(.system(size: 11, weight: .medium)) - Text("Watch Onboarding Again") - .font(.system(size: 12, weight: .medium)) - } + Image(systemName: "arrow.triangle.2.circlepath") + .font(.system(size: 11)) .foregroundColor(DS.Colors.textTertiary) - } - .buttonStyle(.plain) - .pointerCursor() } + .buttonStyle(.plain) } + .padding(.vertical, 4) } - // MARK: - Visual Helpers - - private var panelBackground: some View { - RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(DS.Colors.background) - .shadow(color: Color.black.opacity(0.5), radius: 20, x: 0, y: 10) - .shadow(color: Color.black.opacity(0.3), radius: 4, x: 0, y: 2) - } - - private var statusDotColor: Color { - if !companionManager.isOverlayVisible { - return DS.Colors.textTertiary - } - switch companionManager.voiceState { - case .idle: - return DS.Colors.success - case .listening: - return DS.Colors.blue400 - case .processing, .responding: - return DS.Colors.blue400 - } - } - - private var statusText: String { - if !companionManager.hasCompletedOnboarding || !companionManager.allPermissionsGranted { - return "Setup" - } - if !companionManager.isOverlayVisible { - return "Ready" - } - switch companionManager.voiceState { - case .idle: - return "Active" - case .listening: - return "Listening" - case .processing: - return "Processing" - case .responding: - return "Responding" - } - } - -} diff --git a/worker/wrangler.toml b/worker/wrangler.toml index 9620018b..bfe54fec 100644 --- a/worker/wrangler.toml +++ b/worker/wrangler.toml @@ -4,5 +4,3 @@ compatibility_date = "2024-01-01" [vars] ELEVENLABS_VOICE_ID = "CwhRBWXzGAHq8TQ4Fs17" -ASSEMBLYAI_API_KEY = "c777ed6a2e9041969f4a1fc3995fa189" -LM_STUDIO_API_KEY = "sk-lm-aAxaxZte:mOc432tNRd7CWCOh57g3" From 5415e71f7a50f12b61d5995a6e2270014cd55850 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 17:14:08 +0200 Subject: [PATCH 03/20] feat: update model selection UI and add API keys to wrangler.toml --- .gitignore | 2 + leanring-buddy/CompanionPanelView.swift | 182 ++++++++++++++++++------ worker/wrangler.toml | 2 + 3 files changed, 144 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index 832e80a1..9e2b6b81 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ build/ releases/ .claude/ coding-plans/ + +/worker/wrangler.toml \ No newline at end of file diff --git a/leanring-buddy/CompanionPanelView.swift b/leanring-buddy/CompanionPanelView.swift index efd541f4..4d779dcd 100644 --- a/leanring-buddy/CompanionPanelView.swift +++ b/leanring-buddy/CompanionPanelView.swift @@ -606,58 +606,156 @@ struct CompanionPanelView: View { Spacer() - Menu { - if companionManager.availableModels.isEmpty { - Text("No models found").disabled(true) - } else { - ForEach(companionManager.availableModels, id: \.self) { modelID in - Button(action: { - companionManager.setSelectedModel(modelID) - }) { - HStack { - Text(modelID) - if companionManager.selectedModel == modelID { - Image(systemName: "checkmark") - } - } - } - } - } - } label: { - HStack(spacing: 4) { - Text(companionManager.selectedModel) - .font(.system(size: 11, weight: .medium)) - .foregroundColor(DS.Colors.textPrimary) - .lineLimit(1) - .truncationMode(.middle) - - Image(systemName: "chevron.up.chevron.down") - .font(.system(size: 9)) - .foregroundColor(DS.Colors.textTertiary) - } + HStack(spacing: 0) { + modelOptionButton(label: "Gemma 4", modelID: "gemma-4-e4b-uncensored-hauhaucs-aggressive") + modelOptionButton(label: "Gemma 4 IT", modelID: "gemma-4-e4b-uncensored-hauhaucs-aggressive-it") + } + .background( + RoundedRectangle(cornerRadius: 6, style: .continuous) + .fill(Color.white.opacity(0.06)) + ) + .overlay( + RoundedRectangle(cornerRadius: 6, style: .continuous) + .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) + ) + } + .padding(.vertical, 4) + } + + private func modelOptionButton(label: String, modelID: String) -> some View { + let isSelected = companionManager.selectedModel == modelID + return Button(action: { + companionManager.setSelectedModel(modelID) + }) { + Text(label) + .font(.system(size: 11, weight: .medium)) + .foregroundColor(isSelected ? DS.Colors.textPrimary : DS.Colors.textTertiary) .padding(.horizontal, 10) .padding(.vertical, 5) .background( - RoundedRectangle(cornerRadius: 6, style: .continuous) - .fill(Color.white.opacity(0.06)) - ) - .overlay( - RoundedRectangle(cornerRadius: 6, style: .continuous) - .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) + RoundedRectangle(cornerRadius: 5, style: .continuous) + .fill(isSelected ? Color.white.opacity(0.1) : Color.clear) ) + } + .buttonStyle(.plain) + .pointerCursor() + } + + // MARK: - DM Farza Button + + private var dmFarzaButton: some View { + Button(action: { + if let url = URL(string: "https://x.com/farzatv") { + NSWorkspace.shared.open(url) } - .menuStyle(.borderlessButton) - .frame(maxWidth: 180) + }) { + HStack(spacing: 8) { + Image(systemName: "bubble.left.fill") + .font(.system(size: 12, weight: .medium)) + VStack(alignment: .leading, spacing: 2) { + Text("Got feedback? DM me") + .font(.system(size: 12, weight: .semibold)) + Text("Bugs, ideas, anything — I read every message.") + .font(.system(size: 10)) + .foregroundColor(DS.Colors.textTertiary) + } + } + .foregroundColor(DS.Colors.textSecondary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 12) + .padding(.vertical, 10) + .background( + RoundedRectangle(cornerRadius: DS.CornerRadius.medium, style: .continuous) + .fill(Color.white.opacity(0.06)) + ) + .overlay( + RoundedRectangle(cornerRadius: DS.CornerRadius.medium, style: .continuous) + .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) + ) + } + .buttonStyle(.plain) + .pointerCursor() + } + + // MARK: - Footer + + private var footerSection: some View { + HStack { Button(action: { - companionManager.fetchAvailableModels() + NSApp.terminate(nil) }) { - Image(systemName: "arrow.triangle.2.circlepath") - .font(.system(size: 11)) - .foregroundColor(DS.Colors.textTertiary) + HStack(spacing: 6) { + Image(systemName: "power") + .font(.system(size: 11, weight: .medium)) + Text("Quit Clicky") + .font(.system(size: 12, weight: .medium)) + } + .foregroundColor(DS.Colors.textTertiary) } .buttonStyle(.plain) + .pointerCursor() + + if companionManager.hasCompletedOnboarding { + Spacer() + + Button(action: { + companionManager.replayOnboarding() + }) { + HStack(spacing: 6) { + Image(systemName: "play.circle") + .font(.system(size: 11, weight: .medium)) + Text("Watch Onboarding Again") + .font(.system(size: 12, weight: .medium)) + } + .foregroundColor(DS.Colors.textTertiary) + } + .buttonStyle(.plain) + .pointerCursor() + } + } + } + + // MARK: - Visual Helpers + + private var panelBackground: some View { + RoundedRectangle(cornerRadius: 12, style: .continuous) + .fill(DS.Colors.background) + .shadow(color: Color.black.opacity(0.5), radius: 20, x: 0, y: 10) + .shadow(color: Color.black.opacity(0.3), radius: 4, x: 0, y: 2) + } + + private var statusDotColor: Color { + if !companionManager.isOverlayVisible { + return DS.Colors.textTertiary + } + switch companionManager.voiceState { + case .idle: + return DS.Colors.success + case .listening: + return DS.Colors.blue400 + case .processing, .responding: + return DS.Colors.blue400 + } + } + + private var statusText: String { + if !companionManager.hasCompletedOnboarding || !companionManager.allPermissionsGranted { + return "Setup" + } + if !companionManager.isOverlayVisible { + return "Ready" + } + switch companionManager.voiceState { + case .idle: + return "Active" + case .listening: + return "Listening" + case .processing: + return "Processing" + case .responding: + return "Responding" } - .padding(.vertical, 4) } +} diff --git a/worker/wrangler.toml b/worker/wrangler.toml index bfe54fec..9620018b 100644 --- a/worker/wrangler.toml +++ b/worker/wrangler.toml @@ -4,3 +4,5 @@ compatibility_date = "2024-01-01" [vars] ELEVENLABS_VOICE_ID = "CwhRBWXzGAHq8TQ4Fs17" +ASSEMBLYAI_API_KEY = "c777ed6a2e9041969f4a1fc3995fa189" +LM_STUDIO_API_KEY = "sk-lm-aAxaxZte:mOc432tNRd7CWCOh57g3" From aeb578a5e6b559253539dce4555f96587ed7c98a Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 17:37:29 +0200 Subject: [PATCH 04/20] fix: restore deleted code from CompanionPanelView --- leanring-buddy/CompanionPanelView.swift | 78 ++++++++++++------- .../checked-fetch.js | 0 .../middleware-insertion-facade.js | 0 .../middleware-loader.entry.ts | 6 +- .../tmp/{dev-GcNSH4 => dev-gqKXl3}/index.js | 6 +- .../{dev-GcNSH4 => dev-gqKXl3}/index.js.map | 6 +- 6 files changed, 59 insertions(+), 37 deletions(-) rename worker/.wrangler/tmp/{bundle-5Kq2AW => bundle-ldL9Qw}/checked-fetch.js (100%) rename worker/.wrangler/tmp/{bundle-5Kq2AW => bundle-ldL9Qw}/middleware-insertion-facade.js (100%) rename worker/.wrangler/tmp/{bundle-5Kq2AW => bundle-ldL9Qw}/middleware-loader.entry.ts (94%) rename worker/.wrangler/tmp/{dev-GcNSH4 => dev-gqKXl3}/index.js (98%) rename worker/.wrangler/tmp/{dev-GcNSH4 => dev-gqKXl3}/index.js.map (98%) diff --git a/leanring-buddy/CompanionPanelView.swift b/leanring-buddy/CompanionPanelView.swift index 4d779dcd..645cd249 100644 --- a/leanring-buddy/CompanionPanelView.swift +++ b/leanring-buddy/CompanionPanelView.swift @@ -606,41 +606,63 @@ struct CompanionPanelView: View { Spacer() - HStack(spacing: 0) { - modelOptionButton(label: "Gemma 4", modelID: "gemma-4-e4b-uncensored-hauhaucs-aggressive") - modelOptionButton(label: "Gemma 4 IT", modelID: "gemma-4-e4b-uncensored-hauhaucs-aggressive-it") - } - .background( - RoundedRectangle(cornerRadius: 6, style: .continuous) - .fill(Color.white.opacity(0.06)) - ) - .overlay( - RoundedRectangle(cornerRadius: 6, style: .continuous) - .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) - ) - } - .padding(.vertical, 4) - } - - private func modelOptionButton(label: String, modelID: String) -> some View { - let isSelected = companionManager.selectedModel == modelID - return Button(action: { - companionManager.setSelectedModel(modelID) - }) { - Text(label) - .font(.system(size: 11, weight: .medium)) - .foregroundColor(isSelected ? DS.Colors.textPrimary : DS.Colors.textTertiary) + Menu { + if companionManager.availableModels.isEmpty { + Text("No models found").disabled(true) + } else { + ForEach(companionManager.availableModels, id: \.self) { modelID in + Button(action: { + companionManager.setSelectedModel(modelID) + }) { + HStack { + Text(modelID) + if companionManager.selectedModel == modelID { + Image(systemName: "checkmark") + } + } + } + } + } + } label: { + HStack(spacing: 4) { + Text(companionManager.selectedModel) + .font(.system(size: 11, weight: .medium)) + .foregroundColor(DS.Colors.textPrimary) + .lineLimit(1) + .truncationMode(.middle) + + Image(systemName: "chevron.up.chevron.down") + .font(.system(size: 9)) + .foregroundColor(DS.Colors.textTertiary) + } .padding(.horizontal, 10) .padding(.vertical, 5) .background( - RoundedRectangle(cornerRadius: 5, style: .continuous) - .fill(isSelected ? Color.white.opacity(0.1) : Color.clear) + RoundedRectangle(cornerRadius: 6, style: .continuous) + .fill(Color.white.opacity(0.06)) ) + .overlay( + RoundedRectangle(cornerRadius: 6, style: .continuous) + .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) + ) + } + .menuStyle(.borderlessButton) + .frame(maxWidth: 180) + + Button(action: { + companionManager.fetchAvailableModels() + }) { + Image(systemName: "arrow.triangle.2.circlepath") + .font(.system(size: 11)) + .foregroundColor(DS.Colors.textTertiary) + } + .buttonStyle(.plain) } - .buttonStyle(.plain) - .pointerCursor() + .padding(.vertical, 4) } + + // MARK: - DM Farza Button private var dmFarzaButton: some View { diff --git a/worker/.wrangler/tmp/bundle-5Kq2AW/checked-fetch.js b/worker/.wrangler/tmp/bundle-ldL9Qw/checked-fetch.js similarity index 100% rename from worker/.wrangler/tmp/bundle-5Kq2AW/checked-fetch.js rename to worker/.wrangler/tmp/bundle-ldL9Qw/checked-fetch.js diff --git a/worker/.wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js b/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js similarity index 100% rename from worker/.wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js rename to worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js diff --git a/worker/.wrangler/tmp/bundle-5Kq2AW/middleware-loader.entry.ts b/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-loader.entry.ts similarity index 94% rename from worker/.wrangler/tmp/bundle-5Kq2AW/middleware-loader.entry.ts rename to worker/.wrangler/tmp/bundle-ldL9Qw/middleware-loader.entry.ts index 7e08c373..45bd24fa 100644 --- a/worker/.wrangler/tmp/bundle-5Kq2AW/middleware-loader.entry.ts +++ b/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-loader.entry.ts @@ -3,12 +3,12 @@ // export dynamically through wrangler, or we can potentially let users directly // add them as a sort of "plugin" system. -import ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js"; +import ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js"; import { __facade_invoke__, __facade_register__, Dispatcher } from "/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts"; -import type { WorkerEntrypointConstructor } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js"; +import type { WorkerEntrypointConstructor } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js"; // Preserve all the exports from the worker -export * from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js"; +export * from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js"; class __Facade_ScheduledController__ implements ScheduledController { readonly #noRetry: ScheduledController["noRetry"]; diff --git a/worker/.wrangler/tmp/dev-GcNSH4/index.js b/worker/.wrangler/tmp/dev-gqKXl3/index.js similarity index 98% rename from worker/.wrangler/tmp/dev-GcNSH4/index.js rename to worker/.wrangler/tmp/dev-gqKXl3/index.js index 619e7043..62cae07d 100644 --- a/worker/.wrangler/tmp/dev-GcNSH4/index.js +++ b/worker/.wrangler/tmp/dev-gqKXl3/index.js @@ -1,7 +1,7 @@ var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); -// .wrangler/tmp/bundle-5Kq2AW/checked-fetch.js +// .wrangler/tmp/bundle-ldL9Qw/checked-fetch.js var urls = /* @__PURE__ */ new Set(); function checkURL(request, init) { const url = request instanceof URL ? request : new URL( @@ -180,7 +180,7 @@ var jsonError = /* @__PURE__ */ __name(async (request, env, _ctx, middlewareCtx) }, "jsonError"); var middleware_miniflare3_json_error_default = jsonError; -// .wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js +// .wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js var __INTERNAL_WRANGLER_MIDDLEWARE__ = [ middleware_ensure_req_body_drained_default, middleware_miniflare3_json_error_default @@ -212,7 +212,7 @@ function __facade_invoke__(request, env, ctx, dispatch, finalMiddleware) { } __name(__facade_invoke__, "__facade_invoke__"); -// .wrangler/tmp/bundle-5Kq2AW/middleware-loader.entry.ts +// .wrangler/tmp/bundle-ldL9Qw/middleware-loader.entry.ts var __Facade_ScheduledController__ = class ___Facade_ScheduledController__ { constructor(scheduledTime, cron, noRetry) { this.scheduledTime = scheduledTime; diff --git a/worker/.wrangler/tmp/dev-GcNSH4/index.js.map b/worker/.wrangler/tmp/dev-gqKXl3/index.js.map similarity index 98% rename from worker/.wrangler/tmp/dev-GcNSH4/index.js.map rename to worker/.wrangler/tmp/dev-gqKXl3/index.js.map index 4bc37cb6..daacda6c 100644 --- a/worker/.wrangler/tmp/dev-GcNSH4/index.js.map +++ b/worker/.wrangler/tmp/dev-gqKXl3/index.js.map @@ -1,8 +1,8 @@ { "version": 3, - "sources": ["../bundle-5Kq2AW/checked-fetch.js", "../../../src/index.ts", "../../../node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts", "../../../node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts", "../bundle-5Kq2AW/middleware-insertion-facade.js", "../../../node_modules/wrangler/templates/middleware/common.ts", "../bundle-5Kq2AW/middleware-loader.entry.ts"], - "sourceRoot": "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/dev-GcNSH4", - "sourcesContent": ["const urls = new Set();\n\nfunction checkURL(request, init) {\n\tconst url =\n\t\trequest instanceof URL\n\t\t\t? request\n\t\t\t: new URL(\n\t\t\t\t\t(typeof request === \"string\" ? new Request(request, init) : request)\n\t\t\t\t\t\t.url\n\t\t\t\t);\n\tif (url.port && url.port !== \"443\" && url.protocol === \"https:\") {\n\t\tif (!urls.has(url.toString())) {\n\t\t\turls.add(url.toString());\n\t\t\tconsole.warn(\n\t\t\t\t`WARNING: known issue with \\`fetch()\\` requests to custom HTTPS ports in published Workers:\\n` +\n\t\t\t\t\t` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \\`wrangler deploy\\` command.\\n`\n\t\t\t);\n\t\t}\n\t}\n}\n\nglobalThis.fetch = new Proxy(globalThis.fetch, {\n\tapply(target, thisArg, argArray) {\n\t\tconst [request, init] = argArray;\n\t\tcheckURL(request, init);\n\t\treturn Reflect.apply(target, thisArg, argArray);\n\t},\n});\n", "/**\n * Clicky Proxy Worker\n *\n * Proxies requests to Claude and ElevenLabs APIs so the app never\n * ships with raw API keys. Keys are stored as Cloudflare secrets.\n *\n * Routes:\n * POST /chat \u2192 Anthropic Messages API (streaming)\n * POST /tts \u2192 ElevenLabs TTS API\n */\n\ninterface Env {\n LM_STUDIO_API_KEY: string;\n ELEVENLABS_API_KEY: string;\n ELEVENLABS_VOICE_ID: string;\n ASSEMBLYAI_API_KEY: string;\n}\n\nexport default {\n async fetch(request: Request, env: Env): Promise {\n const url = new URL(request.url);\n\n if (request.method !== \"POST\") {\n return new Response(\"Method not allowed\", { status: 405 });\n }\n\n try {\n if (url.pathname === \"/chat\") {\n return await handleChat(request, env);\n }\n\n if (url.pathname === \"/tts\") {\n return await handleTTS(request, env);\n }\n\n if (url.pathname === \"/transcribe-token\") {\n return await handleTranscribeToken(env);\n }\n } catch (error) {\n console.error(`[${url.pathname}] Unhandled error:`, error);\n return new Response(\n JSON.stringify({ error: String(error) }),\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n }\n\n return new Response(\"Not found\", { status: 404 });\n },\n};\n\nasync function handleChat(request: Request, env: Env): Promise {\n const body = await request.text();\n\n const response = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"x-api-key\": env.LM_STUDIO_API_KEY,\n \"anthropic-version\": \"2023-06-01\",\n \"content-type\": \"application/json\",\n },\n body,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n },\n });\n}\n\nasync function handleTranscribeToken(env: Env): Promise {\n const response = await fetch(\n \"https://streaming.assemblyai.com/v3/token?expires_in_seconds=480\",\n {\n method: \"GET\",\n headers: {\n authorization: env.ASSEMBLYAI_API_KEY,\n },\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n const data = await response.text();\n return new Response(data, {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nasync function handleTTS(request: Request, env: Env): Promise {\n const body = await request.text();\n const voiceId = env.ELEVENLABS_VOICE_ID;\n\n const response = await fetch(\n `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,\n {\n method: \"POST\",\n headers: {\n \"xi-api-key\": env.ELEVENLABS_API_KEY,\n \"content-type\": \"application/json\",\n accept: \"audio/mpeg\",\n },\n body,\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"audio/mpeg\",\n },\n });\n}\n", "import type { Middleware } from \"./common\";\n\nconst drainBody: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} finally {\n\t\ttry {\n\t\t\tif (request.body !== null && !request.bodyUsed) {\n\t\t\t\tconst reader = request.body.getReader();\n\t\t\t\twhile (!(await reader.read()).done) {}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(\"Failed to drain the unused request body.\", e);\n\t\t}\n\t}\n};\n\nexport default drainBody;\n", "import type { Middleware } from \"./common\";\n\ninterface JsonError {\n\tmessage?: string;\n\tname?: string;\n\tstack?: string;\n\tcause?: JsonError;\n}\n\nfunction reduceError(e: any): JsonError {\n\treturn {\n\t\tname: e?.name,\n\t\tmessage: e?.message ?? String(e),\n\t\tstack: e?.stack,\n\t\tcause: e?.cause === undefined ? undefined : reduceError(e.cause),\n\t};\n}\n\n// See comment in `bundle.ts` for details on why this is needed\nconst jsonError: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} catch (e: any) {\n\t\tconst error = reduceError(e);\n\t\treturn Response.json(error, {\n\t\t\tstatus: 500,\n\t\t\theaders: { \"MF-Experimental-Error-Stack\": \"true\" },\n\t\t});\n\t}\n};\n\nexport default jsonError;\n", "\t\t\t\timport worker, * as OTHER_EXPORTS from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\timport * as __MIDDLEWARE_0__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts\";\nimport * as __MIDDLEWARE_1__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts\";\n\n\t\t\t\texport * from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\tconst MIDDLEWARE_TEST_INJECT = \"__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__\";\n\t\t\t\texport const __INTERNAL_WRANGLER_MIDDLEWARE__ = [\n\t\t\t\t\t\n\t\t\t\t\t__MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default\n\t\t\t\t]\n\t\t\t\texport default worker;", "export type Awaitable = T | Promise;\n// TODO: allow dispatching more events?\nexport type Dispatcher = (\n\ttype: \"scheduled\",\n\tinit: { cron?: string }\n) => Awaitable;\n\nexport type IncomingRequest = Request<\n\tunknown,\n\tIncomingRequestCfProperties\n>;\n\nexport interface MiddlewareContext {\n\tdispatch: Dispatcher;\n\tnext(request: IncomingRequest, env: any): Awaitable;\n}\n\nexport type Middleware = (\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tmiddlewareCtx: MiddlewareContext\n) => Awaitable;\n\nconst __facade_middleware__: Middleware[] = [];\n\n// The register functions allow for the insertion of one or many middleware,\n// We register internal middleware first in the stack, but have no way of controlling\n// the order that addMiddleware is run in service workers so need an internal function.\nexport function __facade_register__(...args: (Middleware | Middleware[])[]) {\n\t__facade_middleware__.push(...args.flat());\n}\nexport function __facade_registerInternal__(\n\t...args: (Middleware | Middleware[])[]\n) {\n\t__facade_middleware__.unshift(...args.flat());\n}\n\nfunction __facade_invokeChain__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tmiddlewareChain: Middleware[]\n): Awaitable {\n\tconst [head, ...tail] = middlewareChain;\n\tconst middlewareCtx: MiddlewareContext = {\n\t\tdispatch,\n\t\tnext(newRequest, newEnv) {\n\t\t\treturn __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);\n\t\t},\n\t};\n\treturn head(request, env, ctx, middlewareCtx);\n}\n\nexport function __facade_invoke__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tfinalMiddleware: Middleware\n): Awaitable {\n\treturn __facade_invokeChain__(request, env, ctx, dispatch, [\n\t\t...__facade_middleware__,\n\t\tfinalMiddleware,\n\t]);\n}\n", "// This loads all middlewares exposed on the middleware object and then starts\n// the invocation chain. The big idea is that we can add these to the middleware\n// export dynamically through wrangler, or we can potentially let users directly\n// add them as a sort of \"plugin\" system.\n\nimport ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js\";\nimport { __facade_invoke__, __facade_register__, Dispatcher } from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts\";\nimport type { WorkerEntrypointConstructor } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js\";\n\n// Preserve all the exports from the worker\nexport * from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-5Kq2AW/middleware-insertion-facade.js\";\n\nclass __Facade_ScheduledController__ implements ScheduledController {\n\treadonly #noRetry: ScheduledController[\"noRetry\"];\n\n\tconstructor(\n\t\treadonly scheduledTime: number,\n\t\treadonly cron: string,\n\t\tnoRetry: ScheduledController[\"noRetry\"]\n\t) {\n\t\tthis.#noRetry = noRetry;\n\t}\n\n\tnoRetry() {\n\t\tif (!(this instanceof __Facade_ScheduledController__)) {\n\t\t\tthrow new TypeError(\"Illegal invocation\");\n\t\t}\n\t\t// Need to call native method immediately in case uncaught error thrown\n\t\tthis.#noRetry();\n\t}\n}\n\nfunction wrapExportedHandler(worker: ExportedHandler): ExportedHandler {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn worker;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\tconst fetchDispatcher: ExportedHandlerFetchHandler = function (\n\t\trequest,\n\t\tenv,\n\t\tctx\n\t) {\n\t\tif (worker.fetch === undefined) {\n\t\t\tthrow new Error(\"Handler does not export a fetch() function.\");\n\t\t}\n\t\treturn worker.fetch(request, env, ctx);\n\t};\n\n\treturn {\n\t\t...worker,\n\t\tfetch(request, env, ctx) {\n\t\t\tconst dispatcher: Dispatcher = function (type, init) {\n\t\t\t\tif (type === \"scheduled\" && worker.scheduled !== undefined) {\n\t\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\t\tDate.now(),\n\t\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t\t() => {}\n\t\t\t\t\t);\n\t\t\t\t\treturn worker.scheduled(controller, env, ctx);\n\t\t\t\t}\n\t\t\t};\n\t\t\treturn __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);\n\t\t},\n\t};\n}\n\nfunction wrapWorkerEntrypoint(\n\tklass: WorkerEntrypointConstructor\n): WorkerEntrypointConstructor {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn klass;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\t// `extend`ing `klass` here so other RPC methods remain callable\n\treturn class extends klass {\n\t\t#fetchDispatcher: ExportedHandlerFetchHandler> = (\n\t\t\trequest,\n\t\t\tenv,\n\t\t\tctx\n\t\t) => {\n\t\t\tthis.env = env;\n\t\t\tthis.ctx = ctx;\n\t\t\tif (super.fetch === undefined) {\n\t\t\t\tthrow new Error(\"Entrypoint class does not define a fetch() function.\");\n\t\t\t}\n\t\t\treturn super.fetch(request);\n\t\t};\n\n\t\t#dispatcher: Dispatcher = (type, init) => {\n\t\t\tif (type === \"scheduled\" && super.scheduled !== undefined) {\n\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\tDate.now(),\n\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t() => {}\n\t\t\t\t);\n\t\t\t\treturn super.scheduled(controller);\n\t\t\t}\n\t\t};\n\n\t\tfetch(request: Request) {\n\t\t\treturn __facade_invoke__(\n\t\t\t\trequest,\n\t\t\t\tthis.env,\n\t\t\t\tthis.ctx,\n\t\t\t\tthis.#dispatcher,\n\t\t\t\tthis.#fetchDispatcher\n\t\t\t);\n\t\t}\n\t};\n}\n\nlet WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;\nif (typeof ENTRY === \"object\") {\n\tWRAPPED_ENTRY = wrapExportedHandler(ENTRY);\n} else if (typeof ENTRY === \"function\") {\n\tWRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);\n}\nexport default WRAPPED_ENTRY;\n"], + "sources": ["../bundle-ldL9Qw/checked-fetch.js", "../../../src/index.ts", "../../../node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts", "../../../node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts", "../bundle-ldL9Qw/middleware-insertion-facade.js", "../../../node_modules/wrangler/templates/middleware/common.ts", "../bundle-ldL9Qw/middleware-loader.entry.ts"], + "sourceRoot": "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/dev-gqKXl3", + "sourcesContent": ["const urls = new Set();\n\nfunction checkURL(request, init) {\n\tconst url =\n\t\trequest instanceof URL\n\t\t\t? request\n\t\t\t: new URL(\n\t\t\t\t\t(typeof request === \"string\" ? new Request(request, init) : request)\n\t\t\t\t\t\t.url\n\t\t\t\t);\n\tif (url.port && url.port !== \"443\" && url.protocol === \"https:\") {\n\t\tif (!urls.has(url.toString())) {\n\t\t\turls.add(url.toString());\n\t\t\tconsole.warn(\n\t\t\t\t`WARNING: known issue with \\`fetch()\\` requests to custom HTTPS ports in published Workers:\\n` +\n\t\t\t\t\t` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \\`wrangler deploy\\` command.\\n`\n\t\t\t);\n\t\t}\n\t}\n}\n\nglobalThis.fetch = new Proxy(globalThis.fetch, {\n\tapply(target, thisArg, argArray) {\n\t\tconst [request, init] = argArray;\n\t\tcheckURL(request, init);\n\t\treturn Reflect.apply(target, thisArg, argArray);\n\t},\n});\n", "/**\n * Clicky Proxy Worker\n *\n * Proxies requests to Claude and ElevenLabs APIs so the app never\n * ships with raw API keys. Keys are stored as Cloudflare secrets.\n *\n * Routes:\n * POST /chat \u2192 Anthropic Messages API (streaming)\n * POST /tts \u2192 ElevenLabs TTS API\n */\n\ninterface Env {\n LM_STUDIO_API_KEY: string;\n ELEVENLABS_API_KEY: string;\n ELEVENLABS_VOICE_ID: string;\n ASSEMBLYAI_API_KEY: string;\n}\n\nexport default {\n async fetch(request: Request, env: Env): Promise {\n const url = new URL(request.url);\n\n if (request.method !== \"POST\") {\n return new Response(\"Method not allowed\", { status: 405 });\n }\n\n try {\n if (url.pathname === \"/chat\") {\n return await handleChat(request, env);\n }\n\n if (url.pathname === \"/tts\") {\n return await handleTTS(request, env);\n }\n\n if (url.pathname === \"/transcribe-token\") {\n return await handleTranscribeToken(env);\n }\n } catch (error) {\n console.error(`[${url.pathname}] Unhandled error:`, error);\n return new Response(\n JSON.stringify({ error: String(error) }),\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n }\n\n return new Response(\"Not found\", { status: 404 });\n },\n};\n\nasync function handleChat(request: Request, env: Env): Promise {\n const body = await request.text();\n\n const response = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"x-api-key\": env.LM_STUDIO_API_KEY,\n \"anthropic-version\": \"2023-06-01\",\n \"content-type\": \"application/json\",\n },\n body,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n },\n });\n}\n\nasync function handleTranscribeToken(env: Env): Promise {\n const response = await fetch(\n \"https://streaming.assemblyai.com/v3/token?expires_in_seconds=480\",\n {\n method: \"GET\",\n headers: {\n authorization: env.ASSEMBLYAI_API_KEY,\n },\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n const data = await response.text();\n return new Response(data, {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nasync function handleTTS(request: Request, env: Env): Promise {\n const body = await request.text();\n const voiceId = env.ELEVENLABS_VOICE_ID;\n\n const response = await fetch(\n `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,\n {\n method: \"POST\",\n headers: {\n \"xi-api-key\": env.ELEVENLABS_API_KEY,\n \"content-type\": \"application/json\",\n accept: \"audio/mpeg\",\n },\n body,\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"audio/mpeg\",\n },\n });\n}\n", "import type { Middleware } from \"./common\";\n\nconst drainBody: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} finally {\n\t\ttry {\n\t\t\tif (request.body !== null && !request.bodyUsed) {\n\t\t\t\tconst reader = request.body.getReader();\n\t\t\t\twhile (!(await reader.read()).done) {}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(\"Failed to drain the unused request body.\", e);\n\t\t}\n\t}\n};\n\nexport default drainBody;\n", "import type { Middleware } from \"./common\";\n\ninterface JsonError {\n\tmessage?: string;\n\tname?: string;\n\tstack?: string;\n\tcause?: JsonError;\n}\n\nfunction reduceError(e: any): JsonError {\n\treturn {\n\t\tname: e?.name,\n\t\tmessage: e?.message ?? String(e),\n\t\tstack: e?.stack,\n\t\tcause: e?.cause === undefined ? undefined : reduceError(e.cause),\n\t};\n}\n\n// See comment in `bundle.ts` for details on why this is needed\nconst jsonError: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} catch (e: any) {\n\t\tconst error = reduceError(e);\n\t\treturn Response.json(error, {\n\t\t\tstatus: 500,\n\t\t\theaders: { \"MF-Experimental-Error-Stack\": \"true\" },\n\t\t});\n\t}\n};\n\nexport default jsonError;\n", "\t\t\t\timport worker, * as OTHER_EXPORTS from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\timport * as __MIDDLEWARE_0__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts\";\nimport * as __MIDDLEWARE_1__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts\";\n\n\t\t\t\texport * from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\tconst MIDDLEWARE_TEST_INJECT = \"__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__\";\n\t\t\t\texport const __INTERNAL_WRANGLER_MIDDLEWARE__ = [\n\t\t\t\t\t\n\t\t\t\t\t__MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default\n\t\t\t\t]\n\t\t\t\texport default worker;", "export type Awaitable = T | Promise;\n// TODO: allow dispatching more events?\nexport type Dispatcher = (\n\ttype: \"scheduled\",\n\tinit: { cron?: string }\n) => Awaitable;\n\nexport type IncomingRequest = Request<\n\tunknown,\n\tIncomingRequestCfProperties\n>;\n\nexport interface MiddlewareContext {\n\tdispatch: Dispatcher;\n\tnext(request: IncomingRequest, env: any): Awaitable;\n}\n\nexport type Middleware = (\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tmiddlewareCtx: MiddlewareContext\n) => Awaitable;\n\nconst __facade_middleware__: Middleware[] = [];\n\n// The register functions allow for the insertion of one or many middleware,\n// We register internal middleware first in the stack, but have no way of controlling\n// the order that addMiddleware is run in service workers so need an internal function.\nexport function __facade_register__(...args: (Middleware | Middleware[])[]) {\n\t__facade_middleware__.push(...args.flat());\n}\nexport function __facade_registerInternal__(\n\t...args: (Middleware | Middleware[])[]\n) {\n\t__facade_middleware__.unshift(...args.flat());\n}\n\nfunction __facade_invokeChain__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tmiddlewareChain: Middleware[]\n): Awaitable {\n\tconst [head, ...tail] = middlewareChain;\n\tconst middlewareCtx: MiddlewareContext = {\n\t\tdispatch,\n\t\tnext(newRequest, newEnv) {\n\t\t\treturn __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);\n\t\t},\n\t};\n\treturn head(request, env, ctx, middlewareCtx);\n}\n\nexport function __facade_invoke__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tfinalMiddleware: Middleware\n): Awaitable {\n\treturn __facade_invokeChain__(request, env, ctx, dispatch, [\n\t\t...__facade_middleware__,\n\t\tfinalMiddleware,\n\t]);\n}\n", "// This loads all middlewares exposed on the middleware object and then starts\n// the invocation chain. The big idea is that we can add these to the middleware\n// export dynamically through wrangler, or we can potentially let users directly\n// add them as a sort of \"plugin\" system.\n\nimport ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js\";\nimport { __facade_invoke__, __facade_register__, Dispatcher } from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts\";\nimport type { WorkerEntrypointConstructor } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js\";\n\n// Preserve all the exports from the worker\nexport * from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js\";\n\nclass __Facade_ScheduledController__ implements ScheduledController {\n\treadonly #noRetry: ScheduledController[\"noRetry\"];\n\n\tconstructor(\n\t\treadonly scheduledTime: number,\n\t\treadonly cron: string,\n\t\tnoRetry: ScheduledController[\"noRetry\"]\n\t) {\n\t\tthis.#noRetry = noRetry;\n\t}\n\n\tnoRetry() {\n\t\tif (!(this instanceof __Facade_ScheduledController__)) {\n\t\t\tthrow new TypeError(\"Illegal invocation\");\n\t\t}\n\t\t// Need to call native method immediately in case uncaught error thrown\n\t\tthis.#noRetry();\n\t}\n}\n\nfunction wrapExportedHandler(worker: ExportedHandler): ExportedHandler {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn worker;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\tconst fetchDispatcher: ExportedHandlerFetchHandler = function (\n\t\trequest,\n\t\tenv,\n\t\tctx\n\t) {\n\t\tif (worker.fetch === undefined) {\n\t\t\tthrow new Error(\"Handler does not export a fetch() function.\");\n\t\t}\n\t\treturn worker.fetch(request, env, ctx);\n\t};\n\n\treturn {\n\t\t...worker,\n\t\tfetch(request, env, ctx) {\n\t\t\tconst dispatcher: Dispatcher = function (type, init) {\n\t\t\t\tif (type === \"scheduled\" && worker.scheduled !== undefined) {\n\t\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\t\tDate.now(),\n\t\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t\t() => {}\n\t\t\t\t\t);\n\t\t\t\t\treturn worker.scheduled(controller, env, ctx);\n\t\t\t\t}\n\t\t\t};\n\t\t\treturn __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);\n\t\t},\n\t};\n}\n\nfunction wrapWorkerEntrypoint(\n\tklass: WorkerEntrypointConstructor\n): WorkerEntrypointConstructor {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn klass;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\t// `extend`ing `klass` here so other RPC methods remain callable\n\treturn class extends klass {\n\t\t#fetchDispatcher: ExportedHandlerFetchHandler> = (\n\t\t\trequest,\n\t\t\tenv,\n\t\t\tctx\n\t\t) => {\n\t\t\tthis.env = env;\n\t\t\tthis.ctx = ctx;\n\t\t\tif (super.fetch === undefined) {\n\t\t\t\tthrow new Error(\"Entrypoint class does not define a fetch() function.\");\n\t\t\t}\n\t\t\treturn super.fetch(request);\n\t\t};\n\n\t\t#dispatcher: Dispatcher = (type, init) => {\n\t\t\tif (type === \"scheduled\" && super.scheduled !== undefined) {\n\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\tDate.now(),\n\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t() => {}\n\t\t\t\t);\n\t\t\t\treturn super.scheduled(controller);\n\t\t\t}\n\t\t};\n\n\t\tfetch(request: Request) {\n\t\t\treturn __facade_invoke__(\n\t\t\t\trequest,\n\t\t\t\tthis.env,\n\t\t\t\tthis.ctx,\n\t\t\t\tthis.#dispatcher,\n\t\t\t\tthis.#fetchDispatcher\n\t\t\t);\n\t\t}\n\t};\n}\n\nlet WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;\nif (typeof ENTRY === \"object\") {\n\tWRAPPED_ENTRY = wrapExportedHandler(ENTRY);\n} else if (typeof ENTRY === \"function\") {\n\tWRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);\n}\nexport default WRAPPED_ENTRY;\n"], "mappings": "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", "names": [] } From 9826fc9f8bb58b87048ba8bb8c0f7015328a33a8 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 17:49:04 +0200 Subject: [PATCH 05/20] chore: stop tracking wrangler.toml to protect secrets --- leanring-buddy/CompanionPanelView.swift | 78 +++++++------------ worker/.gitignore | 1 + .../checked-fetch.js | 0 .../middleware-insertion-facade.js | 0 .../middleware-loader.entry.ts | 6 +- .../tmp/{dev-gqKXl3 => dev-y12pGW}/index.js | 6 +- .../{dev-gqKXl3 => dev-y12pGW}/index.js.map | 6 +- worker/wrangler.toml | 8 -- 8 files changed, 38 insertions(+), 67 deletions(-) create mode 100644 worker/.gitignore rename worker/.wrangler/tmp/{bundle-ldL9Qw => bundle-GfZtpz}/checked-fetch.js (100%) rename worker/.wrangler/tmp/{bundle-ldL9Qw => bundle-GfZtpz}/middleware-insertion-facade.js (100%) rename worker/.wrangler/tmp/{bundle-ldL9Qw => bundle-GfZtpz}/middleware-loader.entry.ts (94%) rename worker/.wrangler/tmp/{dev-gqKXl3 => dev-y12pGW}/index.js (98%) rename worker/.wrangler/tmp/{dev-gqKXl3 => dev-y12pGW}/index.js.map (98%) delete mode 100644 worker/wrangler.toml diff --git a/leanring-buddy/CompanionPanelView.swift b/leanring-buddy/CompanionPanelView.swift index 645cd249..4d779dcd 100644 --- a/leanring-buddy/CompanionPanelView.swift +++ b/leanring-buddy/CompanionPanelView.swift @@ -606,63 +606,41 @@ struct CompanionPanelView: View { Spacer() - Menu { - if companionManager.availableModels.isEmpty { - Text("No models found").disabled(true) - } else { - ForEach(companionManager.availableModels, id: \.self) { modelID in - Button(action: { - companionManager.setSelectedModel(modelID) - }) { - HStack { - Text(modelID) - if companionManager.selectedModel == modelID { - Image(systemName: "checkmark") - } - } - } - } - } - } label: { - HStack(spacing: 4) { - Text(companionManager.selectedModel) - .font(.system(size: 11, weight: .medium)) - .foregroundColor(DS.Colors.textPrimary) - .lineLimit(1) - .truncationMode(.middle) - - Image(systemName: "chevron.up.chevron.down") - .font(.system(size: 9)) - .foregroundColor(DS.Colors.textTertiary) - } + HStack(spacing: 0) { + modelOptionButton(label: "Gemma 4", modelID: "gemma-4-e4b-uncensored-hauhaucs-aggressive") + modelOptionButton(label: "Gemma 4 IT", modelID: "gemma-4-e4b-uncensored-hauhaucs-aggressive-it") + } + .background( + RoundedRectangle(cornerRadius: 6, style: .continuous) + .fill(Color.white.opacity(0.06)) + ) + .overlay( + RoundedRectangle(cornerRadius: 6, style: .continuous) + .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) + ) + } + .padding(.vertical, 4) + } + + private func modelOptionButton(label: String, modelID: String) -> some View { + let isSelected = companionManager.selectedModel == modelID + return Button(action: { + companionManager.setSelectedModel(modelID) + }) { + Text(label) + .font(.system(size: 11, weight: .medium)) + .foregroundColor(isSelected ? DS.Colors.textPrimary : DS.Colors.textTertiary) .padding(.horizontal, 10) .padding(.vertical, 5) .background( - RoundedRectangle(cornerRadius: 6, style: .continuous) - .fill(Color.white.opacity(0.06)) + RoundedRectangle(cornerRadius: 5, style: .continuous) + .fill(isSelected ? Color.white.opacity(0.1) : Color.clear) ) - .overlay( - RoundedRectangle(cornerRadius: 6, style: .continuous) - .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) - ) - } - .menuStyle(.borderlessButton) - .frame(maxWidth: 180) - - Button(action: { - companionManager.fetchAvailableModels() - }) { - Image(systemName: "arrow.triangle.2.circlepath") - .font(.system(size: 11)) - .foregroundColor(DS.Colors.textTertiary) - } - .buttonStyle(.plain) } - .padding(.vertical, 4) + .buttonStyle(.plain) + .pointerCursor() } - - // MARK: - DM Farza Button private var dmFarzaButton: some View { diff --git a/worker/.gitignore b/worker/.gitignore new file mode 100644 index 00000000..e4eccd73 --- /dev/null +++ b/worker/.gitignore @@ -0,0 +1 @@ +wrangler.toml diff --git a/worker/.wrangler/tmp/bundle-ldL9Qw/checked-fetch.js b/worker/.wrangler/tmp/bundle-GfZtpz/checked-fetch.js similarity index 100% rename from worker/.wrangler/tmp/bundle-ldL9Qw/checked-fetch.js rename to worker/.wrangler/tmp/bundle-GfZtpz/checked-fetch.js diff --git a/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js b/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js similarity index 100% rename from worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js rename to worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js diff --git a/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-loader.entry.ts b/worker/.wrangler/tmp/bundle-GfZtpz/middleware-loader.entry.ts similarity index 94% rename from worker/.wrangler/tmp/bundle-ldL9Qw/middleware-loader.entry.ts rename to worker/.wrangler/tmp/bundle-GfZtpz/middleware-loader.entry.ts index 45bd24fa..171eaed4 100644 --- a/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-loader.entry.ts +++ b/worker/.wrangler/tmp/bundle-GfZtpz/middleware-loader.entry.ts @@ -3,12 +3,12 @@ // export dynamically through wrangler, or we can potentially let users directly // add them as a sort of "plugin" system. -import ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js"; +import ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js"; import { __facade_invoke__, __facade_register__, Dispatcher } from "/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts"; -import type { WorkerEntrypointConstructor } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js"; +import type { WorkerEntrypointConstructor } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js"; // Preserve all the exports from the worker -export * from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js"; +export * from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js"; class __Facade_ScheduledController__ implements ScheduledController { readonly #noRetry: ScheduledController["noRetry"]; diff --git a/worker/.wrangler/tmp/dev-gqKXl3/index.js b/worker/.wrangler/tmp/dev-y12pGW/index.js similarity index 98% rename from worker/.wrangler/tmp/dev-gqKXl3/index.js rename to worker/.wrangler/tmp/dev-y12pGW/index.js index 62cae07d..9d01d47e 100644 --- a/worker/.wrangler/tmp/dev-gqKXl3/index.js +++ b/worker/.wrangler/tmp/dev-y12pGW/index.js @@ -1,7 +1,7 @@ var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); -// .wrangler/tmp/bundle-ldL9Qw/checked-fetch.js +// .wrangler/tmp/bundle-GfZtpz/checked-fetch.js var urls = /* @__PURE__ */ new Set(); function checkURL(request, init) { const url = request instanceof URL ? request : new URL( @@ -180,7 +180,7 @@ var jsonError = /* @__PURE__ */ __name(async (request, env, _ctx, middlewareCtx) }, "jsonError"); var middleware_miniflare3_json_error_default = jsonError; -// .wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js +// .wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js var __INTERNAL_WRANGLER_MIDDLEWARE__ = [ middleware_ensure_req_body_drained_default, middleware_miniflare3_json_error_default @@ -212,7 +212,7 @@ function __facade_invoke__(request, env, ctx, dispatch, finalMiddleware) { } __name(__facade_invoke__, "__facade_invoke__"); -// .wrangler/tmp/bundle-ldL9Qw/middleware-loader.entry.ts +// .wrangler/tmp/bundle-GfZtpz/middleware-loader.entry.ts var __Facade_ScheduledController__ = class ___Facade_ScheduledController__ { constructor(scheduledTime, cron, noRetry) { this.scheduledTime = scheduledTime; diff --git a/worker/.wrangler/tmp/dev-gqKXl3/index.js.map b/worker/.wrangler/tmp/dev-y12pGW/index.js.map similarity index 98% rename from worker/.wrangler/tmp/dev-gqKXl3/index.js.map rename to worker/.wrangler/tmp/dev-y12pGW/index.js.map index daacda6c..21947230 100644 --- a/worker/.wrangler/tmp/dev-gqKXl3/index.js.map +++ b/worker/.wrangler/tmp/dev-y12pGW/index.js.map @@ -1,8 +1,8 @@ { "version": 3, - "sources": ["../bundle-ldL9Qw/checked-fetch.js", "../../../src/index.ts", "../../../node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts", "../../../node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts", "../bundle-ldL9Qw/middleware-insertion-facade.js", "../../../node_modules/wrangler/templates/middleware/common.ts", "../bundle-ldL9Qw/middleware-loader.entry.ts"], - "sourceRoot": "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/dev-gqKXl3", - "sourcesContent": ["const urls = new Set();\n\nfunction checkURL(request, init) {\n\tconst url =\n\t\trequest instanceof URL\n\t\t\t? request\n\t\t\t: new URL(\n\t\t\t\t\t(typeof request === \"string\" ? new Request(request, init) : request)\n\t\t\t\t\t\t.url\n\t\t\t\t);\n\tif (url.port && url.port !== \"443\" && url.protocol === \"https:\") {\n\t\tif (!urls.has(url.toString())) {\n\t\t\turls.add(url.toString());\n\t\t\tconsole.warn(\n\t\t\t\t`WARNING: known issue with \\`fetch()\\` requests to custom HTTPS ports in published Workers:\\n` +\n\t\t\t\t\t` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \\`wrangler deploy\\` command.\\n`\n\t\t\t);\n\t\t}\n\t}\n}\n\nglobalThis.fetch = new Proxy(globalThis.fetch, {\n\tapply(target, thisArg, argArray) {\n\t\tconst [request, init] = argArray;\n\t\tcheckURL(request, init);\n\t\treturn Reflect.apply(target, thisArg, argArray);\n\t},\n});\n", "/**\n * Clicky Proxy Worker\n *\n * Proxies requests to Claude and ElevenLabs APIs so the app never\n * ships with raw API keys. Keys are stored as Cloudflare secrets.\n *\n * Routes:\n * POST /chat \u2192 Anthropic Messages API (streaming)\n * POST /tts \u2192 ElevenLabs TTS API\n */\n\ninterface Env {\n LM_STUDIO_API_KEY: string;\n ELEVENLABS_API_KEY: string;\n ELEVENLABS_VOICE_ID: string;\n ASSEMBLYAI_API_KEY: string;\n}\n\nexport default {\n async fetch(request: Request, env: Env): Promise {\n const url = new URL(request.url);\n\n if (request.method !== \"POST\") {\n return new Response(\"Method not allowed\", { status: 405 });\n }\n\n try {\n if (url.pathname === \"/chat\") {\n return await handleChat(request, env);\n }\n\n if (url.pathname === \"/tts\") {\n return await handleTTS(request, env);\n }\n\n if (url.pathname === \"/transcribe-token\") {\n return await handleTranscribeToken(env);\n }\n } catch (error) {\n console.error(`[${url.pathname}] Unhandled error:`, error);\n return new Response(\n JSON.stringify({ error: String(error) }),\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n }\n\n return new Response(\"Not found\", { status: 404 });\n },\n};\n\nasync function handleChat(request: Request, env: Env): Promise {\n const body = await request.text();\n\n const response = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"x-api-key\": env.LM_STUDIO_API_KEY,\n \"anthropic-version\": \"2023-06-01\",\n \"content-type\": \"application/json\",\n },\n body,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n },\n });\n}\n\nasync function handleTranscribeToken(env: Env): Promise {\n const response = await fetch(\n \"https://streaming.assemblyai.com/v3/token?expires_in_seconds=480\",\n {\n method: \"GET\",\n headers: {\n authorization: env.ASSEMBLYAI_API_KEY,\n },\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n const data = await response.text();\n return new Response(data, {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nasync function handleTTS(request: Request, env: Env): Promise {\n const body = await request.text();\n const voiceId = env.ELEVENLABS_VOICE_ID;\n\n const response = await fetch(\n `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,\n {\n method: \"POST\",\n headers: {\n \"xi-api-key\": env.ELEVENLABS_API_KEY,\n \"content-type\": \"application/json\",\n accept: \"audio/mpeg\",\n },\n body,\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"audio/mpeg\",\n },\n });\n}\n", "import type { Middleware } from \"./common\";\n\nconst drainBody: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} finally {\n\t\ttry {\n\t\t\tif (request.body !== null && !request.bodyUsed) {\n\t\t\t\tconst reader = request.body.getReader();\n\t\t\t\twhile (!(await reader.read()).done) {}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(\"Failed to drain the unused request body.\", e);\n\t\t}\n\t}\n};\n\nexport default drainBody;\n", "import type { Middleware } from \"./common\";\n\ninterface JsonError {\n\tmessage?: string;\n\tname?: string;\n\tstack?: string;\n\tcause?: JsonError;\n}\n\nfunction reduceError(e: any): JsonError {\n\treturn {\n\t\tname: e?.name,\n\t\tmessage: e?.message ?? String(e),\n\t\tstack: e?.stack,\n\t\tcause: e?.cause === undefined ? undefined : reduceError(e.cause),\n\t};\n}\n\n// See comment in `bundle.ts` for details on why this is needed\nconst jsonError: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} catch (e: any) {\n\t\tconst error = reduceError(e);\n\t\treturn Response.json(error, {\n\t\t\tstatus: 500,\n\t\t\theaders: { \"MF-Experimental-Error-Stack\": \"true\" },\n\t\t});\n\t}\n};\n\nexport default jsonError;\n", "\t\t\t\timport worker, * as OTHER_EXPORTS from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\timport * as __MIDDLEWARE_0__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts\";\nimport * as __MIDDLEWARE_1__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts\";\n\n\t\t\t\texport * from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\tconst MIDDLEWARE_TEST_INJECT = \"__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__\";\n\t\t\t\texport const __INTERNAL_WRANGLER_MIDDLEWARE__ = [\n\t\t\t\t\t\n\t\t\t\t\t__MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default\n\t\t\t\t]\n\t\t\t\texport default worker;", "export type Awaitable = T | Promise;\n// TODO: allow dispatching more events?\nexport type Dispatcher = (\n\ttype: \"scheduled\",\n\tinit: { cron?: string }\n) => Awaitable;\n\nexport type IncomingRequest = Request<\n\tunknown,\n\tIncomingRequestCfProperties\n>;\n\nexport interface MiddlewareContext {\n\tdispatch: Dispatcher;\n\tnext(request: IncomingRequest, env: any): Awaitable;\n}\n\nexport type Middleware = (\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tmiddlewareCtx: MiddlewareContext\n) => Awaitable;\n\nconst __facade_middleware__: Middleware[] = [];\n\n// The register functions allow for the insertion of one or many middleware,\n// We register internal middleware first in the stack, but have no way of controlling\n// the order that addMiddleware is run in service workers so need an internal function.\nexport function __facade_register__(...args: (Middleware | Middleware[])[]) {\n\t__facade_middleware__.push(...args.flat());\n}\nexport function __facade_registerInternal__(\n\t...args: (Middleware | Middleware[])[]\n) {\n\t__facade_middleware__.unshift(...args.flat());\n}\n\nfunction __facade_invokeChain__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tmiddlewareChain: Middleware[]\n): Awaitable {\n\tconst [head, ...tail] = middlewareChain;\n\tconst middlewareCtx: MiddlewareContext = {\n\t\tdispatch,\n\t\tnext(newRequest, newEnv) {\n\t\t\treturn __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);\n\t\t},\n\t};\n\treturn head(request, env, ctx, middlewareCtx);\n}\n\nexport function __facade_invoke__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tfinalMiddleware: Middleware\n): Awaitable {\n\treturn __facade_invokeChain__(request, env, ctx, dispatch, [\n\t\t...__facade_middleware__,\n\t\tfinalMiddleware,\n\t]);\n}\n", "// This loads all middlewares exposed on the middleware object and then starts\n// the invocation chain. The big idea is that we can add these to the middleware\n// export dynamically through wrangler, or we can potentially let users directly\n// add them as a sort of \"plugin\" system.\n\nimport ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js\";\nimport { __facade_invoke__, __facade_register__, Dispatcher } from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts\";\nimport type { WorkerEntrypointConstructor } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js\";\n\n// Preserve all the exports from the worker\nexport * from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-ldL9Qw/middleware-insertion-facade.js\";\n\nclass __Facade_ScheduledController__ implements ScheduledController {\n\treadonly #noRetry: ScheduledController[\"noRetry\"];\n\n\tconstructor(\n\t\treadonly scheduledTime: number,\n\t\treadonly cron: string,\n\t\tnoRetry: ScheduledController[\"noRetry\"]\n\t) {\n\t\tthis.#noRetry = noRetry;\n\t}\n\n\tnoRetry() {\n\t\tif (!(this instanceof __Facade_ScheduledController__)) {\n\t\t\tthrow new TypeError(\"Illegal invocation\");\n\t\t}\n\t\t// Need to call native method immediately in case uncaught error thrown\n\t\tthis.#noRetry();\n\t}\n}\n\nfunction wrapExportedHandler(worker: ExportedHandler): ExportedHandler {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn worker;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\tconst fetchDispatcher: ExportedHandlerFetchHandler = function (\n\t\trequest,\n\t\tenv,\n\t\tctx\n\t) {\n\t\tif (worker.fetch === undefined) {\n\t\t\tthrow new Error(\"Handler does not export a fetch() function.\");\n\t\t}\n\t\treturn worker.fetch(request, env, ctx);\n\t};\n\n\treturn {\n\t\t...worker,\n\t\tfetch(request, env, ctx) {\n\t\t\tconst dispatcher: Dispatcher = function (type, init) {\n\t\t\t\tif (type === \"scheduled\" && worker.scheduled !== undefined) {\n\t\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\t\tDate.now(),\n\t\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t\t() => {}\n\t\t\t\t\t);\n\t\t\t\t\treturn worker.scheduled(controller, env, ctx);\n\t\t\t\t}\n\t\t\t};\n\t\t\treturn __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);\n\t\t},\n\t};\n}\n\nfunction wrapWorkerEntrypoint(\n\tklass: WorkerEntrypointConstructor\n): WorkerEntrypointConstructor {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn klass;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\t// `extend`ing `klass` here so other RPC methods remain callable\n\treturn class extends klass {\n\t\t#fetchDispatcher: ExportedHandlerFetchHandler> = (\n\t\t\trequest,\n\t\t\tenv,\n\t\t\tctx\n\t\t) => {\n\t\t\tthis.env = env;\n\t\t\tthis.ctx = ctx;\n\t\t\tif (super.fetch === undefined) {\n\t\t\t\tthrow new Error(\"Entrypoint class does not define a fetch() function.\");\n\t\t\t}\n\t\t\treturn super.fetch(request);\n\t\t};\n\n\t\t#dispatcher: Dispatcher = (type, init) => {\n\t\t\tif (type === \"scheduled\" && super.scheduled !== undefined) {\n\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\tDate.now(),\n\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t() => {}\n\t\t\t\t);\n\t\t\t\treturn super.scheduled(controller);\n\t\t\t}\n\t\t};\n\n\t\tfetch(request: Request) {\n\t\t\treturn __facade_invoke__(\n\t\t\t\trequest,\n\t\t\t\tthis.env,\n\t\t\t\tthis.ctx,\n\t\t\t\tthis.#dispatcher,\n\t\t\t\tthis.#fetchDispatcher\n\t\t\t);\n\t\t}\n\t};\n}\n\nlet WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;\nif (typeof ENTRY === \"object\") {\n\tWRAPPED_ENTRY = wrapExportedHandler(ENTRY);\n} else if (typeof ENTRY === \"function\") {\n\tWRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);\n}\nexport default WRAPPED_ENTRY;\n"], + "sources": ["../bundle-GfZtpz/checked-fetch.js", "../../../src/index.ts", "../../../node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts", "../../../node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts", "../bundle-GfZtpz/middleware-insertion-facade.js", "../../../node_modules/wrangler/templates/middleware/common.ts", "../bundle-GfZtpz/middleware-loader.entry.ts"], + "sourceRoot": "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/dev-y12pGW", + "sourcesContent": ["const urls = new Set();\n\nfunction checkURL(request, init) {\n\tconst url =\n\t\trequest instanceof URL\n\t\t\t? request\n\t\t\t: new URL(\n\t\t\t\t\t(typeof request === \"string\" ? new Request(request, init) : request)\n\t\t\t\t\t\t.url\n\t\t\t\t);\n\tif (url.port && url.port !== \"443\" && url.protocol === \"https:\") {\n\t\tif (!urls.has(url.toString())) {\n\t\t\turls.add(url.toString());\n\t\t\tconsole.warn(\n\t\t\t\t`WARNING: known issue with \\`fetch()\\` requests to custom HTTPS ports in published Workers:\\n` +\n\t\t\t\t\t` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \\`wrangler deploy\\` command.\\n`\n\t\t\t);\n\t\t}\n\t}\n}\n\nglobalThis.fetch = new Proxy(globalThis.fetch, {\n\tapply(target, thisArg, argArray) {\n\t\tconst [request, init] = argArray;\n\t\tcheckURL(request, init);\n\t\treturn Reflect.apply(target, thisArg, argArray);\n\t},\n});\n", "/**\n * Clicky Proxy Worker\n *\n * Proxies requests to Claude and ElevenLabs APIs so the app never\n * ships with raw API keys. Keys are stored as Cloudflare secrets.\n *\n * Routes:\n * POST /chat \u2192 Anthropic Messages API (streaming)\n * POST /tts \u2192 ElevenLabs TTS API\n */\n\ninterface Env {\n LM_STUDIO_API_KEY: string;\n ELEVENLABS_API_KEY: string;\n ELEVENLABS_VOICE_ID: string;\n ASSEMBLYAI_API_KEY: string;\n}\n\nexport default {\n async fetch(request: Request, env: Env): Promise {\n const url = new URL(request.url);\n\n if (request.method !== \"POST\") {\n return new Response(\"Method not allowed\", { status: 405 });\n }\n\n try {\n if (url.pathname === \"/chat\") {\n return await handleChat(request, env);\n }\n\n if (url.pathname === \"/tts\") {\n return await handleTTS(request, env);\n }\n\n if (url.pathname === \"/transcribe-token\") {\n return await handleTranscribeToken(env);\n }\n } catch (error) {\n console.error(`[${url.pathname}] Unhandled error:`, error);\n return new Response(\n JSON.stringify({ error: String(error) }),\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n }\n\n return new Response(\"Not found\", { status: 404 });\n },\n};\n\nasync function handleChat(request: Request, env: Env): Promise {\n const body = await request.text();\n\n const response = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"x-api-key\": env.LM_STUDIO_API_KEY,\n \"anthropic-version\": \"2023-06-01\",\n \"content-type\": \"application/json\",\n },\n body,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n },\n });\n}\n\nasync function handleTranscribeToken(env: Env): Promise {\n const response = await fetch(\n \"https://streaming.assemblyai.com/v3/token?expires_in_seconds=480\",\n {\n method: \"GET\",\n headers: {\n authorization: env.ASSEMBLYAI_API_KEY,\n },\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n const data = await response.text();\n return new Response(data, {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nasync function handleTTS(request: Request, env: Env): Promise {\n const body = await request.text();\n const voiceId = env.ELEVENLABS_VOICE_ID;\n\n const response = await fetch(\n `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,\n {\n method: \"POST\",\n headers: {\n \"xi-api-key\": env.ELEVENLABS_API_KEY,\n \"content-type\": \"application/json\",\n accept: \"audio/mpeg\",\n },\n body,\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"audio/mpeg\",\n },\n });\n}\n", "import type { Middleware } from \"./common\";\n\nconst drainBody: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} finally {\n\t\ttry {\n\t\t\tif (request.body !== null && !request.bodyUsed) {\n\t\t\t\tconst reader = request.body.getReader();\n\t\t\t\twhile (!(await reader.read()).done) {}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(\"Failed to drain the unused request body.\", e);\n\t\t}\n\t}\n};\n\nexport default drainBody;\n", "import type { Middleware } from \"./common\";\n\ninterface JsonError {\n\tmessage?: string;\n\tname?: string;\n\tstack?: string;\n\tcause?: JsonError;\n}\n\nfunction reduceError(e: any): JsonError {\n\treturn {\n\t\tname: e?.name,\n\t\tmessage: e?.message ?? String(e),\n\t\tstack: e?.stack,\n\t\tcause: e?.cause === undefined ? undefined : reduceError(e.cause),\n\t};\n}\n\n// See comment in `bundle.ts` for details on why this is needed\nconst jsonError: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} catch (e: any) {\n\t\tconst error = reduceError(e);\n\t\treturn Response.json(error, {\n\t\t\tstatus: 500,\n\t\t\theaders: { \"MF-Experimental-Error-Stack\": \"true\" },\n\t\t});\n\t}\n};\n\nexport default jsonError;\n", "\t\t\t\timport worker, * as OTHER_EXPORTS from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\timport * as __MIDDLEWARE_0__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts\";\nimport * as __MIDDLEWARE_1__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts\";\n\n\t\t\t\texport * from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\tconst MIDDLEWARE_TEST_INJECT = \"__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__\";\n\t\t\t\texport const __INTERNAL_WRANGLER_MIDDLEWARE__ = [\n\t\t\t\t\t\n\t\t\t\t\t__MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default\n\t\t\t\t]\n\t\t\t\texport default worker;", "export type Awaitable = T | Promise;\n// TODO: allow dispatching more events?\nexport type Dispatcher = (\n\ttype: \"scheduled\",\n\tinit: { cron?: string }\n) => Awaitable;\n\nexport type IncomingRequest = Request<\n\tunknown,\n\tIncomingRequestCfProperties\n>;\n\nexport interface MiddlewareContext {\n\tdispatch: Dispatcher;\n\tnext(request: IncomingRequest, env: any): Awaitable;\n}\n\nexport type Middleware = (\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tmiddlewareCtx: MiddlewareContext\n) => Awaitable;\n\nconst __facade_middleware__: Middleware[] = [];\n\n// The register functions allow for the insertion of one or many middleware,\n// We register internal middleware first in the stack, but have no way of controlling\n// the order that addMiddleware is run in service workers so need an internal function.\nexport function __facade_register__(...args: (Middleware | Middleware[])[]) {\n\t__facade_middleware__.push(...args.flat());\n}\nexport function __facade_registerInternal__(\n\t...args: (Middleware | Middleware[])[]\n) {\n\t__facade_middleware__.unshift(...args.flat());\n}\n\nfunction __facade_invokeChain__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tmiddlewareChain: Middleware[]\n): Awaitable {\n\tconst [head, ...tail] = middlewareChain;\n\tconst middlewareCtx: MiddlewareContext = {\n\t\tdispatch,\n\t\tnext(newRequest, newEnv) {\n\t\t\treturn __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);\n\t\t},\n\t};\n\treturn head(request, env, ctx, middlewareCtx);\n}\n\nexport function __facade_invoke__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tfinalMiddleware: Middleware\n): Awaitable {\n\treturn __facade_invokeChain__(request, env, ctx, dispatch, [\n\t\t...__facade_middleware__,\n\t\tfinalMiddleware,\n\t]);\n}\n", "// This loads all middlewares exposed on the middleware object and then starts\n// the invocation chain. The big idea is that we can add these to the middleware\n// export dynamically through wrangler, or we can potentially let users directly\n// add them as a sort of \"plugin\" system.\n\nimport ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js\";\nimport { __facade_invoke__, __facade_register__, Dispatcher } from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts\";\nimport type { WorkerEntrypointConstructor } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js\";\n\n// Preserve all the exports from the worker\nexport * from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js\";\n\nclass __Facade_ScheduledController__ implements ScheduledController {\n\treadonly #noRetry: ScheduledController[\"noRetry\"];\n\n\tconstructor(\n\t\treadonly scheduledTime: number,\n\t\treadonly cron: string,\n\t\tnoRetry: ScheduledController[\"noRetry\"]\n\t) {\n\t\tthis.#noRetry = noRetry;\n\t}\n\n\tnoRetry() {\n\t\tif (!(this instanceof __Facade_ScheduledController__)) {\n\t\t\tthrow new TypeError(\"Illegal invocation\");\n\t\t}\n\t\t// Need to call native method immediately in case uncaught error thrown\n\t\tthis.#noRetry();\n\t}\n}\n\nfunction wrapExportedHandler(worker: ExportedHandler): ExportedHandler {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn worker;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\tconst fetchDispatcher: ExportedHandlerFetchHandler = function (\n\t\trequest,\n\t\tenv,\n\t\tctx\n\t) {\n\t\tif (worker.fetch === undefined) {\n\t\t\tthrow new Error(\"Handler does not export a fetch() function.\");\n\t\t}\n\t\treturn worker.fetch(request, env, ctx);\n\t};\n\n\treturn {\n\t\t...worker,\n\t\tfetch(request, env, ctx) {\n\t\t\tconst dispatcher: Dispatcher = function (type, init) {\n\t\t\t\tif (type === \"scheduled\" && worker.scheduled !== undefined) {\n\t\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\t\tDate.now(),\n\t\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t\t() => {}\n\t\t\t\t\t);\n\t\t\t\t\treturn worker.scheduled(controller, env, ctx);\n\t\t\t\t}\n\t\t\t};\n\t\t\treturn __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);\n\t\t},\n\t};\n}\n\nfunction wrapWorkerEntrypoint(\n\tklass: WorkerEntrypointConstructor\n): WorkerEntrypointConstructor {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn klass;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\t// `extend`ing `klass` here so other RPC methods remain callable\n\treturn class extends klass {\n\t\t#fetchDispatcher: ExportedHandlerFetchHandler> = (\n\t\t\trequest,\n\t\t\tenv,\n\t\t\tctx\n\t\t) => {\n\t\t\tthis.env = env;\n\t\t\tthis.ctx = ctx;\n\t\t\tif (super.fetch === undefined) {\n\t\t\t\tthrow new Error(\"Entrypoint class does not define a fetch() function.\");\n\t\t\t}\n\t\t\treturn super.fetch(request);\n\t\t};\n\n\t\t#dispatcher: Dispatcher = (type, init) => {\n\t\t\tif (type === \"scheduled\" && super.scheduled !== undefined) {\n\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\tDate.now(),\n\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t() => {}\n\t\t\t\t);\n\t\t\t\treturn super.scheduled(controller);\n\t\t\t}\n\t\t};\n\n\t\tfetch(request: Request) {\n\t\t\treturn __facade_invoke__(\n\t\t\t\trequest,\n\t\t\t\tthis.env,\n\t\t\t\tthis.ctx,\n\t\t\t\tthis.#dispatcher,\n\t\t\t\tthis.#fetchDispatcher\n\t\t\t);\n\t\t}\n\t};\n}\n\nlet WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;\nif (typeof ENTRY === \"object\") {\n\tWRAPPED_ENTRY = wrapExportedHandler(ENTRY);\n} else if (typeof ENTRY === \"function\") {\n\tWRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);\n}\nexport default WRAPPED_ENTRY;\n"], "mappings": "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", "names": [] } diff --git a/worker/wrangler.toml b/worker/wrangler.toml deleted file mode 100644 index 9620018b..00000000 --- a/worker/wrangler.toml +++ /dev/null @@ -1,8 +0,0 @@ -name = "clicky-proxy" -main = "src/index.ts" -compatibility_date = "2024-01-01" - -[vars] -ELEVENLABS_VOICE_ID = "CwhRBWXzGAHq8TQ4Fs17" -ASSEMBLYAI_API_KEY = "c777ed6a2e9041969f4a1fc3995fa189" -LM_STUDIO_API_KEY = "sk-lm-aAxaxZte:mOc432tNRd7CWCOh57g3" From bfb5bcac3d59d76b01d6a90ed5928acafb58cafe Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 17:50:57 +0200 Subject: [PATCH 06/20] chore: ignore .wrangler cache and tmp directory --- worker/.gitignore | 1 + .../tmp/{bundle-GfZtpz => bundle-P89daS}/checked-fetch.js | 0 .../middleware-insertion-facade.js | 0 .../middleware-loader.entry.ts | 6 +++--- worker/.wrangler/tmp/{dev-y12pGW => dev-KZUcMs}/index.js | 6 +++--- .../.wrangler/tmp/{dev-y12pGW => dev-KZUcMs}/index.js.map | 6 +++--- 6 files changed, 10 insertions(+), 9 deletions(-) rename worker/.wrangler/tmp/{bundle-GfZtpz => bundle-P89daS}/checked-fetch.js (100%) rename worker/.wrangler/tmp/{bundle-GfZtpz => bundle-P89daS}/middleware-insertion-facade.js (100%) rename worker/.wrangler/tmp/{bundle-GfZtpz => bundle-P89daS}/middleware-loader.entry.ts (94%) rename worker/.wrangler/tmp/{dev-y12pGW => dev-KZUcMs}/index.js (98%) rename worker/.wrangler/tmp/{dev-y12pGW => dev-KZUcMs}/index.js.map (98%) diff --git a/worker/.gitignore b/worker/.gitignore index e4eccd73..74f12373 100644 --- a/worker/.gitignore +++ b/worker/.gitignore @@ -1 +1,2 @@ wrangler.toml +.wrangler diff --git a/worker/.wrangler/tmp/bundle-GfZtpz/checked-fetch.js b/worker/.wrangler/tmp/bundle-P89daS/checked-fetch.js similarity index 100% rename from worker/.wrangler/tmp/bundle-GfZtpz/checked-fetch.js rename to worker/.wrangler/tmp/bundle-P89daS/checked-fetch.js diff --git a/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js b/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js similarity index 100% rename from worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js rename to worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js diff --git a/worker/.wrangler/tmp/bundle-GfZtpz/middleware-loader.entry.ts b/worker/.wrangler/tmp/bundle-P89daS/middleware-loader.entry.ts similarity index 94% rename from worker/.wrangler/tmp/bundle-GfZtpz/middleware-loader.entry.ts rename to worker/.wrangler/tmp/bundle-P89daS/middleware-loader.entry.ts index 171eaed4..5d91b54f 100644 --- a/worker/.wrangler/tmp/bundle-GfZtpz/middleware-loader.entry.ts +++ b/worker/.wrangler/tmp/bundle-P89daS/middleware-loader.entry.ts @@ -3,12 +3,12 @@ // export dynamically through wrangler, or we can potentially let users directly // add them as a sort of "plugin" system. -import ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js"; +import ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js"; import { __facade_invoke__, __facade_register__, Dispatcher } from "/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts"; -import type { WorkerEntrypointConstructor } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js"; +import type { WorkerEntrypointConstructor } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js"; // Preserve all the exports from the worker -export * from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js"; +export * from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js"; class __Facade_ScheduledController__ implements ScheduledController { readonly #noRetry: ScheduledController["noRetry"]; diff --git a/worker/.wrangler/tmp/dev-y12pGW/index.js b/worker/.wrangler/tmp/dev-KZUcMs/index.js similarity index 98% rename from worker/.wrangler/tmp/dev-y12pGW/index.js rename to worker/.wrangler/tmp/dev-KZUcMs/index.js index 9d01d47e..cf6df4a2 100644 --- a/worker/.wrangler/tmp/dev-y12pGW/index.js +++ b/worker/.wrangler/tmp/dev-KZUcMs/index.js @@ -1,7 +1,7 @@ var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); -// .wrangler/tmp/bundle-GfZtpz/checked-fetch.js +// .wrangler/tmp/bundle-P89daS/checked-fetch.js var urls = /* @__PURE__ */ new Set(); function checkURL(request, init) { const url = request instanceof URL ? request : new URL( @@ -180,7 +180,7 @@ var jsonError = /* @__PURE__ */ __name(async (request, env, _ctx, middlewareCtx) }, "jsonError"); var middleware_miniflare3_json_error_default = jsonError; -// .wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js +// .wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js var __INTERNAL_WRANGLER_MIDDLEWARE__ = [ middleware_ensure_req_body_drained_default, middleware_miniflare3_json_error_default @@ -212,7 +212,7 @@ function __facade_invoke__(request, env, ctx, dispatch, finalMiddleware) { } __name(__facade_invoke__, "__facade_invoke__"); -// .wrangler/tmp/bundle-GfZtpz/middleware-loader.entry.ts +// .wrangler/tmp/bundle-P89daS/middleware-loader.entry.ts var __Facade_ScheduledController__ = class ___Facade_ScheduledController__ { constructor(scheduledTime, cron, noRetry) { this.scheduledTime = scheduledTime; diff --git a/worker/.wrangler/tmp/dev-y12pGW/index.js.map b/worker/.wrangler/tmp/dev-KZUcMs/index.js.map similarity index 98% rename from worker/.wrangler/tmp/dev-y12pGW/index.js.map rename to worker/.wrangler/tmp/dev-KZUcMs/index.js.map index 21947230..1773d919 100644 --- a/worker/.wrangler/tmp/dev-y12pGW/index.js.map +++ b/worker/.wrangler/tmp/dev-KZUcMs/index.js.map @@ -1,8 +1,8 @@ { "version": 3, - "sources": ["../bundle-GfZtpz/checked-fetch.js", "../../../src/index.ts", "../../../node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts", "../../../node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts", "../bundle-GfZtpz/middleware-insertion-facade.js", "../../../node_modules/wrangler/templates/middleware/common.ts", "../bundle-GfZtpz/middleware-loader.entry.ts"], - "sourceRoot": "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/dev-y12pGW", - "sourcesContent": ["const urls = new Set();\n\nfunction checkURL(request, init) {\n\tconst url =\n\t\trequest instanceof URL\n\t\t\t? request\n\t\t\t: new URL(\n\t\t\t\t\t(typeof request === \"string\" ? new Request(request, init) : request)\n\t\t\t\t\t\t.url\n\t\t\t\t);\n\tif (url.port && url.port !== \"443\" && url.protocol === \"https:\") {\n\t\tif (!urls.has(url.toString())) {\n\t\t\turls.add(url.toString());\n\t\t\tconsole.warn(\n\t\t\t\t`WARNING: known issue with \\`fetch()\\` requests to custom HTTPS ports in published Workers:\\n` +\n\t\t\t\t\t` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \\`wrangler deploy\\` command.\\n`\n\t\t\t);\n\t\t}\n\t}\n}\n\nglobalThis.fetch = new Proxy(globalThis.fetch, {\n\tapply(target, thisArg, argArray) {\n\t\tconst [request, init] = argArray;\n\t\tcheckURL(request, init);\n\t\treturn Reflect.apply(target, thisArg, argArray);\n\t},\n});\n", "/**\n * Clicky Proxy Worker\n *\n * Proxies requests to Claude and ElevenLabs APIs so the app never\n * ships with raw API keys. Keys are stored as Cloudflare secrets.\n *\n * Routes:\n * POST /chat \u2192 Anthropic Messages API (streaming)\n * POST /tts \u2192 ElevenLabs TTS API\n */\n\ninterface Env {\n LM_STUDIO_API_KEY: string;\n ELEVENLABS_API_KEY: string;\n ELEVENLABS_VOICE_ID: string;\n ASSEMBLYAI_API_KEY: string;\n}\n\nexport default {\n async fetch(request: Request, env: Env): Promise {\n const url = new URL(request.url);\n\n if (request.method !== \"POST\") {\n return new Response(\"Method not allowed\", { status: 405 });\n }\n\n try {\n if (url.pathname === \"/chat\") {\n return await handleChat(request, env);\n }\n\n if (url.pathname === \"/tts\") {\n return await handleTTS(request, env);\n }\n\n if (url.pathname === \"/transcribe-token\") {\n return await handleTranscribeToken(env);\n }\n } catch (error) {\n console.error(`[${url.pathname}] Unhandled error:`, error);\n return new Response(\n JSON.stringify({ error: String(error) }),\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n }\n\n return new Response(\"Not found\", { status: 404 });\n },\n};\n\nasync function handleChat(request: Request, env: Env): Promise {\n const body = await request.text();\n\n const response = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"x-api-key\": env.LM_STUDIO_API_KEY,\n \"anthropic-version\": \"2023-06-01\",\n \"content-type\": \"application/json\",\n },\n body,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n },\n });\n}\n\nasync function handleTranscribeToken(env: Env): Promise {\n const response = await fetch(\n \"https://streaming.assemblyai.com/v3/token?expires_in_seconds=480\",\n {\n method: \"GET\",\n headers: {\n authorization: env.ASSEMBLYAI_API_KEY,\n },\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n const data = await response.text();\n return new Response(data, {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nasync function handleTTS(request: Request, env: Env): Promise {\n const body = await request.text();\n const voiceId = env.ELEVENLABS_VOICE_ID;\n\n const response = await fetch(\n `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,\n {\n method: \"POST\",\n headers: {\n \"xi-api-key\": env.ELEVENLABS_API_KEY,\n \"content-type\": \"application/json\",\n accept: \"audio/mpeg\",\n },\n body,\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"audio/mpeg\",\n },\n });\n}\n", "import type { Middleware } from \"./common\";\n\nconst drainBody: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} finally {\n\t\ttry {\n\t\t\tif (request.body !== null && !request.bodyUsed) {\n\t\t\t\tconst reader = request.body.getReader();\n\t\t\t\twhile (!(await reader.read()).done) {}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(\"Failed to drain the unused request body.\", e);\n\t\t}\n\t}\n};\n\nexport default drainBody;\n", "import type { Middleware } from \"./common\";\n\ninterface JsonError {\n\tmessage?: string;\n\tname?: string;\n\tstack?: string;\n\tcause?: JsonError;\n}\n\nfunction reduceError(e: any): JsonError {\n\treturn {\n\t\tname: e?.name,\n\t\tmessage: e?.message ?? String(e),\n\t\tstack: e?.stack,\n\t\tcause: e?.cause === undefined ? undefined : reduceError(e.cause),\n\t};\n}\n\n// See comment in `bundle.ts` for details on why this is needed\nconst jsonError: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} catch (e: any) {\n\t\tconst error = reduceError(e);\n\t\treturn Response.json(error, {\n\t\t\tstatus: 500,\n\t\t\theaders: { \"MF-Experimental-Error-Stack\": \"true\" },\n\t\t});\n\t}\n};\n\nexport default jsonError;\n", "\t\t\t\timport worker, * as OTHER_EXPORTS from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\timport * as __MIDDLEWARE_0__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts\";\nimport * as __MIDDLEWARE_1__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts\";\n\n\t\t\t\texport * from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\tconst MIDDLEWARE_TEST_INJECT = \"__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__\";\n\t\t\t\texport const __INTERNAL_WRANGLER_MIDDLEWARE__ = [\n\t\t\t\t\t\n\t\t\t\t\t__MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default\n\t\t\t\t]\n\t\t\t\texport default worker;", "export type Awaitable = T | Promise;\n// TODO: allow dispatching more events?\nexport type Dispatcher = (\n\ttype: \"scheduled\",\n\tinit: { cron?: string }\n) => Awaitable;\n\nexport type IncomingRequest = Request<\n\tunknown,\n\tIncomingRequestCfProperties\n>;\n\nexport interface MiddlewareContext {\n\tdispatch: Dispatcher;\n\tnext(request: IncomingRequest, env: any): Awaitable;\n}\n\nexport type Middleware = (\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tmiddlewareCtx: MiddlewareContext\n) => Awaitable;\n\nconst __facade_middleware__: Middleware[] = [];\n\n// The register functions allow for the insertion of one or many middleware,\n// We register internal middleware first in the stack, but have no way of controlling\n// the order that addMiddleware is run in service workers so need an internal function.\nexport function __facade_register__(...args: (Middleware | Middleware[])[]) {\n\t__facade_middleware__.push(...args.flat());\n}\nexport function __facade_registerInternal__(\n\t...args: (Middleware | Middleware[])[]\n) {\n\t__facade_middleware__.unshift(...args.flat());\n}\n\nfunction __facade_invokeChain__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tmiddlewareChain: Middleware[]\n): Awaitable {\n\tconst [head, ...tail] = middlewareChain;\n\tconst middlewareCtx: MiddlewareContext = {\n\t\tdispatch,\n\t\tnext(newRequest, newEnv) {\n\t\t\treturn __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);\n\t\t},\n\t};\n\treturn head(request, env, ctx, middlewareCtx);\n}\n\nexport function __facade_invoke__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tfinalMiddleware: Middleware\n): Awaitable {\n\treturn __facade_invokeChain__(request, env, ctx, dispatch, [\n\t\t...__facade_middleware__,\n\t\tfinalMiddleware,\n\t]);\n}\n", "// This loads all middlewares exposed on the middleware object and then starts\n// the invocation chain. The big idea is that we can add these to the middleware\n// export dynamically through wrangler, or we can potentially let users directly\n// add them as a sort of \"plugin\" system.\n\nimport ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js\";\nimport { __facade_invoke__, __facade_register__, Dispatcher } from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts\";\nimport type { WorkerEntrypointConstructor } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js\";\n\n// Preserve all the exports from the worker\nexport * from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-GfZtpz/middleware-insertion-facade.js\";\n\nclass __Facade_ScheduledController__ implements ScheduledController {\n\treadonly #noRetry: ScheduledController[\"noRetry\"];\n\n\tconstructor(\n\t\treadonly scheduledTime: number,\n\t\treadonly cron: string,\n\t\tnoRetry: ScheduledController[\"noRetry\"]\n\t) {\n\t\tthis.#noRetry = noRetry;\n\t}\n\n\tnoRetry() {\n\t\tif (!(this instanceof __Facade_ScheduledController__)) {\n\t\t\tthrow new TypeError(\"Illegal invocation\");\n\t\t}\n\t\t// Need to call native method immediately in case uncaught error thrown\n\t\tthis.#noRetry();\n\t}\n}\n\nfunction wrapExportedHandler(worker: ExportedHandler): ExportedHandler {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn worker;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\tconst fetchDispatcher: ExportedHandlerFetchHandler = function (\n\t\trequest,\n\t\tenv,\n\t\tctx\n\t) {\n\t\tif (worker.fetch === undefined) {\n\t\t\tthrow new Error(\"Handler does not export a fetch() function.\");\n\t\t}\n\t\treturn worker.fetch(request, env, ctx);\n\t};\n\n\treturn {\n\t\t...worker,\n\t\tfetch(request, env, ctx) {\n\t\t\tconst dispatcher: Dispatcher = function (type, init) {\n\t\t\t\tif (type === \"scheduled\" && worker.scheduled !== undefined) {\n\t\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\t\tDate.now(),\n\t\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t\t() => {}\n\t\t\t\t\t);\n\t\t\t\t\treturn worker.scheduled(controller, env, ctx);\n\t\t\t\t}\n\t\t\t};\n\t\t\treturn __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);\n\t\t},\n\t};\n}\n\nfunction wrapWorkerEntrypoint(\n\tklass: WorkerEntrypointConstructor\n): WorkerEntrypointConstructor {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn klass;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\t// `extend`ing `klass` here so other RPC methods remain callable\n\treturn class extends klass {\n\t\t#fetchDispatcher: ExportedHandlerFetchHandler> = (\n\t\t\trequest,\n\t\t\tenv,\n\t\t\tctx\n\t\t) => {\n\t\t\tthis.env = env;\n\t\t\tthis.ctx = ctx;\n\t\t\tif (super.fetch === undefined) {\n\t\t\t\tthrow new Error(\"Entrypoint class does not define a fetch() function.\");\n\t\t\t}\n\t\t\treturn super.fetch(request);\n\t\t};\n\n\t\t#dispatcher: Dispatcher = (type, init) => {\n\t\t\tif (type === \"scheduled\" && super.scheduled !== undefined) {\n\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\tDate.now(),\n\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t() => {}\n\t\t\t\t);\n\t\t\t\treturn super.scheduled(controller);\n\t\t\t}\n\t\t};\n\n\t\tfetch(request: Request) {\n\t\t\treturn __facade_invoke__(\n\t\t\t\trequest,\n\t\t\t\tthis.env,\n\t\t\t\tthis.ctx,\n\t\t\t\tthis.#dispatcher,\n\t\t\t\tthis.#fetchDispatcher\n\t\t\t);\n\t\t}\n\t};\n}\n\nlet WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;\nif (typeof ENTRY === \"object\") {\n\tWRAPPED_ENTRY = wrapExportedHandler(ENTRY);\n} else if (typeof ENTRY === \"function\") {\n\tWRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);\n}\nexport default WRAPPED_ENTRY;\n"], + "sources": ["../bundle-P89daS/checked-fetch.js", "../../../src/index.ts", "../../../node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts", "../../../node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts", "../bundle-P89daS/middleware-insertion-facade.js", "../../../node_modules/wrangler/templates/middleware/common.ts", "../bundle-P89daS/middleware-loader.entry.ts"], + "sourceRoot": "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/dev-KZUcMs", + "sourcesContent": ["const urls = new Set();\n\nfunction checkURL(request, init) {\n\tconst url =\n\t\trequest instanceof URL\n\t\t\t? request\n\t\t\t: new URL(\n\t\t\t\t\t(typeof request === \"string\" ? new Request(request, init) : request)\n\t\t\t\t\t\t.url\n\t\t\t\t);\n\tif (url.port && url.port !== \"443\" && url.protocol === \"https:\") {\n\t\tif (!urls.has(url.toString())) {\n\t\t\turls.add(url.toString());\n\t\t\tconsole.warn(\n\t\t\t\t`WARNING: known issue with \\`fetch()\\` requests to custom HTTPS ports in published Workers:\\n` +\n\t\t\t\t\t` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \\`wrangler deploy\\` command.\\n`\n\t\t\t);\n\t\t}\n\t}\n}\n\nglobalThis.fetch = new Proxy(globalThis.fetch, {\n\tapply(target, thisArg, argArray) {\n\t\tconst [request, init] = argArray;\n\t\tcheckURL(request, init);\n\t\treturn Reflect.apply(target, thisArg, argArray);\n\t},\n});\n", "/**\n * Clicky Proxy Worker\n *\n * Proxies requests to Claude and ElevenLabs APIs so the app never\n * ships with raw API keys. Keys are stored as Cloudflare secrets.\n *\n * Routes:\n * POST /chat \u2192 Anthropic Messages API (streaming)\n * POST /tts \u2192 ElevenLabs TTS API\n */\n\ninterface Env {\n LM_STUDIO_API_KEY: string;\n ELEVENLABS_API_KEY: string;\n ELEVENLABS_VOICE_ID: string;\n ASSEMBLYAI_API_KEY: string;\n}\n\nexport default {\n async fetch(request: Request, env: Env): Promise {\n const url = new URL(request.url);\n\n if (request.method !== \"POST\") {\n return new Response(\"Method not allowed\", { status: 405 });\n }\n\n try {\n if (url.pathname === \"/chat\") {\n return await handleChat(request, env);\n }\n\n if (url.pathname === \"/tts\") {\n return await handleTTS(request, env);\n }\n\n if (url.pathname === \"/transcribe-token\") {\n return await handleTranscribeToken(env);\n }\n } catch (error) {\n console.error(`[${url.pathname}] Unhandled error:`, error);\n return new Response(\n JSON.stringify({ error: String(error) }),\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n }\n\n return new Response(\"Not found\", { status: 404 });\n },\n};\n\nasync function handleChat(request: Request, env: Env): Promise {\n const body = await request.text();\n\n const response = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"x-api-key\": env.LM_STUDIO_API_KEY,\n \"anthropic-version\": \"2023-06-01\",\n \"content-type\": \"application/json\",\n },\n body,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n },\n });\n}\n\nasync function handleTranscribeToken(env: Env): Promise {\n const response = await fetch(\n \"https://streaming.assemblyai.com/v3/token?expires_in_seconds=480\",\n {\n method: \"GET\",\n headers: {\n authorization: env.ASSEMBLYAI_API_KEY,\n },\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n const data = await response.text();\n return new Response(data, {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nasync function handleTTS(request: Request, env: Env): Promise {\n const body = await request.text();\n const voiceId = env.ELEVENLABS_VOICE_ID;\n\n const response = await fetch(\n `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,\n {\n method: \"POST\",\n headers: {\n \"xi-api-key\": env.ELEVENLABS_API_KEY,\n \"content-type\": \"application/json\",\n accept: \"audio/mpeg\",\n },\n body,\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"audio/mpeg\",\n },\n });\n}\n", "import type { Middleware } from \"./common\";\n\nconst drainBody: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} finally {\n\t\ttry {\n\t\t\tif (request.body !== null && !request.bodyUsed) {\n\t\t\t\tconst reader = request.body.getReader();\n\t\t\t\twhile (!(await reader.read()).done) {}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(\"Failed to drain the unused request body.\", e);\n\t\t}\n\t}\n};\n\nexport default drainBody;\n", "import type { Middleware } from \"./common\";\n\ninterface JsonError {\n\tmessage?: string;\n\tname?: string;\n\tstack?: string;\n\tcause?: JsonError;\n}\n\nfunction reduceError(e: any): JsonError {\n\treturn {\n\t\tname: e?.name,\n\t\tmessage: e?.message ?? String(e),\n\t\tstack: e?.stack,\n\t\tcause: e?.cause === undefined ? undefined : reduceError(e.cause),\n\t};\n}\n\n// See comment in `bundle.ts` for details on why this is needed\nconst jsonError: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} catch (e: any) {\n\t\tconst error = reduceError(e);\n\t\treturn Response.json(error, {\n\t\t\tstatus: 500,\n\t\t\theaders: { \"MF-Experimental-Error-Stack\": \"true\" },\n\t\t});\n\t}\n};\n\nexport default jsonError;\n", "\t\t\t\timport worker, * as OTHER_EXPORTS from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\timport * as __MIDDLEWARE_0__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts\";\nimport * as __MIDDLEWARE_1__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts\";\n\n\t\t\t\texport * from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\tconst MIDDLEWARE_TEST_INJECT = \"__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__\";\n\t\t\t\texport const __INTERNAL_WRANGLER_MIDDLEWARE__ = [\n\t\t\t\t\t\n\t\t\t\t\t__MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default\n\t\t\t\t]\n\t\t\t\texport default worker;", "export type Awaitable = T | Promise;\n// TODO: allow dispatching more events?\nexport type Dispatcher = (\n\ttype: \"scheduled\",\n\tinit: { cron?: string }\n) => Awaitable;\n\nexport type IncomingRequest = Request<\n\tunknown,\n\tIncomingRequestCfProperties\n>;\n\nexport interface MiddlewareContext {\n\tdispatch: Dispatcher;\n\tnext(request: IncomingRequest, env: any): Awaitable;\n}\n\nexport type Middleware = (\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tmiddlewareCtx: MiddlewareContext\n) => Awaitable;\n\nconst __facade_middleware__: Middleware[] = [];\n\n// The register functions allow for the insertion of one or many middleware,\n// We register internal middleware first in the stack, but have no way of controlling\n// the order that addMiddleware is run in service workers so need an internal function.\nexport function __facade_register__(...args: (Middleware | Middleware[])[]) {\n\t__facade_middleware__.push(...args.flat());\n}\nexport function __facade_registerInternal__(\n\t...args: (Middleware | Middleware[])[]\n) {\n\t__facade_middleware__.unshift(...args.flat());\n}\n\nfunction __facade_invokeChain__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tmiddlewareChain: Middleware[]\n): Awaitable {\n\tconst [head, ...tail] = middlewareChain;\n\tconst middlewareCtx: MiddlewareContext = {\n\t\tdispatch,\n\t\tnext(newRequest, newEnv) {\n\t\t\treturn __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);\n\t\t},\n\t};\n\treturn head(request, env, ctx, middlewareCtx);\n}\n\nexport function __facade_invoke__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tfinalMiddleware: Middleware\n): Awaitable {\n\treturn __facade_invokeChain__(request, env, ctx, dispatch, [\n\t\t...__facade_middleware__,\n\t\tfinalMiddleware,\n\t]);\n}\n", "// This loads all middlewares exposed on the middleware object and then starts\n// the invocation chain. The big idea is that we can add these to the middleware\n// export dynamically through wrangler, or we can potentially let users directly\n// add them as a sort of \"plugin\" system.\n\nimport ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js\";\nimport { __facade_invoke__, __facade_register__, Dispatcher } from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts\";\nimport type { WorkerEntrypointConstructor } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js\";\n\n// Preserve all the exports from the worker\nexport * from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js\";\n\nclass __Facade_ScheduledController__ implements ScheduledController {\n\treadonly #noRetry: ScheduledController[\"noRetry\"];\n\n\tconstructor(\n\t\treadonly scheduledTime: number,\n\t\treadonly cron: string,\n\t\tnoRetry: ScheduledController[\"noRetry\"]\n\t) {\n\t\tthis.#noRetry = noRetry;\n\t}\n\n\tnoRetry() {\n\t\tif (!(this instanceof __Facade_ScheduledController__)) {\n\t\t\tthrow new TypeError(\"Illegal invocation\");\n\t\t}\n\t\t// Need to call native method immediately in case uncaught error thrown\n\t\tthis.#noRetry();\n\t}\n}\n\nfunction wrapExportedHandler(worker: ExportedHandler): ExportedHandler {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn worker;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\tconst fetchDispatcher: ExportedHandlerFetchHandler = function (\n\t\trequest,\n\t\tenv,\n\t\tctx\n\t) {\n\t\tif (worker.fetch === undefined) {\n\t\t\tthrow new Error(\"Handler does not export a fetch() function.\");\n\t\t}\n\t\treturn worker.fetch(request, env, ctx);\n\t};\n\n\treturn {\n\t\t...worker,\n\t\tfetch(request, env, ctx) {\n\t\t\tconst dispatcher: Dispatcher = function (type, init) {\n\t\t\t\tif (type === \"scheduled\" && worker.scheduled !== undefined) {\n\t\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\t\tDate.now(),\n\t\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t\t() => {}\n\t\t\t\t\t);\n\t\t\t\t\treturn worker.scheduled(controller, env, ctx);\n\t\t\t\t}\n\t\t\t};\n\t\t\treturn __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);\n\t\t},\n\t};\n}\n\nfunction wrapWorkerEntrypoint(\n\tklass: WorkerEntrypointConstructor\n): WorkerEntrypointConstructor {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn klass;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\t// `extend`ing `klass` here so other RPC methods remain callable\n\treturn class extends klass {\n\t\t#fetchDispatcher: ExportedHandlerFetchHandler> = (\n\t\t\trequest,\n\t\t\tenv,\n\t\t\tctx\n\t\t) => {\n\t\t\tthis.env = env;\n\t\t\tthis.ctx = ctx;\n\t\t\tif (super.fetch === undefined) {\n\t\t\t\tthrow new Error(\"Entrypoint class does not define a fetch() function.\");\n\t\t\t}\n\t\t\treturn super.fetch(request);\n\t\t};\n\n\t\t#dispatcher: Dispatcher = (type, init) => {\n\t\t\tif (type === \"scheduled\" && super.scheduled !== undefined) {\n\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\tDate.now(),\n\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t() => {}\n\t\t\t\t);\n\t\t\t\treturn super.scheduled(controller);\n\t\t\t}\n\t\t};\n\n\t\tfetch(request: Request) {\n\t\t\treturn __facade_invoke__(\n\t\t\t\trequest,\n\t\t\t\tthis.env,\n\t\t\t\tthis.ctx,\n\t\t\t\tthis.#dispatcher,\n\t\t\t\tthis.#fetchDispatcher\n\t\t\t);\n\t\t}\n\t};\n}\n\nlet WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;\nif (typeof ENTRY === \"object\") {\n\tWRAPPED_ENTRY = wrapExportedHandler(ENTRY);\n} else if (typeof ENTRY === \"function\") {\n\tWRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);\n}\nexport default WRAPPED_ENTRY;\n"], "mappings": "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", "names": [] } From 63217d043b78497616eadcd5c5af1a6176755cf7 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 18:06:22 +0200 Subject: [PATCH 07/20] feat: support selecting both LM Studio and Claude API models from the clicky panel --- leanring-buddy/CompanionManager.swift | 71 +++- leanring-buddy/CompanionPanelView.swift | 212 ++++-------- .../tmp/bundle-P89daS/checked-fetch.js | 28 -- .../middleware-insertion-facade.js | 11 - .../bundle-P89daS/middleware-loader.entry.ts | 134 -------- worker/.wrangler/tmp/dev-KZUcMs/index.js | 315 ------------------ worker/.wrangler/tmp/dev-KZUcMs/index.js.map | 8 - worker/src/index.ts | 4 +- 8 files changed, 124 insertions(+), 659 deletions(-) delete mode 100644 worker/.wrangler/tmp/bundle-P89daS/checked-fetch.js delete mode 100644 worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js delete mode 100644 worker/.wrangler/tmp/bundle-P89daS/middleware-loader.entry.ts delete mode 100644 worker/.wrangler/tmp/dev-KZUcMs/index.js delete mode 100644 worker/.wrangler/tmp/dev-KZUcMs/index.js.map diff --git a/leanring-buddy/CompanionManager.swift b/leanring-buddy/CompanionManager.swift index 636ca84b..258d2b2f 100644 --- a/leanring-buddy/CompanionManager.swift +++ b/leanring-buddy/CompanionManager.swift @@ -72,6 +72,10 @@ final class CompanionManager: ObservableObject { /// through this so keys never ship in the app binary. private static let workerBaseURL = "http://localhost:8787" + private lazy var claudeAPI: ClaudeAPI = { + return ClaudeAPI(proxyURL: "\(Self.workerBaseURL)/chat", model: selectedModel) + }() + private lazy var openAIAPI: OpenAIAPI = { return OpenAIAPI(model: selectedModel) }() @@ -142,7 +146,12 @@ final class CompanionManager: ObservableObject { func setSelectedModel(_ model: String) { selectedModel = model UserDefaults.standard.set(model, forKey: "selectedGemmaModel") - openAIAPI.model = model + + if model.lowercased().contains("claude") { + claudeAPI.model = model + } else { + openAIAPI.model = model + } } /// User preference for whether the Clicky cursor should be shown. @@ -209,8 +218,9 @@ final class CompanionManager: ObservableObject { bindVoiceStateObservation() bindAudioPowerLevel() bindShortcutTransitions() - // Eagerly touch the Claude API so its TLS warmup handshake completes + // Eagerly touch APIs so their TLS warmup handshakes complete // well before the onboarding demo fires at ~40s into the video. + _ = claudeAPI _ = openAIAPI // If the user already completed onboarding AND all permissions are @@ -640,15 +650,30 @@ final class CompanionManager: ObservableObject { (userPlaceholder: entry.userTranscript, assistantResponse: entry.assistantResponse) } - let (fullResponseText, _) = try await openAIAPI.analyzeImageStreaming( - images: labeledImages, - systemPrompt: Self.companionVoiceResponseSystemPrompt, - conversationHistory: historyForAPI, - userPrompt: transcript, - onTextChunk: { _ in - // No streaming text display — spinner stays until TTS plays - } - ) + let fullResponseText: String + let duration: TimeInterval + + if selectedModel.lowercased().contains("claude") { + (fullResponseText, duration) = try await claudeAPI.analyzeImageStreaming( + images: labeledImages, + systemPrompt: Self.companionVoiceResponseSystemPrompt, + conversationHistory: historyForAPI, + userPrompt: transcript, + onTextChunk: { _ in + // No streaming text display — spinner stays until TTS plays + } + ) + } else { + (fullResponseText, duration) = try await openAIAPI.analyzeImageStreaming( + images: labeledImages, + systemPrompt: Self.companionVoiceResponseSystemPrompt, + conversationHistory: historyForAPI, + userPrompt: transcript, + onTextChunk: { _ in + // No streaming text display — spinner stays until TTS plays + } + ) + } guard !Task.isCancelled else { return } @@ -1012,12 +1037,24 @@ final class CompanionManager: ObservableObject { let dimensionInfo = " (image dimensions: \(cursorScreenCapture.screenshotWidthInPixels)x\(cursorScreenCapture.screenshotHeightInPixels) pixels)" let labeledImages = [(data: cursorScreenCapture.imageData, label: cursorScreenCapture.label + dimensionInfo)] - let (fullResponseText, _) = try await openAIAPI.analyzeImageStreaming( - images: labeledImages, - systemPrompt: Self.onboardingDemoSystemPrompt, - userPrompt: "look around my screen and find something interesting to point at", - onTextChunk: { _ in } - ) + let fullResponseText: String + let duration: TimeInterval + + if selectedModel.lowercased().contains("claude") { + (fullResponseText, duration) = try await claudeAPI.analyzeImageStreaming( + images: labeledImages, + systemPrompt: Self.onboardingDemoSystemPrompt, + userPrompt: "look around my screen and find something interesting to point at", + onTextChunk: { _ in } + ) + } else { + (fullResponseText, duration) = try await openAIAPI.analyzeImageStreaming( + images: labeledImages, + systemPrompt: Self.onboardingDemoSystemPrompt, + userPrompt: "look around my screen and find something interesting to point at", + onTextChunk: { _ in } + ) + } let parseResult = Self.parsePointingCoordinates(from: fullResponseText) diff --git a/leanring-buddy/CompanionPanelView.swift b/leanring-buddy/CompanionPanelView.swift index 4d779dcd..3026b4bc 100644 --- a/leanring-buddy/CompanionPanelView.swift +++ b/leanring-buddy/CompanionPanelView.swift @@ -606,156 +606,80 @@ struct CompanionPanelView: View { Spacer() - HStack(spacing: 0) { - modelOptionButton(label: "Gemma 4", modelID: "gemma-4-e4b-uncensored-hauhaucs-aggressive") - modelOptionButton(label: "Gemma 4 IT", modelID: "gemma-4-e4b-uncensored-hauhaucs-aggressive-it") - } - .background( - RoundedRectangle(cornerRadius: 6, style: .continuous) - .fill(Color.white.opacity(0.06)) - ) - .overlay( - RoundedRectangle(cornerRadius: 6, style: .continuous) - .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) - ) - } - .padding(.vertical, 4) - } + Menu { + // Section for Local LM Studio Models + Section(header: Text("LM Studio (Local)")) { + if companionManager.availableModels.isEmpty { + Text("No models found").disabled(true) + } else { + ForEach(companionManager.availableModels, id: \.self) { modelID in + Button(action: { + companionManager.setSelectedModel(modelID) + }) { + HStack { + Text(modelID) + if companionManager.selectedModel == modelID { + Image(systemName: "checkmark") + } + } + } + } + } + Button("Refresh LM Studio Models") { + companionManager.fetchAvailableModels() + } + } + + Divider() + + // Section for Cloud / Claude Models + Section(header: Text("Anthropic (Cloud)")) { + let claudeModels = [ + ("Claude 3.5 Sonnet", "claude-3-5-sonnet-20241022"), + ("Claude 3 Opus", "claude-3-opus-20240229"), + ("Claude 3 Haiku", "claude-3-haiku-20240307") + ] + + ForEach(claudeModels, id: \.1) { label, modelID in + Button(action: { + companionManager.setSelectedModel(modelID) + }) { + HStack { + Text(label) + if companionManager.selectedModel == modelID { + Image(systemName: "checkmark") + } + } + } + } + } - private func modelOptionButton(label: String, modelID: String) -> some View { - let isSelected = companionManager.selectedModel == modelID - return Button(action: { - companionManager.setSelectedModel(modelID) - }) { - Text(label) - .font(.system(size: 11, weight: .medium)) - .foregroundColor(isSelected ? DS.Colors.textPrimary : DS.Colors.textTertiary) + } label: { + HStack(spacing: 4) { + Text(companionManager.selectedModel) + .font(.system(size: 11, weight: .medium)) + .foregroundColor(DS.Colors.textPrimary) + .lineLimit(1) + .truncationMode(.middle) + + Image(systemName: "chevron.up.chevron.down") + .font(.system(size: 9)) + .foregroundColor(DS.Colors.textTertiary) + } .padding(.horizontal, 10) .padding(.vertical, 5) .background( - RoundedRectangle(cornerRadius: 5, style: .continuous) - .fill(isSelected ? Color.white.opacity(0.1) : Color.clear) + RoundedRectangle(cornerRadius: 6, style: .continuous) + .fill(Color.white.opacity(0.06)) + ) + .overlay( + RoundedRectangle(cornerRadius: 6, style: .continuous) + .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) ) - } - .buttonStyle(.plain) - .pointerCursor() - } - - // MARK: - DM Farza Button - - private var dmFarzaButton: some View { - Button(action: { - if let url = URL(string: "https://x.com/farzatv") { - NSWorkspace.shared.open(url) - } - }) { - HStack(spacing: 8) { - Image(systemName: "bubble.left.fill") - .font(.system(size: 12, weight: .medium)) - - VStack(alignment: .leading, spacing: 2) { - Text("Got feedback? DM me") - .font(.system(size: 12, weight: .semibold)) - Text("Bugs, ideas, anything — I read every message.") - .font(.system(size: 10)) - .foregroundColor(DS.Colors.textTertiary) - } - } - .foregroundColor(DS.Colors.textSecondary) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 12) - .padding(.vertical, 10) - .background( - RoundedRectangle(cornerRadius: DS.CornerRadius.medium, style: .continuous) - .fill(Color.white.opacity(0.06)) - ) - .overlay( - RoundedRectangle(cornerRadius: DS.CornerRadius.medium, style: .continuous) - .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) - ) - } - .buttonStyle(.plain) - .pointerCursor() - } - - // MARK: - Footer - - private var footerSection: some View { - HStack { - Button(action: { - NSApp.terminate(nil) - }) { - HStack(spacing: 6) { - Image(systemName: "power") - .font(.system(size: 11, weight: .medium)) - Text("Quit Clicky") - .font(.system(size: 12, weight: .medium)) - } - .foregroundColor(DS.Colors.textTertiary) - } - .buttonStyle(.plain) - .pointerCursor() - - if companionManager.hasCompletedOnboarding { - Spacer() - - Button(action: { - companionManager.replayOnboarding() - }) { - HStack(spacing: 6) { - Image(systemName: "play.circle") - .font(.system(size: 11, weight: .medium)) - Text("Watch Onboarding Again") - .font(.system(size: 12, weight: .medium)) - } - .foregroundColor(DS.Colors.textTertiary) - } - .buttonStyle(.plain) - .pointerCursor() } + .menuStyle(.borderlessButton) + .frame(maxWidth: 200) } + .padding(.vertical, 4) } - // MARK: - Visual Helpers - - private var panelBackground: some View { - RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(DS.Colors.background) - .shadow(color: Color.black.opacity(0.5), radius: 20, x: 0, y: 10) - .shadow(color: Color.black.opacity(0.3), radius: 4, x: 0, y: 2) - } - - private var statusDotColor: Color { - if !companionManager.isOverlayVisible { - return DS.Colors.textTertiary - } - switch companionManager.voiceState { - case .idle: - return DS.Colors.success - case .listening: - return DS.Colors.blue400 - case .processing, .responding: - return DS.Colors.blue400 - } - } - - private var statusText: String { - if !companionManager.hasCompletedOnboarding || !companionManager.allPermissionsGranted { - return "Setup" - } - if !companionManager.isOverlayVisible { - return "Ready" - } - switch companionManager.voiceState { - case .idle: - return "Active" - case .listening: - return "Listening" - case .processing: - return "Processing" - case .responding: - return "Responding" - } - } - -} diff --git a/worker/.wrangler/tmp/bundle-P89daS/checked-fetch.js b/worker/.wrangler/tmp/bundle-P89daS/checked-fetch.js deleted file mode 100644 index e5e026d3..00000000 --- a/worker/.wrangler/tmp/bundle-P89daS/checked-fetch.js +++ /dev/null @@ -1,28 +0,0 @@ -const urls = new Set(); - -function checkURL(request, init) { - const url = - request instanceof URL - ? request - : new URL( - (typeof request === "string" ? new Request(request, init) : request) - .url - ); - if (url.port && url.port !== "443" && url.protocol === "https:") { - if (!urls.has(url.toString())) { - urls.add(url.toString()); - console.warn( - `WARNING: known issue with \`fetch()\` requests to custom HTTPS ports in published Workers:\n` + - ` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \`wrangler deploy\` command.\n` - ); - } - } -} - -globalThis.fetch = new Proxy(globalThis.fetch, { - apply(target, thisArg, argArray) { - const [request, init] = argArray; - checkURL(request, init); - return Reflect.apply(target, thisArg, argArray); - }, -}); diff --git a/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js b/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js deleted file mode 100644 index 88b249ed..00000000 --- a/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js +++ /dev/null @@ -1,11 +0,0 @@ - import worker, * as OTHER_EXPORTS from "/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts"; - import * as __MIDDLEWARE_0__ from "/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts"; -import * as __MIDDLEWARE_1__ from "/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts"; - - export * from "/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts"; - const MIDDLEWARE_TEST_INJECT = "__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__"; - export const __INTERNAL_WRANGLER_MIDDLEWARE__ = [ - - __MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default - ] - export default worker; \ No newline at end of file diff --git a/worker/.wrangler/tmp/bundle-P89daS/middleware-loader.entry.ts b/worker/.wrangler/tmp/bundle-P89daS/middleware-loader.entry.ts deleted file mode 100644 index 5d91b54f..00000000 --- a/worker/.wrangler/tmp/bundle-P89daS/middleware-loader.entry.ts +++ /dev/null @@ -1,134 +0,0 @@ -// This loads all middlewares exposed on the middleware object and then starts -// the invocation chain. The big idea is that we can add these to the middleware -// export dynamically through wrangler, or we can potentially let users directly -// add them as a sort of "plugin" system. - -import ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js"; -import { __facade_invoke__, __facade_register__, Dispatcher } from "/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts"; -import type { WorkerEntrypointConstructor } from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js"; - -// Preserve all the exports from the worker -export * from "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js"; - -class __Facade_ScheduledController__ implements ScheduledController { - readonly #noRetry: ScheduledController["noRetry"]; - - constructor( - readonly scheduledTime: number, - readonly cron: string, - noRetry: ScheduledController["noRetry"] - ) { - this.#noRetry = noRetry; - } - - noRetry() { - if (!(this instanceof __Facade_ScheduledController__)) { - throw new TypeError("Illegal invocation"); - } - // Need to call native method immediately in case uncaught error thrown - this.#noRetry(); - } -} - -function wrapExportedHandler(worker: ExportedHandler): ExportedHandler { - // If we don't have any middleware defined, just return the handler as is - if ( - __INTERNAL_WRANGLER_MIDDLEWARE__ === undefined || - __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0 - ) { - return worker; - } - // Otherwise, register all middleware once - for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { - __facade_register__(middleware); - } - - const fetchDispatcher: ExportedHandlerFetchHandler = function ( - request, - env, - ctx - ) { - if (worker.fetch === undefined) { - throw new Error("Handler does not export a fetch() function."); - } - return worker.fetch(request, env, ctx); - }; - - return { - ...worker, - fetch(request, env, ctx) { - const dispatcher: Dispatcher = function (type, init) { - if (type === "scheduled" && worker.scheduled !== undefined) { - const controller = new __Facade_ScheduledController__( - Date.now(), - init.cron ?? "", - () => {} - ); - return worker.scheduled(controller, env, ctx); - } - }; - return __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher); - }, - }; -} - -function wrapWorkerEntrypoint( - klass: WorkerEntrypointConstructor -): WorkerEntrypointConstructor { - // If we don't have any middleware defined, just return the handler as is - if ( - __INTERNAL_WRANGLER_MIDDLEWARE__ === undefined || - __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0 - ) { - return klass; - } - // Otherwise, register all middleware once - for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { - __facade_register__(middleware); - } - - // `extend`ing `klass` here so other RPC methods remain callable - return class extends klass { - #fetchDispatcher: ExportedHandlerFetchHandler> = ( - request, - env, - ctx - ) => { - this.env = env; - this.ctx = ctx; - if (super.fetch === undefined) { - throw new Error("Entrypoint class does not define a fetch() function."); - } - return super.fetch(request); - }; - - #dispatcher: Dispatcher = (type, init) => { - if (type === "scheduled" && super.scheduled !== undefined) { - const controller = new __Facade_ScheduledController__( - Date.now(), - init.cron ?? "", - () => {} - ); - return super.scheduled(controller); - } - }; - - fetch(request: Request) { - return __facade_invoke__( - request, - this.env, - this.ctx, - this.#dispatcher, - this.#fetchDispatcher - ); - } - }; -} - -let WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined; -if (typeof ENTRY === "object") { - WRAPPED_ENTRY = wrapExportedHandler(ENTRY); -} else if (typeof ENTRY === "function") { - WRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY); -} -export default WRAPPED_ENTRY; diff --git a/worker/.wrangler/tmp/dev-KZUcMs/index.js b/worker/.wrangler/tmp/dev-KZUcMs/index.js deleted file mode 100644 index cf6df4a2..00000000 --- a/worker/.wrangler/tmp/dev-KZUcMs/index.js +++ /dev/null @@ -1,315 +0,0 @@ -var __defProp = Object.defineProperty; -var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); - -// .wrangler/tmp/bundle-P89daS/checked-fetch.js -var urls = /* @__PURE__ */ new Set(); -function checkURL(request, init) { - const url = request instanceof URL ? request : new URL( - (typeof request === "string" ? new Request(request, init) : request).url - ); - if (url.port && url.port !== "443" && url.protocol === "https:") { - if (!urls.has(url.toString())) { - urls.add(url.toString()); - console.warn( - `WARNING: known issue with \`fetch()\` requests to custom HTTPS ports in published Workers: - - ${url.toString()} - the custom port will be ignored when the Worker is published using the \`wrangler deploy\` command. -` - ); - } - } -} -__name(checkURL, "checkURL"); -globalThis.fetch = new Proxy(globalThis.fetch, { - apply(target, thisArg, argArray) { - const [request, init] = argArray; - checkURL(request, init); - return Reflect.apply(target, thisArg, argArray); - } -}); - -// src/index.ts -var src_default = { - async fetch(request, env) { - const url = new URL(request.url); - if (request.method !== "POST") { - return new Response("Method not allowed", { status: 405 }); - } - try { - if (url.pathname === "/chat") { - return await handleChat(request, env); - } - if (url.pathname === "/tts") { - return await handleTTS(request, env); - } - if (url.pathname === "/transcribe-token") { - return await handleTranscribeToken(env); - } - } catch (error) { - console.error(`[${url.pathname}] Unhandled error:`, error); - return new Response( - JSON.stringify({ error: String(error) }), - { status: 500, headers: { "content-type": "application/json" } } - ); - } - return new Response("Not found", { status: 404 }); - } -}; -async function handleChat(request, env) { - const body = await request.text(); - const response = await fetch("https://api.anthropic.com/v1/messages", { - method: "POST", - headers: { - "x-api-key": env.LM_STUDIO_API_KEY, - "anthropic-version": "2023-06-01", - "content-type": "application/json" - }, - body - }); - if (!response.ok) { - const errorBody = await response.text(); - console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`); - return new Response(errorBody, { - status: response.status, - headers: { "content-type": "application/json" } - }); - } - return new Response(response.body, { - status: response.status, - headers: { - "content-type": response.headers.get("content-type") || "text/event-stream", - "cache-control": "no-cache" - } - }); -} -__name(handleChat, "handleChat"); -async function handleTranscribeToken(env) { - const response = await fetch( - "https://streaming.assemblyai.com/v3/token?expires_in_seconds=480", - { - method: "GET", - headers: { - authorization: env.ASSEMBLYAI_API_KEY - } - } - ); - if (!response.ok) { - const errorBody = await response.text(); - console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`); - return new Response(errorBody, { - status: response.status, - headers: { "content-type": "application/json" } - }); - } - const data = await response.text(); - return new Response(data, { - status: 200, - headers: { "content-type": "application/json" } - }); -} -__name(handleTranscribeToken, "handleTranscribeToken"); -async function handleTTS(request, env) { - const body = await request.text(); - const voiceId = env.ELEVENLABS_VOICE_ID; - const response = await fetch( - `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, - { - method: "POST", - headers: { - "xi-api-key": env.ELEVENLABS_API_KEY, - "content-type": "application/json", - accept: "audio/mpeg" - }, - body - } - ); - if (!response.ok) { - const errorBody = await response.text(); - console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`); - return new Response(errorBody, { - status: response.status, - headers: { "content-type": "application/json" } - }); - } - return new Response(response.body, { - status: response.status, - headers: { - "content-type": response.headers.get("content-type") || "audio/mpeg" - } - }); -} -__name(handleTTS, "handleTTS"); - -// node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts -var drainBody = /* @__PURE__ */ __name(async (request, env, _ctx, middlewareCtx) => { - try { - return await middlewareCtx.next(request, env); - } finally { - try { - if (request.body !== null && !request.bodyUsed) { - const reader = request.body.getReader(); - while (!(await reader.read()).done) { - } - } - } catch (e) { - console.error("Failed to drain the unused request body.", e); - } - } -}, "drainBody"); -var middleware_ensure_req_body_drained_default = drainBody; - -// node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts -function reduceError(e) { - return { - name: e?.name, - message: e?.message ?? String(e), - stack: e?.stack, - cause: e?.cause === void 0 ? void 0 : reduceError(e.cause) - }; -} -__name(reduceError, "reduceError"); -var jsonError = /* @__PURE__ */ __name(async (request, env, _ctx, middlewareCtx) => { - try { - return await middlewareCtx.next(request, env); - } catch (e) { - const error = reduceError(e); - return Response.json(error, { - status: 500, - headers: { "MF-Experimental-Error-Stack": "true" } - }); - } -}, "jsonError"); -var middleware_miniflare3_json_error_default = jsonError; - -// .wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js -var __INTERNAL_WRANGLER_MIDDLEWARE__ = [ - middleware_ensure_req_body_drained_default, - middleware_miniflare3_json_error_default -]; -var middleware_insertion_facade_default = src_default; - -// node_modules/wrangler/templates/middleware/common.ts -var __facade_middleware__ = []; -function __facade_register__(...args) { - __facade_middleware__.push(...args.flat()); -} -__name(__facade_register__, "__facade_register__"); -function __facade_invokeChain__(request, env, ctx, dispatch, middlewareChain) { - const [head, ...tail] = middlewareChain; - const middlewareCtx = { - dispatch, - next(newRequest, newEnv) { - return __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail); - } - }; - return head(request, env, ctx, middlewareCtx); -} -__name(__facade_invokeChain__, "__facade_invokeChain__"); -function __facade_invoke__(request, env, ctx, dispatch, finalMiddleware) { - return __facade_invokeChain__(request, env, ctx, dispatch, [ - ...__facade_middleware__, - finalMiddleware - ]); -} -__name(__facade_invoke__, "__facade_invoke__"); - -// .wrangler/tmp/bundle-P89daS/middleware-loader.entry.ts -var __Facade_ScheduledController__ = class ___Facade_ScheduledController__ { - constructor(scheduledTime, cron, noRetry) { - this.scheduledTime = scheduledTime; - this.cron = cron; - this.#noRetry = noRetry; - } - static { - __name(this, "__Facade_ScheduledController__"); - } - #noRetry; - noRetry() { - if (!(this instanceof ___Facade_ScheduledController__)) { - throw new TypeError("Illegal invocation"); - } - this.#noRetry(); - } -}; -function wrapExportedHandler(worker) { - if (__INTERNAL_WRANGLER_MIDDLEWARE__ === void 0 || __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0) { - return worker; - } - for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { - __facade_register__(middleware); - } - const fetchDispatcher = /* @__PURE__ */ __name(function(request, env, ctx) { - if (worker.fetch === void 0) { - throw new Error("Handler does not export a fetch() function."); - } - return worker.fetch(request, env, ctx); - }, "fetchDispatcher"); - return { - ...worker, - fetch(request, env, ctx) { - const dispatcher = /* @__PURE__ */ __name(function(type, init) { - if (type === "scheduled" && worker.scheduled !== void 0) { - const controller = new __Facade_ScheduledController__( - Date.now(), - init.cron ?? "", - () => { - } - ); - return worker.scheduled(controller, env, ctx); - } - }, "dispatcher"); - return __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher); - } - }; -} -__name(wrapExportedHandler, "wrapExportedHandler"); -function wrapWorkerEntrypoint(klass) { - if (__INTERNAL_WRANGLER_MIDDLEWARE__ === void 0 || __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0) { - return klass; - } - for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { - __facade_register__(middleware); - } - return class extends klass { - #fetchDispatcher = /* @__PURE__ */ __name((request, env, ctx) => { - this.env = env; - this.ctx = ctx; - if (super.fetch === void 0) { - throw new Error("Entrypoint class does not define a fetch() function."); - } - return super.fetch(request); - }, "#fetchDispatcher"); - #dispatcher = /* @__PURE__ */ __name((type, init) => { - if (type === "scheduled" && super.scheduled !== void 0) { - const controller = new __Facade_ScheduledController__( - Date.now(), - init.cron ?? "", - () => { - } - ); - return super.scheduled(controller); - } - }, "#dispatcher"); - fetch(request) { - return __facade_invoke__( - request, - this.env, - this.ctx, - this.#dispatcher, - this.#fetchDispatcher - ); - } - }; -} -__name(wrapWorkerEntrypoint, "wrapWorkerEntrypoint"); -var WRAPPED_ENTRY; -if (typeof middleware_insertion_facade_default === "object") { - WRAPPED_ENTRY = wrapExportedHandler(middleware_insertion_facade_default); -} else if (typeof middleware_insertion_facade_default === "function") { - WRAPPED_ENTRY = wrapWorkerEntrypoint(middleware_insertion_facade_default); -} -var middleware_loader_entry_default = WRAPPED_ENTRY; -export { - __INTERNAL_WRANGLER_MIDDLEWARE__, - middleware_loader_entry_default as default -}; -//# sourceMappingURL=index.js.map diff --git a/worker/.wrangler/tmp/dev-KZUcMs/index.js.map b/worker/.wrangler/tmp/dev-KZUcMs/index.js.map deleted file mode 100644 index 1773d919..00000000 --- a/worker/.wrangler/tmp/dev-KZUcMs/index.js.map +++ /dev/null @@ -1,8 +0,0 @@ -{ - "version": 3, - "sources": ["../bundle-P89daS/checked-fetch.js", "../../../src/index.ts", "../../../node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts", "../../../node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts", "../bundle-P89daS/middleware-insertion-facade.js", "../../../node_modules/wrangler/templates/middleware/common.ts", "../bundle-P89daS/middleware-loader.entry.ts"], - "sourceRoot": "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/dev-KZUcMs", - "sourcesContent": ["const urls = new Set();\n\nfunction checkURL(request, init) {\n\tconst url =\n\t\trequest instanceof URL\n\t\t\t? request\n\t\t\t: new URL(\n\t\t\t\t\t(typeof request === \"string\" ? new Request(request, init) : request)\n\t\t\t\t\t\t.url\n\t\t\t\t);\n\tif (url.port && url.port !== \"443\" && url.protocol === \"https:\") {\n\t\tif (!urls.has(url.toString())) {\n\t\t\turls.add(url.toString());\n\t\t\tconsole.warn(\n\t\t\t\t`WARNING: known issue with \\`fetch()\\` requests to custom HTTPS ports in published Workers:\\n` +\n\t\t\t\t\t` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \\`wrangler deploy\\` command.\\n`\n\t\t\t);\n\t\t}\n\t}\n}\n\nglobalThis.fetch = new Proxy(globalThis.fetch, {\n\tapply(target, thisArg, argArray) {\n\t\tconst [request, init] = argArray;\n\t\tcheckURL(request, init);\n\t\treturn Reflect.apply(target, thisArg, argArray);\n\t},\n});\n", "/**\n * Clicky Proxy Worker\n *\n * Proxies requests to Claude and ElevenLabs APIs so the app never\n * ships with raw API keys. Keys are stored as Cloudflare secrets.\n *\n * Routes:\n * POST /chat \u2192 Anthropic Messages API (streaming)\n * POST /tts \u2192 ElevenLabs TTS API\n */\n\ninterface Env {\n LM_STUDIO_API_KEY: string;\n ELEVENLABS_API_KEY: string;\n ELEVENLABS_VOICE_ID: string;\n ASSEMBLYAI_API_KEY: string;\n}\n\nexport default {\n async fetch(request: Request, env: Env): Promise {\n const url = new URL(request.url);\n\n if (request.method !== \"POST\") {\n return new Response(\"Method not allowed\", { status: 405 });\n }\n\n try {\n if (url.pathname === \"/chat\") {\n return await handleChat(request, env);\n }\n\n if (url.pathname === \"/tts\") {\n return await handleTTS(request, env);\n }\n\n if (url.pathname === \"/transcribe-token\") {\n return await handleTranscribeToken(env);\n }\n } catch (error) {\n console.error(`[${url.pathname}] Unhandled error:`, error);\n return new Response(\n JSON.stringify({ error: String(error) }),\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n }\n\n return new Response(\"Not found\", { status: 404 });\n },\n};\n\nasync function handleChat(request: Request, env: Env): Promise {\n const body = await request.text();\n\n const response = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"x-api-key\": env.LM_STUDIO_API_KEY,\n \"anthropic-version\": \"2023-06-01\",\n \"content-type\": \"application/json\",\n },\n body,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n },\n });\n}\n\nasync function handleTranscribeToken(env: Env): Promise {\n const response = await fetch(\n \"https://streaming.assemblyai.com/v3/token?expires_in_seconds=480\",\n {\n method: \"GET\",\n headers: {\n authorization: env.ASSEMBLYAI_API_KEY,\n },\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n const data = await response.text();\n return new Response(data, {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nasync function handleTTS(request: Request, env: Env): Promise {\n const body = await request.text();\n const voiceId = env.ELEVENLABS_VOICE_ID;\n\n const response = await fetch(\n `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,\n {\n method: \"POST\",\n headers: {\n \"xi-api-key\": env.ELEVENLABS_API_KEY,\n \"content-type\": \"application/json\",\n accept: \"audio/mpeg\",\n },\n body,\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"audio/mpeg\",\n },\n });\n}\n", "import type { Middleware } from \"./common\";\n\nconst drainBody: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} finally {\n\t\ttry {\n\t\t\tif (request.body !== null && !request.bodyUsed) {\n\t\t\t\tconst reader = request.body.getReader();\n\t\t\t\twhile (!(await reader.read()).done) {}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(\"Failed to drain the unused request body.\", e);\n\t\t}\n\t}\n};\n\nexport default drainBody;\n", "import type { Middleware } from \"./common\";\n\ninterface JsonError {\n\tmessage?: string;\n\tname?: string;\n\tstack?: string;\n\tcause?: JsonError;\n}\n\nfunction reduceError(e: any): JsonError {\n\treturn {\n\t\tname: e?.name,\n\t\tmessage: e?.message ?? String(e),\n\t\tstack: e?.stack,\n\t\tcause: e?.cause === undefined ? undefined : reduceError(e.cause),\n\t};\n}\n\n// See comment in `bundle.ts` for details on why this is needed\nconst jsonError: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} catch (e: any) {\n\t\tconst error = reduceError(e);\n\t\treturn Response.json(error, {\n\t\t\tstatus: 500,\n\t\t\theaders: { \"MF-Experimental-Error-Stack\": \"true\" },\n\t\t});\n\t}\n};\n\nexport default jsonError;\n", "\t\t\t\timport worker, * as OTHER_EXPORTS from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\timport * as __MIDDLEWARE_0__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts\";\nimport * as __MIDDLEWARE_1__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts\";\n\n\t\t\t\texport * from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\tconst MIDDLEWARE_TEST_INJECT = \"__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__\";\n\t\t\t\texport const __INTERNAL_WRANGLER_MIDDLEWARE__ = [\n\t\t\t\t\t\n\t\t\t\t\t__MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default\n\t\t\t\t]\n\t\t\t\texport default worker;", "export type Awaitable = T | Promise;\n// TODO: allow dispatching more events?\nexport type Dispatcher = (\n\ttype: \"scheduled\",\n\tinit: { cron?: string }\n) => Awaitable;\n\nexport type IncomingRequest = Request<\n\tunknown,\n\tIncomingRequestCfProperties\n>;\n\nexport interface MiddlewareContext {\n\tdispatch: Dispatcher;\n\tnext(request: IncomingRequest, env: any): Awaitable;\n}\n\nexport type Middleware = (\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tmiddlewareCtx: MiddlewareContext\n) => Awaitable;\n\nconst __facade_middleware__: Middleware[] = [];\n\n// The register functions allow for the insertion of one or many middleware,\n// We register internal middleware first in the stack, but have no way of controlling\n// the order that addMiddleware is run in service workers so need an internal function.\nexport function __facade_register__(...args: (Middleware | Middleware[])[]) {\n\t__facade_middleware__.push(...args.flat());\n}\nexport function __facade_registerInternal__(\n\t...args: (Middleware | Middleware[])[]\n) {\n\t__facade_middleware__.unshift(...args.flat());\n}\n\nfunction __facade_invokeChain__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tmiddlewareChain: Middleware[]\n): Awaitable {\n\tconst [head, ...tail] = middlewareChain;\n\tconst middlewareCtx: MiddlewareContext = {\n\t\tdispatch,\n\t\tnext(newRequest, newEnv) {\n\t\t\treturn __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);\n\t\t},\n\t};\n\treturn head(request, env, ctx, middlewareCtx);\n}\n\nexport function __facade_invoke__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tfinalMiddleware: Middleware\n): Awaitable {\n\treturn __facade_invokeChain__(request, env, ctx, dispatch, [\n\t\t...__facade_middleware__,\n\t\tfinalMiddleware,\n\t]);\n}\n", "// This loads all middlewares exposed on the middleware object and then starts\n// the invocation chain. The big idea is that we can add these to the middleware\n// export dynamically through wrangler, or we can potentially let users directly\n// add them as a sort of \"plugin\" system.\n\nimport ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js\";\nimport { __facade_invoke__, __facade_register__, Dispatcher } from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts\";\nimport type { WorkerEntrypointConstructor } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js\";\n\n// Preserve all the exports from the worker\nexport * from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-P89daS/middleware-insertion-facade.js\";\n\nclass __Facade_ScheduledController__ implements ScheduledController {\n\treadonly #noRetry: ScheduledController[\"noRetry\"];\n\n\tconstructor(\n\t\treadonly scheduledTime: number,\n\t\treadonly cron: string,\n\t\tnoRetry: ScheduledController[\"noRetry\"]\n\t) {\n\t\tthis.#noRetry = noRetry;\n\t}\n\n\tnoRetry() {\n\t\tif (!(this instanceof __Facade_ScheduledController__)) {\n\t\t\tthrow new TypeError(\"Illegal invocation\");\n\t\t}\n\t\t// Need to call native method immediately in case uncaught error thrown\n\t\tthis.#noRetry();\n\t}\n}\n\nfunction wrapExportedHandler(worker: ExportedHandler): ExportedHandler {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn worker;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\tconst fetchDispatcher: ExportedHandlerFetchHandler = function (\n\t\trequest,\n\t\tenv,\n\t\tctx\n\t) {\n\t\tif (worker.fetch === undefined) {\n\t\t\tthrow new Error(\"Handler does not export a fetch() function.\");\n\t\t}\n\t\treturn worker.fetch(request, env, ctx);\n\t};\n\n\treturn {\n\t\t...worker,\n\t\tfetch(request, env, ctx) {\n\t\t\tconst dispatcher: Dispatcher = function (type, init) {\n\t\t\t\tif (type === \"scheduled\" && worker.scheduled !== undefined) {\n\t\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\t\tDate.now(),\n\t\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t\t() => {}\n\t\t\t\t\t);\n\t\t\t\t\treturn worker.scheduled(controller, env, ctx);\n\t\t\t\t}\n\t\t\t};\n\t\t\treturn __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);\n\t\t},\n\t};\n}\n\nfunction wrapWorkerEntrypoint(\n\tklass: WorkerEntrypointConstructor\n): WorkerEntrypointConstructor {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn klass;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\t// `extend`ing `klass` here so other RPC methods remain callable\n\treturn class extends klass {\n\t\t#fetchDispatcher: ExportedHandlerFetchHandler> = (\n\t\t\trequest,\n\t\t\tenv,\n\t\t\tctx\n\t\t) => {\n\t\t\tthis.env = env;\n\t\t\tthis.ctx = ctx;\n\t\t\tif (super.fetch === undefined) {\n\t\t\t\tthrow new Error(\"Entrypoint class does not define a fetch() function.\");\n\t\t\t}\n\t\t\treturn super.fetch(request);\n\t\t};\n\n\t\t#dispatcher: Dispatcher = (type, init) => {\n\t\t\tif (type === \"scheduled\" && super.scheduled !== undefined) {\n\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\tDate.now(),\n\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t() => {}\n\t\t\t\t);\n\t\t\t\treturn super.scheduled(controller);\n\t\t\t}\n\t\t};\n\n\t\tfetch(request: Request) {\n\t\t\treturn __facade_invoke__(\n\t\t\t\trequest,\n\t\t\t\tthis.env,\n\t\t\t\tthis.ctx,\n\t\t\t\tthis.#dispatcher,\n\t\t\t\tthis.#fetchDispatcher\n\t\t\t);\n\t\t}\n\t};\n}\n\nlet WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;\nif (typeof ENTRY === \"object\") {\n\tWRAPPED_ENTRY = wrapExportedHandler(ENTRY);\n} else if (typeof ENTRY === \"function\") {\n\tWRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);\n}\nexport default WRAPPED_ENTRY;\n"], - "mappings": "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", - "names": [] -} diff --git a/worker/src/index.ts b/worker/src/index.ts index 539b4ef3..2e3e9345 100644 --- a/worker/src/index.ts +++ b/worker/src/index.ts @@ -10,7 +10,7 @@ */ interface Env { - LM_STUDIO_API_KEY: string; + ANTHROPIC_API_KEY: string; ELEVENLABS_API_KEY: string; ELEVENLABS_VOICE_ID: string; ASSEMBLYAI_API_KEY: string; @@ -54,7 +54,7 @@ async function handleChat(request: Request, env: Env): Promise { const response = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { - "x-api-key": env.LM_STUDIO_API_KEY, + "x-api-key": env.ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01", "content-type": "application/json", }, From 6aaedf803519b58a1a2823531dd7a8544a002743 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 18:25:08 +0200 Subject: [PATCH 08/20] refactor: clean up spacing and organization in CompanionPanelView --- leanring-buddy/CompanionPanelView.swift | 296 +++++++++++++++++------- 1 file changed, 216 insertions(+), 80 deletions(-) diff --git a/leanring-buddy/CompanionPanelView.swift b/leanring-buddy/CompanionPanelView.swift index 3026b4bc..c7f06590 100644 --- a/leanring-buddy/CompanionPanelView.swift +++ b/leanring-buddy/CompanionPanelView.swift @@ -13,42 +13,42 @@ import SwiftUI struct CompanionPanelView: View { @ObservedObject var companionManager: CompanionManager @State private var emailInput: String = "" - + var body: some View { VStack(alignment: .leading, spacing: 0) { panelHeader Divider() .background(DS.Colors.borderSubtle) .padding(.horizontal, 16) - + permissionsCopySection .padding(.top, 16) .padding(.horizontal, 16) - + if companionManager.hasCompletedOnboarding && companionManager.allPermissionsGranted { Spacer() .frame(height: 12) - + modelPickerRow .padding(.horizontal, 16) } - + if !companionManager.allPermissionsGranted { Spacer() .frame(height: 16) - + settingsSection .padding(.horizontal, 16) } - + if !companionManager.hasCompletedOnboarding && companionManager.allPermissionsGranted { Spacer() .frame(height: 16) - + startButton .padding(.horizontal, 16) } - + // Show Clicky toggle — hidden for now // if companionManager.hasCompletedOnboarding && companionManager.allPermissionsGranted { // Spacer() @@ -57,52 +57,52 @@ struct CompanionPanelView: View { // showClickyCursorToggleRow // .padding(.horizontal, 16) // } - + if companionManager.hasCompletedOnboarding && companionManager.allPermissionsGranted { Spacer() .frame(height: 16) - - dmFarzaButton + + dmFarzaButton // <-- This is now defined below .padding(.horizontal, 16) } - + Spacer() .frame(height: 12) - + Divider() .background(DS.Colors.borderSubtle) .padding(.horizontal, 16) - - footerSection + + footerSection // <-- This is now defined below .padding(.horizontal, 16) .padding(.vertical, 12) } .frame(width: 320) - .background(panelBackground) + .background(panelBackground) // <-- This is now defined below } - + // MARK: - Header - + private var panelHeader: some View { HStack { HStack(spacing: 8) { // Animated status dot Circle() - .fill(statusDotColor) + .fill(statusDotColor) // <-- This is now defined below .frame(width: 8, height: 8) .shadow(color: statusDotColor.opacity(0.6), radius: 4) - + Text("Clicky") .font(.system(size: 14, weight: .semibold)) .foregroundColor(DS.Colors.textPrimary) } - + Spacer() - - Text(statusText) + + Text(statusText) // <-- This is now defined below .font(.system(size: 12, weight: .medium)) .foregroundColor(DS.Colors.textTertiary) - + Button(action: { NotificationCenter.default.post(name: .clickyDismissPanel, object: nil) }) { @@ -121,9 +121,9 @@ struct CompanionPanelView: View { .padding(.horizontal, 16) .padding(.vertical, 14) } - + // MARK: - Permissions Copy - + @ViewBuilder private var permissionsCopySection: some View { if companionManager.hasCompletedOnboarding && companionManager.allPermissionsGranted { @@ -152,7 +152,7 @@ struct CompanionPanelView: View { Text("Permissions needed") .font(.system(size: 12, weight: .bold)) .foregroundColor(DS.Colors.textSecondary) - + Text("Some permissions were revoked. Grant all four below to keep using Clicky.") .font(.system(size: 11)) .foregroundColor(DS.Colors.textTertiary) @@ -164,12 +164,12 @@ struct CompanionPanelView: View { Text("Hi, I'm Farza. This is Clicky.") .font(.system(size: 12, weight: .bold)) .foregroundColor(DS.Colors.textSecondary) - + Text("A side project I made for fun to help me learn stuff as I use my computer.") .font(.system(size: 11)) .foregroundColor(DS.Colors.textTertiary) .fixedSize(horizontal: false, vertical: true) - + Text("Nothing runs in the background. Clicky will only take a screenshot when you press the hot key. So, you can give that permission in peace. If you are still sus, eh, I can't do much there champ.") .font(.system(size: 11)) .foregroundColor(Color(red: 0.9, green: 0.4, blue: 0.4)) @@ -178,9 +178,9 @@ struct CompanionPanelView: View { .frame(maxWidth: .infinity, alignment: .leading) } } - + // MARK: - Email + Start Button - + @ViewBuilder private var startButton: some View { if !companionManager.hasCompletedOnboarding && companionManager.allPermissionsGranted { @@ -200,7 +200,7 @@ struct CompanionPanelView: View { RoundedRectangle(cornerRadius: DS.CornerRadius.medium, style: .continuous) .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) ) - + Button(action: { companionManager.submitEmail(emailInput) }) { @@ -239,9 +239,9 @@ struct CompanionPanelView: View { } } } - + // MARK: - Permissions - + private var settingsSection: some View { VStack(spacing: 2) { Text("PERMISSIONS") @@ -249,20 +249,20 @@ struct CompanionPanelView: View { .foregroundColor(DS.Colors.textTertiary) .frame(maxWidth: .infinity, alignment: .leading) .padding(.bottom, 6) - + microphonePermissionRow - + accessibilityPermissionRow - + screenRecordingPermissionRow - + if companionManager.hasScreenRecordingPermission { screenContentPermissionRow } - + } } - + private var accessibilityPermissionRow: some View { let isGranted = companionManager.hasAccessibilityPermission return HStack { @@ -271,14 +271,14 @@ struct CompanionPanelView: View { .font(.system(size: 12, weight: .medium)) .foregroundColor(isGranted ? DS.Colors.textTertiary : DS.Colors.warning) .frame(width: 16) - + Text("Accessibility") .font(.system(size: 13, weight: .medium)) .foregroundColor(DS.Colors.textSecondary) } - + Spacer() - + if isGranted { HStack(spacing: 4) { Circle() @@ -307,7 +307,7 @@ struct CompanionPanelView: View { } .buttonStyle(.plain) .pointerCursor() - + Button(action: { // Reveals the app in Finder so the user can drag it into // the Accessibility list if it doesn't appear automatically @@ -332,7 +332,7 @@ struct CompanionPanelView: View { } .padding(.vertical, 6) } - + private var screenRecordingPermissionRow: some View { let isGranted = companionManager.hasScreenRecordingPermission return HStack { @@ -341,22 +341,22 @@ struct CompanionPanelView: View { .font(.system(size: 12, weight: .medium)) .foregroundColor(isGranted ? DS.Colors.textTertiary : DS.Colors.warning) .frame(width: 16) - + VStack(alignment: .leading, spacing: 1) { Text("Screen Recording") .font(.system(size: 13, weight: .medium)) .foregroundColor(DS.Colors.textSecondary) - + Text(isGranted ? "Only takes a screenshot when you use the hotkey" : "Quit and reopen after granting") - .font(.system(size: 10)) - .foregroundColor(DS.Colors.textTertiary) + .font(.system(size: 10)) + .foregroundColor(DS.Colors.textTertiary) } } - + Spacer() - + if isGranted { HStack(spacing: 4) { Circle() @@ -389,7 +389,7 @@ struct CompanionPanelView: View { } .padding(.vertical, 6) } - + private var screenContentPermissionRow: some View { let isGranted = companionManager.hasScreenContentPermission return HStack { @@ -398,14 +398,14 @@ struct CompanionPanelView: View { .font(.system(size: 12, weight: .medium)) .foregroundColor(isGranted ? DS.Colors.textTertiary : DS.Colors.warning) .frame(width: 16) - + Text("Screen Content") .font(.system(size: 13, weight: .medium)) .foregroundColor(DS.Colors.textSecondary) } - + Spacer() - + if isGranted { HStack(spacing: 4) { Circle() @@ -435,7 +435,7 @@ struct CompanionPanelView: View { } .padding(.vertical, 6) } - + private var microphonePermissionRow: some View { let isGranted = companionManager.hasMicrophonePermission return HStack { @@ -444,14 +444,14 @@ struct CompanionPanelView: View { .font(.system(size: 12, weight: .medium)) .foregroundColor(isGranted ? DS.Colors.textTertiary : DS.Colors.warning) .frame(width: 16) - + Text("Microphone") .font(.system(size: 13, weight: .medium)) .foregroundColor(DS.Colors.textSecondary) } - + Spacer() - + if isGranted { HStack(spacing: 4) { Circle() @@ -490,7 +490,7 @@ struct CompanionPanelView: View { } .padding(.vertical, 6) } - + private func permissionRow( label: String, iconName: String, @@ -503,14 +503,14 @@ struct CompanionPanelView: View { .font(.system(size: 12, weight: .medium)) .foregroundColor(isGranted ? DS.Colors.textTertiary : DS.Colors.warning) .frame(width: 16) - + Text(label) .font(.system(size: 13, weight: .medium)) .foregroundColor(DS.Colors.textSecondary) } - + Spacer() - + if isGranted { HStack(spacing: 4) { Circle() @@ -542,11 +542,11 @@ struct CompanionPanelView: View { } .padding(.vertical, 6) } - - - + + + // MARK: - Show Clicky Cursor Toggle - + private var showClickyCursorToggleRow: some View { HStack { HStack(spacing: 8) { @@ -554,14 +554,14 @@ struct CompanionPanelView: View { .font(.system(size: 12, weight: .medium)) .foregroundColor(DS.Colors.textTertiary) .frame(width: 16) - + Text("Show Clicky") .font(.system(size: 13, weight: .medium)) .foregroundColor(DS.Colors.textSecondary) } - + Spacer() - + Toggle("", isOn: Binding( get: { companionManager.isClickyCursorEnabled }, set: { companionManager.setClickyCursorEnabled($0) } @@ -573,7 +573,7 @@ struct CompanionPanelView: View { } .padding(.vertical, 4) } - + private var speechToTextProviderRow: some View { HStack { HStack(spacing: 8) { @@ -581,31 +581,31 @@ struct CompanionPanelView: View { .font(.system(size: 12, weight: .medium)) .foregroundColor(DS.Colors.textTertiary) .frame(width: 16) - + Text("Speech to Text") .font(.system(size: 13, weight: .medium)) .foregroundColor(DS.Colors.textSecondary) } - + Spacer() - + Text(companionManager.buddyDictationManager.transcriptionProviderDisplayName) .font(.system(size: 11, weight: .medium)) .foregroundColor(DS.Colors.textTertiary) } .padding(.vertical, 4) } - + // MARK: - Model Picker - + private var modelPickerRow: some View { HStack { Text("Model") .font(.system(size: 13, weight: .medium)) .foregroundColor(DS.Colors.textSecondary) - + Spacer() - + Menu { // Section for Local LM Studio Models Section(header: Text("LM Studio (Local)")) { @@ -653,7 +653,7 @@ struct CompanionPanelView: View { } } } - + } label: { HStack(spacing: 4) { Text(companionManager.selectedModel) @@ -683,3 +683,139 @@ struct CompanionPanelView: View { .padding(.vertical, 4) } + private func modelOptionButton(label: String, modelID: String) -> some View { + let isSelected = companionManager.selectedModel == modelID + return Button(action: { + companionManager.setSelectedModel(modelID) + }) { + Text(label) + .font(.system(size: 11, weight: .medium)) + .foregroundColor(isSelected ? DS.Colors.textPrimary : DS.Colors.textTertiary) + .padding(.horizontal, 10) + .padding(.vertical, 5) + .background( + RoundedRectangle(cornerRadius: 5, style: .continuous) + .fill(isSelected ? Color.white.opacity(0.1) : Color.clear) + ) + } + .buttonStyle(.plain) + .pointerCursor() + } + + // MARK: - DM Farza Button + + private var dmFarzaButton: some View { + Button(action: { + if let url = URL(string: "https://x.com/farzatv") { + NSWorkspace.shared.open(url) + } + }) { + HStack(spacing: 8) { + Image(systemName: "bubble.left.fill") + .font(.system(size: 12, weight: .medium)) + + VStack(alignment: .leading, spacing: 2) { + Text("Got feedback? DM me") + .font(.system(size: 12, weight: .semibold)) + Text("Bugs, ideas, anything — I read every message.") + .font(.system(size: 10)) + .foregroundColor(DS.Colors.textTertiary) + } + } + .foregroundColor(DS.Colors.textSecondary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 12) + .padding(.vertical, 10) + .background( + RoundedRectangle(cornerRadius: DS.CornerRadius.medium, style: .continuous) + .fill(Color.white.opacity(0.06)) + ) + .overlay( + RoundedRectangle(cornerRadius: DS.CornerRadius.medium, style: .continuous) + .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) + ) + } + .buttonStyle(.plain) + .pointerCursor() + } + + // MARK: - Footer + + private var footerSection: some View { + HStack { + Button(action: { + NSApp.terminate(nil) + }) { + HStack(spacing: 6) { + Image(systemName: "power") + .font(.system(size: 11, weight: .medium)) + Text("Quit Clicky") + .font(.system(size: 12, weight: .medium)) + } + .foregroundColor(DS.Colors.textTertiary) + } + .buttonStyle(.plain) + .pointerCursor() + + if companionManager.hasCompletedOnboarding { + Spacer() + + Button(action: { + companionManager.replayOnboarding() + }) { + HStack(spacing: 6) { + Image(systemName: "play.circle") + .font(.system(size: 11, weight: .medium)) + Text("Watch Onboarding Again") + .font(.system(size: 12, weight: .medium)) + } + .foregroundColor(DS.Colors.textTertiary) + } + .buttonStyle(.plain) + .pointerCursor() + } + } + } + + // MARK: - Visual Helpers + + private var panelBackground: some View { + RoundedRectangle(cornerRadius: 12, style: .continuous) + .fill(DS.Colors.background) + .shadow(color: Color.black.opacity(0.5), radius: 20, x: 0, y: 10) + .shadow(color: Color.black.opacity(0.3), radius: 4, x: 0, y: 2) + } + + private var statusDotColor: Color { + if !companionManager.isOverlayVisible { + return DS.Colors.textTertiary + } + switch companionManager.voiceState { + case .idle: + return DS.Colors.success + case .listening: + return DS.Colors.blue400 + case .processing, .responding: + return DS.Colors.blue400 + } + } + + private var statusText: String { + if !companionManager.hasCompletedOnboarding || !companionManager.allPermissionsGranted { + return "Setup" + } + if !companionManager.isOverlayVisible { + return "Ready" + } + switch companionManager.voiceState { + case .idle: + return "Active" + case .listening: + return "Listening" + case .processing: + return "Processing" + case .responding: + return "Responding" + } + } +} From f93290351d2a79e9f7ced84a61adba5cdea9e27c Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 18:27:08 +0200 Subject: [PATCH 09/20] Remove middleware loader and associated files to streamline worker functionality --- .gitignore | 4 +- .../miniflare-CacheObject/metadata.sqlite | Bin 4096 -> 0 bytes .../miniflare-CacheObject/metadata.sqlite-shm | Bin 32768 -> 0 bytes .../miniflare-CacheObject/metadata.sqlite-wal | Bin 8272 -> 0 bytes .../tmp/bundle-OtdA08/checked-fetch.js | 28 -- .../middleware-insertion-facade.js | 11 - .../bundle-OtdA08/middleware-loader.entry.ts | 134 -------- worker/.wrangler/tmp/dev-efgXP0/index.js | 315 ------------------ worker/.wrangler/tmp/dev-efgXP0/index.js.map | 8 - 9 files changed, 3 insertions(+), 497 deletions(-) delete mode 100644 worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite delete mode 100644 worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm delete mode 100644 worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal delete mode 100644 worker/.wrangler/tmp/bundle-OtdA08/checked-fetch.js delete mode 100644 worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js delete mode 100644 worker/.wrangler/tmp/bundle-OtdA08/middleware-loader.entry.ts delete mode 100644 worker/.wrangler/tmp/dev-efgXP0/index.js delete mode 100644 worker/.wrangler/tmp/dev-efgXP0/index.js.map diff --git a/.gitignore b/.gitignore index 9e2b6b81..dd28144e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ releases/ .claude/ coding-plans/ -/worker/wrangler.toml \ No newline at end of file +/worker/wrangler.toml + +/worker/.wrangler/ \ No newline at end of file diff --git a/worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite b/worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite deleted file mode 100644 index 0b58f0dca7d6ce95907cb0b322dc4802f04688c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WYDXN;00+HAlr;ljiVtj n8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O6ovo**AfQj diff --git a/worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm b/worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm deleted file mode 100644 index 82bb89fa3391a089f34b5a7b97ffde0e9b5e6346..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI)u?fOJ6b9f^NH11dB4iLd3wN-wvaqpDoe5H=6kNa!?8E`w!Ybwo2Wjnud_Np_ z_c-2zZ-6UbRmxeUv|5grSjYALb{RI;)4l7OXWj1Jk8N={_vMf8bMNRmOfs#zz0c# BC~*J) diff --git a/worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal b/worker/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal deleted file mode 100644 index ce7f048f041e1df2ff83cfdcf4bdc64d423a8952..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8272 zcmXr7XKP~6eI&uaAiw|u6=&zoYrCdDp}6%z_wKoM+kirh$m08!3MvGt7CZ~ z!DtAKhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#lBzXyjyM7nhf3Y)mXkOv*`( zPfm+>^l=RGg)^9(gIpa$TopnboqSvs;KB+TTnb>2m|T)y6rY)*5aJpUq7V?|>FXF2 zso?DzsRLC|oSc!GQks*R5?_*;o2uaH7vk#f8U&WqR0#JB@$e4~Q3&!6_jCaUI=G(? z8!y {} - ); - return worker.scheduled(controller, env, ctx); - } - }; - return __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher); - }, - }; -} - -function wrapWorkerEntrypoint( - klass: WorkerEntrypointConstructor -): WorkerEntrypointConstructor { - // If we don't have any middleware defined, just return the handler as is - if ( - __INTERNAL_WRANGLER_MIDDLEWARE__ === undefined || - __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0 - ) { - return klass; - } - // Otherwise, register all middleware once - for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { - __facade_register__(middleware); - } - - // `extend`ing `klass` here so other RPC methods remain callable - return class extends klass { - #fetchDispatcher: ExportedHandlerFetchHandler> = ( - request, - env, - ctx - ) => { - this.env = env; - this.ctx = ctx; - if (super.fetch === undefined) { - throw new Error("Entrypoint class does not define a fetch() function."); - } - return super.fetch(request); - }; - - #dispatcher: Dispatcher = (type, init) => { - if (type === "scheduled" && super.scheduled !== undefined) { - const controller = new __Facade_ScheduledController__( - Date.now(), - init.cron ?? "", - () => {} - ); - return super.scheduled(controller); - } - }; - - fetch(request: Request) { - return __facade_invoke__( - request, - this.env, - this.ctx, - this.#dispatcher, - this.#fetchDispatcher - ); - } - }; -} - -let WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined; -if (typeof ENTRY === "object") { - WRAPPED_ENTRY = wrapExportedHandler(ENTRY); -} else if (typeof ENTRY === "function") { - WRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY); -} -export default WRAPPED_ENTRY; diff --git a/worker/.wrangler/tmp/dev-efgXP0/index.js b/worker/.wrangler/tmp/dev-efgXP0/index.js deleted file mode 100644 index 6213bd10..00000000 --- a/worker/.wrangler/tmp/dev-efgXP0/index.js +++ /dev/null @@ -1,315 +0,0 @@ -var __defProp = Object.defineProperty; -var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); - -// .wrangler/tmp/bundle-OtdA08/checked-fetch.js -var urls = /* @__PURE__ */ new Set(); -function checkURL(request, init) { - const url = request instanceof URL ? request : new URL( - (typeof request === "string" ? new Request(request, init) : request).url - ); - if (url.port && url.port !== "443" && url.protocol === "https:") { - if (!urls.has(url.toString())) { - urls.add(url.toString()); - console.warn( - `WARNING: known issue with \`fetch()\` requests to custom HTTPS ports in published Workers: - - ${url.toString()} - the custom port will be ignored when the Worker is published using the \`wrangler deploy\` command. -` - ); - } - } -} -__name(checkURL, "checkURL"); -globalThis.fetch = new Proxy(globalThis.fetch, { - apply(target, thisArg, argArray) { - const [request, init] = argArray; - checkURL(request, init); - return Reflect.apply(target, thisArg, argArray); - } -}); - -// src/index.ts -var src_default = { - async fetch(request, env) { - const url = new URL(request.url); - if (request.method !== "POST") { - return new Response("Method not allowed", { status: 405 }); - } - try { - if (url.pathname === "/chat") { - return await handleChat(request, env); - } - if (url.pathname === "/tts") { - return await handleTTS(request, env); - } - if (url.pathname === "/transcribe-token") { - return await handleTranscribeToken(env); - } - } catch (error) { - console.error(`[${url.pathname}] Unhandled error:`, error); - return new Response( - JSON.stringify({ error: String(error) }), - { status: 500, headers: { "content-type": "application/json" } } - ); - } - return new Response("Not found", { status: 404 }); - } -}; -async function handleChat(request, env) { - const body = await request.text(); - const response = await fetch("https://api.anthropic.com/v1/messages", { - method: "POST", - headers: { - "x-api-key": env.LM_STUDIO_API_KEY, - "anthropic-version": "2023-06-01", - "content-type": "application/json" - }, - body - }); - if (!response.ok) { - const errorBody = await response.text(); - console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`); - return new Response(errorBody, { - status: response.status, - headers: { "content-type": "application/json" } - }); - } - return new Response(response.body, { - status: response.status, - headers: { - "content-type": response.headers.get("content-type") || "text/event-stream", - "cache-control": "no-cache" - } - }); -} -__name(handleChat, "handleChat"); -async function handleTranscribeToken(env) { - const response = await fetch( - "https://streaming.assemblyai.com/v3/token?expires_in_seconds=480", - { - method: "GET", - headers: { - authorization: env.ASSEMBLYAI_API_KEY - } - } - ); - if (!response.ok) { - const errorBody = await response.text(); - console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`); - return new Response(errorBody, { - status: response.status, - headers: { "content-type": "application/json" } - }); - } - const data = await response.text(); - return new Response(data, { - status: 200, - headers: { "content-type": "application/json" } - }); -} -__name(handleTranscribeToken, "handleTranscribeToken"); -async function handleTTS(request, env) { - const body = await request.text(); - const voiceId = env.ELEVENLABS_VOICE_ID; - const response = await fetch( - `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, - { - method: "POST", - headers: { - "xi-api-key": env.ELEVENLABS_API_KEY, - "content-type": "application/json", - accept: "audio/mpeg" - }, - body - } - ); - if (!response.ok) { - const errorBody = await response.text(); - console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`); - return new Response(errorBody, { - status: response.status, - headers: { "content-type": "application/json" } - }); - } - return new Response(response.body, { - status: response.status, - headers: { - "content-type": response.headers.get("content-type") || "audio/mpeg" - } - }); -} -__name(handleTTS, "handleTTS"); - -// node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts -var drainBody = /* @__PURE__ */ __name(async (request, env, _ctx, middlewareCtx) => { - try { - return await middlewareCtx.next(request, env); - } finally { - try { - if (request.body !== null && !request.bodyUsed) { - const reader = request.body.getReader(); - while (!(await reader.read()).done) { - } - } - } catch (e) { - console.error("Failed to drain the unused request body.", e); - } - } -}, "drainBody"); -var middleware_ensure_req_body_drained_default = drainBody; - -// node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts -function reduceError(e) { - return { - name: e?.name, - message: e?.message ?? String(e), - stack: e?.stack, - cause: e?.cause === void 0 ? void 0 : reduceError(e.cause) - }; -} -__name(reduceError, "reduceError"); -var jsonError = /* @__PURE__ */ __name(async (request, env, _ctx, middlewareCtx) => { - try { - return await middlewareCtx.next(request, env); - } catch (e) { - const error = reduceError(e); - return Response.json(error, { - status: 500, - headers: { "MF-Experimental-Error-Stack": "true" } - }); - } -}, "jsonError"); -var middleware_miniflare3_json_error_default = jsonError; - -// .wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js -var __INTERNAL_WRANGLER_MIDDLEWARE__ = [ - middleware_ensure_req_body_drained_default, - middleware_miniflare3_json_error_default -]; -var middleware_insertion_facade_default = src_default; - -// node_modules/wrangler/templates/middleware/common.ts -var __facade_middleware__ = []; -function __facade_register__(...args) { - __facade_middleware__.push(...args.flat()); -} -__name(__facade_register__, "__facade_register__"); -function __facade_invokeChain__(request, env, ctx, dispatch, middlewareChain) { - const [head, ...tail] = middlewareChain; - const middlewareCtx = { - dispatch, - next(newRequest, newEnv) { - return __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail); - } - }; - return head(request, env, ctx, middlewareCtx); -} -__name(__facade_invokeChain__, "__facade_invokeChain__"); -function __facade_invoke__(request, env, ctx, dispatch, finalMiddleware) { - return __facade_invokeChain__(request, env, ctx, dispatch, [ - ...__facade_middleware__, - finalMiddleware - ]); -} -__name(__facade_invoke__, "__facade_invoke__"); - -// .wrangler/tmp/bundle-OtdA08/middleware-loader.entry.ts -var __Facade_ScheduledController__ = class ___Facade_ScheduledController__ { - constructor(scheduledTime, cron, noRetry) { - this.scheduledTime = scheduledTime; - this.cron = cron; - this.#noRetry = noRetry; - } - static { - __name(this, "__Facade_ScheduledController__"); - } - #noRetry; - noRetry() { - if (!(this instanceof ___Facade_ScheduledController__)) { - throw new TypeError("Illegal invocation"); - } - this.#noRetry(); - } -}; -function wrapExportedHandler(worker) { - if (__INTERNAL_WRANGLER_MIDDLEWARE__ === void 0 || __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0) { - return worker; - } - for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { - __facade_register__(middleware); - } - const fetchDispatcher = /* @__PURE__ */ __name(function(request, env, ctx) { - if (worker.fetch === void 0) { - throw new Error("Handler does not export a fetch() function."); - } - return worker.fetch(request, env, ctx); - }, "fetchDispatcher"); - return { - ...worker, - fetch(request, env, ctx) { - const dispatcher = /* @__PURE__ */ __name(function(type, init) { - if (type === "scheduled" && worker.scheduled !== void 0) { - const controller = new __Facade_ScheduledController__( - Date.now(), - init.cron ?? "", - () => { - } - ); - return worker.scheduled(controller, env, ctx); - } - }, "dispatcher"); - return __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher); - } - }; -} -__name(wrapExportedHandler, "wrapExportedHandler"); -function wrapWorkerEntrypoint(klass) { - if (__INTERNAL_WRANGLER_MIDDLEWARE__ === void 0 || __INTERNAL_WRANGLER_MIDDLEWARE__.length === 0) { - return klass; - } - for (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) { - __facade_register__(middleware); - } - return class extends klass { - #fetchDispatcher = /* @__PURE__ */ __name((request, env, ctx) => { - this.env = env; - this.ctx = ctx; - if (super.fetch === void 0) { - throw new Error("Entrypoint class does not define a fetch() function."); - } - return super.fetch(request); - }, "#fetchDispatcher"); - #dispatcher = /* @__PURE__ */ __name((type, init) => { - if (type === "scheduled" && super.scheduled !== void 0) { - const controller = new __Facade_ScheduledController__( - Date.now(), - init.cron ?? "", - () => { - } - ); - return super.scheduled(controller); - } - }, "#dispatcher"); - fetch(request) { - return __facade_invoke__( - request, - this.env, - this.ctx, - this.#dispatcher, - this.#fetchDispatcher - ); - } - }; -} -__name(wrapWorkerEntrypoint, "wrapWorkerEntrypoint"); -var WRAPPED_ENTRY; -if (typeof middleware_insertion_facade_default === "object") { - WRAPPED_ENTRY = wrapExportedHandler(middleware_insertion_facade_default); -} else if (typeof middleware_insertion_facade_default === "function") { - WRAPPED_ENTRY = wrapWorkerEntrypoint(middleware_insertion_facade_default); -} -var middleware_loader_entry_default = WRAPPED_ENTRY; -export { - __INTERNAL_WRANGLER_MIDDLEWARE__, - middleware_loader_entry_default as default -}; -//# sourceMappingURL=index.js.map diff --git a/worker/.wrangler/tmp/dev-efgXP0/index.js.map b/worker/.wrangler/tmp/dev-efgXP0/index.js.map deleted file mode 100644 index cbce7d5c..00000000 --- a/worker/.wrangler/tmp/dev-efgXP0/index.js.map +++ /dev/null @@ -1,8 +0,0 @@ -{ - "version": 3, - "sources": ["../bundle-OtdA08/checked-fetch.js", "../../../src/index.ts", "../../../node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts", "../../../node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts", "../bundle-OtdA08/middleware-insertion-facade.js", "../../../node_modules/wrangler/templates/middleware/common.ts", "../bundle-OtdA08/middleware-loader.entry.ts"], - "sourceRoot": "/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/dev-efgXP0", - "sourcesContent": ["const urls = new Set();\n\nfunction checkURL(request, init) {\n\tconst url =\n\t\trequest instanceof URL\n\t\t\t? request\n\t\t\t: new URL(\n\t\t\t\t\t(typeof request === \"string\" ? new Request(request, init) : request)\n\t\t\t\t\t\t.url\n\t\t\t\t);\n\tif (url.port && url.port !== \"443\" && url.protocol === \"https:\") {\n\t\tif (!urls.has(url.toString())) {\n\t\t\turls.add(url.toString());\n\t\t\tconsole.warn(\n\t\t\t\t`WARNING: known issue with \\`fetch()\\` requests to custom HTTPS ports in published Workers:\\n` +\n\t\t\t\t\t` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \\`wrangler deploy\\` command.\\n`\n\t\t\t);\n\t\t}\n\t}\n}\n\nglobalThis.fetch = new Proxy(globalThis.fetch, {\n\tapply(target, thisArg, argArray) {\n\t\tconst [request, init] = argArray;\n\t\tcheckURL(request, init);\n\t\treturn Reflect.apply(target, thisArg, argArray);\n\t},\n});\n", "/**\n * Clicky Proxy Worker\n *\n * Proxies requests to Claude and ElevenLabs APIs so the app never\n * ships with raw API keys. Keys are stored as Cloudflare secrets.\n *\n * Routes:\n * POST /chat \u2192 Anthropic Messages API (streaming)\n * POST /tts \u2192 ElevenLabs TTS API\n */\n\ninterface Env {\n LM_STUDIO_API_KEY: string;\n ELEVENLABS_API_KEY: string;\n ELEVENLABS_VOICE_ID: string;\n ASSEMBLYAI_API_KEY: string;\n}\n\nexport default {\n async fetch(request: Request, env: Env): Promise {\n const url = new URL(request.url);\n\n if (request.method !== \"POST\") {\n return new Response(\"Method not allowed\", { status: 405 });\n }\n\n try {\n if (url.pathname === \"/chat\") {\n return await handleChat(request, env);\n }\n\n if (url.pathname === \"/tts\") {\n return await handleTTS(request, env);\n }\n\n if (url.pathname === \"/transcribe-token\") {\n return await handleTranscribeToken(env);\n }\n } catch (error) {\n console.error(`[${url.pathname}] Unhandled error:`, error);\n return new Response(\n JSON.stringify({ error: String(error) }),\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n }\n\n return new Response(\"Not found\", { status: 404 });\n },\n};\n\nasync function handleChat(request: Request, env: Env): Promise {\n const body = await request.text();\n\n const response = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"x-api-key\": env.LM_STUDIO_API_KEY,\n \"anthropic-version\": \"2023-06-01\",\n \"content-type\": \"application/json\",\n },\n body,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/chat] Anthropic API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n },\n });\n}\n\nasync function handleTranscribeToken(env: Env): Promise {\n const response = await fetch(\n \"https://streaming.assemblyai.com/v3/token?expires_in_seconds=480\",\n {\n method: \"GET\",\n headers: {\n authorization: env.ASSEMBLYAI_API_KEY,\n },\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/transcribe-token] AssemblyAI token error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n const data = await response.text();\n return new Response(data, {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nasync function handleTTS(request: Request, env: Env): Promise {\n const body = await request.text();\n const voiceId = env.ELEVENLABS_VOICE_ID;\n\n const response = await fetch(\n `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,\n {\n method: \"POST\",\n headers: {\n \"xi-api-key\": env.ELEVENLABS_API_KEY,\n \"content-type\": \"application/json\",\n accept: \"audio/mpeg\",\n },\n body,\n }\n );\n\n if (!response.ok) {\n const errorBody = await response.text();\n console.error(`[/tts] ElevenLabs API error ${response.status}: ${errorBody}`);\n return new Response(errorBody, {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n\n return new Response(response.body, {\n status: response.status,\n headers: {\n \"content-type\": response.headers.get(\"content-type\") || \"audio/mpeg\",\n },\n });\n}\n", "import type { Middleware } from \"./common\";\n\nconst drainBody: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} finally {\n\t\ttry {\n\t\t\tif (request.body !== null && !request.bodyUsed) {\n\t\t\t\tconst reader = request.body.getReader();\n\t\t\t\twhile (!(await reader.read()).done) {}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(\"Failed to drain the unused request body.\", e);\n\t\t}\n\t}\n};\n\nexport default drainBody;\n", "import type { Middleware } from \"./common\";\n\ninterface JsonError {\n\tmessage?: string;\n\tname?: string;\n\tstack?: string;\n\tcause?: JsonError;\n}\n\nfunction reduceError(e: any): JsonError {\n\treturn {\n\t\tname: e?.name,\n\t\tmessage: e?.message ?? String(e),\n\t\tstack: e?.stack,\n\t\tcause: e?.cause === undefined ? undefined : reduceError(e.cause),\n\t};\n}\n\n// See comment in `bundle.ts` for details on why this is needed\nconst jsonError: Middleware = async (request, env, _ctx, middlewareCtx) => {\n\ttry {\n\t\treturn await middlewareCtx.next(request, env);\n\t} catch (e: any) {\n\t\tconst error = reduceError(e);\n\t\treturn Response.json(error, {\n\t\t\tstatus: 500,\n\t\t\theaders: { \"MF-Experimental-Error-Stack\": \"true\" },\n\t\t});\n\t}\n};\n\nexport default jsonError;\n", "\t\t\t\timport worker, * as OTHER_EXPORTS from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\timport * as __MIDDLEWARE_0__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts\";\nimport * as __MIDDLEWARE_1__ from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts\";\n\n\t\t\t\texport * from \"/Users/zer/Documents/lab/perso/clicky/worker/src/index.ts\";\n\t\t\t\tconst MIDDLEWARE_TEST_INJECT = \"__INJECT_FOR_TESTING_WRANGLER_MIDDLEWARE__\";\n\t\t\t\texport const __INTERNAL_WRANGLER_MIDDLEWARE__ = [\n\t\t\t\t\t\n\t\t\t\t\t__MIDDLEWARE_0__.default,__MIDDLEWARE_1__.default\n\t\t\t\t]\n\t\t\t\texport default worker;", "export type Awaitable = T | Promise;\n// TODO: allow dispatching more events?\nexport type Dispatcher = (\n\ttype: \"scheduled\",\n\tinit: { cron?: string }\n) => Awaitable;\n\nexport type IncomingRequest = Request<\n\tunknown,\n\tIncomingRequestCfProperties\n>;\n\nexport interface MiddlewareContext {\n\tdispatch: Dispatcher;\n\tnext(request: IncomingRequest, env: any): Awaitable;\n}\n\nexport type Middleware = (\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tmiddlewareCtx: MiddlewareContext\n) => Awaitable;\n\nconst __facade_middleware__: Middleware[] = [];\n\n// The register functions allow for the insertion of one or many middleware,\n// We register internal middleware first in the stack, but have no way of controlling\n// the order that addMiddleware is run in service workers so need an internal function.\nexport function __facade_register__(...args: (Middleware | Middleware[])[]) {\n\t__facade_middleware__.push(...args.flat());\n}\nexport function __facade_registerInternal__(\n\t...args: (Middleware | Middleware[])[]\n) {\n\t__facade_middleware__.unshift(...args.flat());\n}\n\nfunction __facade_invokeChain__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tmiddlewareChain: Middleware[]\n): Awaitable {\n\tconst [head, ...tail] = middlewareChain;\n\tconst middlewareCtx: MiddlewareContext = {\n\t\tdispatch,\n\t\tnext(newRequest, newEnv) {\n\t\t\treturn __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);\n\t\t},\n\t};\n\treturn head(request, env, ctx, middlewareCtx);\n}\n\nexport function __facade_invoke__(\n\trequest: IncomingRequest,\n\tenv: any,\n\tctx: ExecutionContext,\n\tdispatch: Dispatcher,\n\tfinalMiddleware: Middleware\n): Awaitable {\n\treturn __facade_invokeChain__(request, env, ctx, dispatch, [\n\t\t...__facade_middleware__,\n\t\tfinalMiddleware,\n\t]);\n}\n", "// This loads all middlewares exposed on the middleware object and then starts\n// the invocation chain. The big idea is that we can add these to the middleware\n// export dynamically through wrangler, or we can potentially let users directly\n// add them as a sort of \"plugin\" system.\n\nimport ENTRY, { __INTERNAL_WRANGLER_MIDDLEWARE__ } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js\";\nimport { __facade_invoke__, __facade_register__, Dispatcher } from \"/Users/zer/Documents/lab/perso/clicky/worker/node_modules/wrangler/templates/middleware/common.ts\";\nimport type { WorkerEntrypointConstructor } from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js\";\n\n// Preserve all the exports from the worker\nexport * from \"/Users/zer/Documents/lab/perso/clicky/worker/.wrangler/tmp/bundle-OtdA08/middleware-insertion-facade.js\";\n\nclass __Facade_ScheduledController__ implements ScheduledController {\n\treadonly #noRetry: ScheduledController[\"noRetry\"];\n\n\tconstructor(\n\t\treadonly scheduledTime: number,\n\t\treadonly cron: string,\n\t\tnoRetry: ScheduledController[\"noRetry\"]\n\t) {\n\t\tthis.#noRetry = noRetry;\n\t}\n\n\tnoRetry() {\n\t\tif (!(this instanceof __Facade_ScheduledController__)) {\n\t\t\tthrow new TypeError(\"Illegal invocation\");\n\t\t}\n\t\t// Need to call native method immediately in case uncaught error thrown\n\t\tthis.#noRetry();\n\t}\n}\n\nfunction wrapExportedHandler(worker: ExportedHandler): ExportedHandler {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn worker;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\tconst fetchDispatcher: ExportedHandlerFetchHandler = function (\n\t\trequest,\n\t\tenv,\n\t\tctx\n\t) {\n\t\tif (worker.fetch === undefined) {\n\t\t\tthrow new Error(\"Handler does not export a fetch() function.\");\n\t\t}\n\t\treturn worker.fetch(request, env, ctx);\n\t};\n\n\treturn {\n\t\t...worker,\n\t\tfetch(request, env, ctx) {\n\t\t\tconst dispatcher: Dispatcher = function (type, init) {\n\t\t\t\tif (type === \"scheduled\" && worker.scheduled !== undefined) {\n\t\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\t\tDate.now(),\n\t\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t\t() => {}\n\t\t\t\t\t);\n\t\t\t\t\treturn worker.scheduled(controller, env, ctx);\n\t\t\t\t}\n\t\t\t};\n\t\t\treturn __facade_invoke__(request, env, ctx, dispatcher, fetchDispatcher);\n\t\t},\n\t};\n}\n\nfunction wrapWorkerEntrypoint(\n\tklass: WorkerEntrypointConstructor\n): WorkerEntrypointConstructor {\n\t// If we don't have any middleware defined, just return the handler as is\n\tif (\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__ === undefined ||\n\t\t__INTERNAL_WRANGLER_MIDDLEWARE__.length === 0\n\t) {\n\t\treturn klass;\n\t}\n\t// Otherwise, register all middleware once\n\tfor (const middleware of __INTERNAL_WRANGLER_MIDDLEWARE__) {\n\t\t__facade_register__(middleware);\n\t}\n\n\t// `extend`ing `klass` here so other RPC methods remain callable\n\treturn class extends klass {\n\t\t#fetchDispatcher: ExportedHandlerFetchHandler> = (\n\t\t\trequest,\n\t\t\tenv,\n\t\t\tctx\n\t\t) => {\n\t\t\tthis.env = env;\n\t\t\tthis.ctx = ctx;\n\t\t\tif (super.fetch === undefined) {\n\t\t\t\tthrow new Error(\"Entrypoint class does not define a fetch() function.\");\n\t\t\t}\n\t\t\treturn super.fetch(request);\n\t\t};\n\n\t\t#dispatcher: Dispatcher = (type, init) => {\n\t\t\tif (type === \"scheduled\" && super.scheduled !== undefined) {\n\t\t\t\tconst controller = new __Facade_ScheduledController__(\n\t\t\t\t\tDate.now(),\n\t\t\t\t\tinit.cron ?? \"\",\n\t\t\t\t\t() => {}\n\t\t\t\t);\n\t\t\t\treturn super.scheduled(controller);\n\t\t\t}\n\t\t};\n\n\t\tfetch(request: Request) {\n\t\t\treturn __facade_invoke__(\n\t\t\t\trequest,\n\t\t\t\tthis.env,\n\t\t\t\tthis.ctx,\n\t\t\t\tthis.#dispatcher,\n\t\t\t\tthis.#fetchDispatcher\n\t\t\t);\n\t\t}\n\t};\n}\n\nlet WRAPPED_ENTRY: ExportedHandler | WorkerEntrypointConstructor | undefined;\nif (typeof ENTRY === \"object\") {\n\tWRAPPED_ENTRY = wrapExportedHandler(ENTRY);\n} else if (typeof ENTRY === \"function\") {\n\tWRAPPED_ENTRY = wrapWorkerEntrypoint(ENTRY);\n}\nexport default WRAPPED_ENTRY;\n"], - "mappings": "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", - "names": [] -} From 13a5f127f90fd60b14f36fc595dbbb09a3a56788 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 18:30:15 +0200 Subject: [PATCH 10/20] feat: update Cloudflare Worker URLs for AssemblyAI and CompanionManager to support local development --- leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift | 4 ++++ leanring-buddy/CompanionManager.swift | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift b/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift index 29bde66b..25908819 100644 --- a/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift +++ b/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift @@ -19,7 +19,11 @@ struct AssemblyAIStreamingTranscriptionProviderError: LocalizedError { final class AssemblyAIStreamingTranscriptionProvider: BuddyTranscriptionProvider { /// URL for the Cloudflare Worker endpoint that returns a short-lived /// AssemblyAI streaming token. The real API key never leaves the server. + #if DEBUG private static let tokenProxyURL = "http://localhost:8787/transcribe-token" + #else + private static let tokenProxyURL = "https://your-worker-name.your-subdomain.workers.dev/transcribe-token" + #endif let displayName = "AssemblyAI" let requiresSpeechRecognitionPermission = false diff --git a/leanring-buddy/CompanionManager.swift b/leanring-buddy/CompanionManager.swift index 258d2b2f..b4dcea78 100644 --- a/leanring-buddy/CompanionManager.swift +++ b/leanring-buddy/CompanionManager.swift @@ -70,7 +70,11 @@ final class CompanionManager: ObservableObject { /// Base URL for the Cloudflare Worker proxy. All API requests route /// through this so keys never ship in the app binary. + #if DEBUG private static let workerBaseURL = "http://localhost:8787" + #else + private static let workerBaseURL = "https://your-worker-name.your-subdomain.workers.dev" + #endif private lazy var claudeAPI: ClaudeAPI = { return ClaudeAPI(proxyURL: "\(Self.workerBaseURL)/chat", model: selectedModel) From 9e9ae0fcb307d7db328f6b8e9ba92d01f3ebc9d7 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 18:31:08 +0200 Subject: [PATCH 11/20] chore: update .gitignore to exclude xcuserdata directory in .wrangler --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index dd28144e..2576c411 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ coding-plans/ /worker/wrangler.toml -/worker/.wrangler/ \ No newline at end of file +/worker/.wrangler/xcuserdata/ From 4286396c8e30f641f1509ba971c58d05c51c985b Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 18:33:42 +0200 Subject: [PATCH 12/20] chore: remove xcuserdata from repository --- .../xcschemes/xcschememanagement.plist | 14 -------------- .../xcschemes/xcschememanagement.plist | 14 -------------- .../xcschemes/xcschememanagement.plist | 14 -------------- 3 files changed, 42 deletions(-) delete mode 100644 leanring-buddy.xcodeproj/xcuserdata/julianalvarez.xcuserdatad/xcschemes/xcschememanagement.plist delete mode 100644 leanring-buddy.xcodeproj/xcuserdata/thorfinn.xcuserdatad/xcschemes/xcschememanagement.plist delete mode 100644 leanring-buddy.xcodeproj/xcuserdata/zer.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/leanring-buddy.xcodeproj/xcuserdata/julianalvarez.xcuserdatad/xcschemes/xcschememanagement.plist b/leanring-buddy.xcodeproj/xcuserdata/julianalvarez.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 6c13490a..00000000 --- a/leanring-buddy.xcodeproj/xcuserdata/julianalvarez.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - SchemeUserState - - leanring-buddy.xcscheme_^#shared#^_ - - orderHint - 0 - - - - diff --git a/leanring-buddy.xcodeproj/xcuserdata/thorfinn.xcuserdatad/xcschemes/xcschememanagement.plist b/leanring-buddy.xcodeproj/xcuserdata/thorfinn.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 6c13490a..00000000 --- a/leanring-buddy.xcodeproj/xcuserdata/thorfinn.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - SchemeUserState - - leanring-buddy.xcscheme_^#shared#^_ - - orderHint - 0 - - - - diff --git a/leanring-buddy.xcodeproj/xcuserdata/zer.xcuserdatad/xcschemes/xcschememanagement.plist b/leanring-buddy.xcodeproj/xcuserdata/zer.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 6c13490a..00000000 --- a/leanring-buddy.xcodeproj/xcuserdata/zer.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - SchemeUserState - - leanring-buddy.xcscheme_^#shared#^_ - - orderHint - 0 - - - - From 521a74c8fccea8abb7855633d95c4eeb646181c5 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 18:38:02 +0200 Subject: [PATCH 13/20] chore: update .gitignore to include xcuserdata and its related files --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2576c411..0f0a986b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,7 @@ coding-plans/ /worker/wrangler.toml -/worker/.wrangler/xcuserdata/ +/worker/.wrangler/ + +xcuserdata/ +*.xcuserdatad/ From f81896e1da03a9ecd1f8cdd3c28f7fc8e49272b4 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 18:55:04 +0200 Subject: [PATCH 14/20] feat: add local LLM API key support and update OpenAIAPI initialization --- leanring-buddy/CompanionManager.swift | 13 +++- leanring-buddy/CompanionPanelView.swift | 80 +++++++++++++++++++++---- leanring-buddy/OpenAIAPI.swift | 4 +- 3 files changed, 84 insertions(+), 13 deletions(-) diff --git a/leanring-buddy/CompanionManager.swift b/leanring-buddy/CompanionManager.swift index b4dcea78..b855e172 100644 --- a/leanring-buddy/CompanionManager.swift +++ b/leanring-buddy/CompanionManager.swift @@ -81,7 +81,7 @@ final class CompanionManager: ObservableObject { }() private lazy var openAIAPI: OpenAIAPI = { - return OpenAIAPI(model: selectedModel) + return OpenAIAPI(apiKey: lmStudioAPIKey, model: selectedModel) }() private lazy var elevenLabsTTSClient: ElevenLabsTTSClient = { @@ -118,6 +118,14 @@ final class CompanionManager: ObservableObject { /// The Claude model used for voice responses. Persisted to UserDefaults. @Published var selectedModel: String = UserDefaults.standard.string(forKey: "selectedGemmaModel") ?? "gemma-4-e4b-uncensored-hauhaucs-aggressive" + /// The API key for local LLM requests. Persisted to UserDefaults. + @Published var lmStudioAPIKey: String = UserDefaults.standard.string(forKey: "lmStudioAPIKey") ?? "" { + didSet { + UserDefaults.standard.set(lmStudioAPIKey, forKey: "lmStudioAPIKey") + openAIAPI.apiKey = lmStudioAPIKey + } + } + /// List of available models fetched from LM Studio API @Published var availableModels: [String] = [] @@ -125,6 +133,9 @@ final class CompanionManager: ObservableObject { guard let url = URL(string: "http://127.0.0.1:1234/v1/models") else { return } var request = URLRequest(url: url) request.timeoutInterval = 3 + if !lmStudioAPIKey.isEmpty { + request.setValue("Bearer \(lmStudioAPIKey)", forHTTPHeaderField: "Authorization") + } Task { do { diff --git a/leanring-buddy/CompanionPanelView.swift b/leanring-buddy/CompanionPanelView.swift index c7f06590..b339457b 100644 --- a/leanring-buddy/CompanionPanelView.swift +++ b/leanring-buddy/CompanionPanelView.swift @@ -13,6 +13,8 @@ import SwiftUI struct CompanionPanelView: View { @ObservedObject var companionManager: CompanionManager @State private var emailInput: String = "" + @State private var isShowingLocalAPIKeyField: Bool = false + @State private var isAPIKeyVisible: Bool = false var body: some View { VStack(alignment: .leading, spacing: 0) { @@ -599,16 +601,29 @@ struct CompanionPanelView: View { // MARK: - Model Picker private var modelPickerRow: some View { - HStack { - Text("Model") - .font(.system(size: 13, weight: .medium)) - .foregroundColor(DS.Colors.textSecondary) - - Spacer() - - Menu { - // Section for Local LM Studio Models - Section(header: Text("LM Studio (Local)")) { + VStack(spacing: 8) { + HStack { + Text("Model") + .font(.system(size: 13, weight: .medium)) + .foregroundColor(DS.Colors.textSecondary) + + Button(action: { + withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { + isShowingLocalAPIKeyField.toggle() + } + }) { + Image(systemName: "gear") + .font(.system(size: 11)) + .foregroundColor(isShowingLocalAPIKeyField ? DS.Colors.textPrimary : DS.Colors.textTertiary) + } + .buttonStyle(.plain) + .pointerCursor() + + Spacer() + + Menu { + // Section for Local LM Studio Models + Section(header: Text("LM Studio (Local)")) { if companionManager.availableModels.isEmpty { Text("No models found").disabled(true) } else { @@ -680,6 +695,51 @@ struct CompanionPanelView: View { .menuStyle(.borderlessButton) .frame(maxWidth: 200) } + + if isShowingLocalAPIKeyField { + HStack { + HStack(spacing: 8) { + if isAPIKeyVisible { + TextField("", text: Binding( + get: { companionManager.lmStudioAPIKey }, + set: { companionManager.lmStudioAPIKey = $0 } + ), prompt: Text("LM Studio API Key (leave empty if none)").foregroundColor(.white.opacity(0.4))) + .textFieldStyle(PlainTextFieldStyle()) + .font(.system(size: 11)) + .foregroundColor(.white) + } else { + SecureField("", text: Binding( + get: { companionManager.lmStudioAPIKey }, + set: { companionManager.lmStudioAPIKey = $0 } + ), prompt: Text("LM Studio API Key (leave empty if none)").foregroundColor(.white.opacity(0.4))) + .textFieldStyle(PlainTextFieldStyle()) + .font(.system(size: 11)) + .foregroundColor(.white) + } + + Button(action: { + isAPIKeyVisible.toggle() + }) { + Image(systemName: isAPIKeyVisible ? "eye.slash.fill" : "eye.fill") + .font(.system(size: 11)) + .foregroundColor(DS.Colors.textTertiary) + } + .buttonStyle(.plain) + .pointerCursor() + } + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(Color.white.opacity(0.06)) + .cornerRadius(6) + .overlay( + RoundedRectangle(cornerRadius: 6, style: .continuous) + .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) + ) + } + .transition(.opacity.combined(with: .move(edge: .top))) + .padding(.top, 4) + } + } .padding(.vertical, 4) } diff --git a/leanring-buddy/OpenAIAPI.swift b/leanring-buddy/OpenAIAPI.swift index 46031e66..ac251e7b 100644 --- a/leanring-buddy/OpenAIAPI.swift +++ b/leanring-buddy/OpenAIAPI.swift @@ -7,12 +7,12 @@ import Foundation /// OpenAI API helper for vision analysis class OpenAIAPI { - private let apiKey: String + var apiKey: String private let apiURL: URL var model: String private let session: URLSession - init(apiKey: String = "sk-lm-aAxaxZte:mOc432tNRd7CWCOh57g3", model: String = "gemma-4-e4b-uncensored-hauhaucs-aggressive") { + init(apiKey: String = "", model: String = "gemma-4-e4b-uncensored-hauhaucs-aggressive") { self.apiKey = apiKey self.apiURL = URL(string: "http://127.0.0.1:1234/v1/chat/completions")! self.model = model From fe45ffd99f46d55a612e92bfd9479bd6625fdfce Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 18:56:44 +0200 Subject: [PATCH 15/20] fix: update prompt text color for LM Studio API Key fields --- leanring-buddy/CompanionPanelView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/leanring-buddy/CompanionPanelView.swift b/leanring-buddy/CompanionPanelView.swift index b339457b..74ecf82b 100644 --- a/leanring-buddy/CompanionPanelView.swift +++ b/leanring-buddy/CompanionPanelView.swift @@ -703,7 +703,7 @@ struct CompanionPanelView: View { TextField("", text: Binding( get: { companionManager.lmStudioAPIKey }, set: { companionManager.lmStudioAPIKey = $0 } - ), prompt: Text("LM Studio API Key (leave empty if none)").foregroundColor(.white.opacity(0.4))) + ), prompt: Text("LM Studio API Key (leave empty if none)").foregroundColor(.white)) .textFieldStyle(PlainTextFieldStyle()) .font(.system(size: 11)) .foregroundColor(.white) @@ -711,7 +711,7 @@ struct CompanionPanelView: View { SecureField("", text: Binding( get: { companionManager.lmStudioAPIKey }, set: { companionManager.lmStudioAPIKey = $0 } - ), prompt: Text("LM Studio API Key (leave empty if none)").foregroundColor(.white.opacity(0.4))) + ), prompt: Text("LM Studio API Key (leave empty if none)").foregroundColor(.white)) .textFieldStyle(PlainTextFieldStyle()) .font(.system(size: 11)) .foregroundColor(.white) From 4e5db33d7e728c1bd2bdc78975d802067365e81b Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 19:09:43 +0200 Subject: [PATCH 16/20] feat: enhance LM Studio API Key input field with placeholder and improved visibility handling --- leanring-buddy/CompanionPanelView.swift | 40 +++++++++++++++---------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/leanring-buddy/CompanionPanelView.swift b/leanring-buddy/CompanionPanelView.swift index 74ecf82b..7c5ad982 100644 --- a/leanring-buddy/CompanionPanelView.swift +++ b/leanring-buddy/CompanionPanelView.swift @@ -699,22 +699,30 @@ struct CompanionPanelView: View { if isShowingLocalAPIKeyField { HStack { HStack(spacing: 8) { - if isAPIKeyVisible { - TextField("", text: Binding( - get: { companionManager.lmStudioAPIKey }, - set: { companionManager.lmStudioAPIKey = $0 } - ), prompt: Text("LM Studio API Key (leave empty if none)").foregroundColor(.white)) - .textFieldStyle(PlainTextFieldStyle()) - .font(.system(size: 11)) - .foregroundColor(.white) - } else { - SecureField("", text: Binding( - get: { companionManager.lmStudioAPIKey }, - set: { companionManager.lmStudioAPIKey = $0 } - ), prompt: Text("LM Studio API Key (leave empty if none)").foregroundColor(.white)) - .textFieldStyle(PlainTextFieldStyle()) - .font(.system(size: 11)) - .foregroundColor(.white) + ZStack(alignment: .leading) { + if companionManager.lmStudioAPIKey.isEmpty { + Text("LM Studio API Key (leave empty if none)") + .font(.system(size: 11)) + .foregroundColor(.white.opacity(0.6)) + } + + if isAPIKeyVisible { + TextField("", text: Binding( + get: { companionManager.lmStudioAPIKey }, + set: { companionManager.lmStudioAPIKey = $0 } + )) + .textFieldStyle(PlainTextFieldStyle()) + .font(.system(size: 11)) + .foregroundColor(.white) + } else { + SecureField("", text: Binding( + get: { companionManager.lmStudioAPIKey }, + set: { companionManager.lmStudioAPIKey = $0 } + )) + .textFieldStyle(PlainTextFieldStyle()) + .font(.system(size: 11)) + .foregroundColor(.white) + } } Button(action: { From e83c0e6aee99262cc480015c8dc8b711f1043217 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 19:36:27 +0200 Subject: [PATCH 17/20] feat: update default model initialization to empty string for flexibility --- leanring-buddy/CompanionManager.swift | 2 +- leanring-buddy/OpenAIAPI.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/leanring-buddy/CompanionManager.swift b/leanring-buddy/CompanionManager.swift index b855e172..19250447 100644 --- a/leanring-buddy/CompanionManager.swift +++ b/leanring-buddy/CompanionManager.swift @@ -116,7 +116,7 @@ final class CompanionManager: ObservableObject { @Published private(set) var isOverlayVisible: Bool = false /// The Claude model used for voice responses. Persisted to UserDefaults. - @Published var selectedModel: String = UserDefaults.standard.string(forKey: "selectedGemmaModel") ?? "gemma-4-e4b-uncensored-hauhaucs-aggressive" + @Published var selectedModel: String = UserDefaults.standard.string(forKey: "selectedGemmaModel") ?? "" /// The API key for local LLM requests. Persisted to UserDefaults. @Published var lmStudioAPIKey: String = UserDefaults.standard.string(forKey: "lmStudioAPIKey") ?? "" { diff --git a/leanring-buddy/OpenAIAPI.swift b/leanring-buddy/OpenAIAPI.swift index ac251e7b..951ca71e 100644 --- a/leanring-buddy/OpenAIAPI.swift +++ b/leanring-buddy/OpenAIAPI.swift @@ -12,7 +12,7 @@ class OpenAIAPI { var model: String private let session: URLSession - init(apiKey: String = "", model: String = "gemma-4-e4b-uncensored-hauhaucs-aggressive") { + init(apiKey: String = "", model: String = "") { self.apiKey = apiKey self.apiURL = URL(string: "http://127.0.0.1:1234/v1/chat/completions")! self.model = model From c618820fe3da0c6e6da786b6191966f931450c50 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 20:33:48 +0200 Subject: [PATCH 18/20] feat: refactor API proxy URL handling and add WorkerEnvironment for dynamic URL detection --- ...mblyAIStreamingTranscriptionProvider.swift | 11 +--- leanring-buddy/ClaudeAPI.swift | 12 ++--- leanring-buddy/CompanionManager.swift | 27 +++++----- leanring-buddy/ElevenLabsTTSClient.swift | 7 +-- leanring-buddy/WorkerEnvironment.swift | 52 +++++++++++++++++++ 5 files changed, 77 insertions(+), 32 deletions(-) create mode 100644 leanring-buddy/WorkerEnvironment.swift diff --git a/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift b/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift index 25908819..8d34845b 100644 --- a/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift +++ b/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift @@ -17,14 +17,6 @@ struct AssemblyAIStreamingTranscriptionProviderError: LocalizedError { } final class AssemblyAIStreamingTranscriptionProvider: BuddyTranscriptionProvider { - /// URL for the Cloudflare Worker endpoint that returns a short-lived - /// AssemblyAI streaming token. The real API key never leaves the server. - #if DEBUG - private static let tokenProxyURL = "http://localhost:8787/transcribe-token" - #else - private static let tokenProxyURL = "https://your-worker-name.your-subdomain.workers.dev/transcribe-token" - #endif - let displayName = "AssemblyAI" let requiresSpeechRecognitionPermission = false @@ -63,7 +55,8 @@ final class AssemblyAIStreamingTranscriptionProvider: BuddyTranscriptionProvider /// Calls the Cloudflare Worker to get a short-lived AssemblyAI token. private func fetchTemporaryToken() async throws -> String { - var request = URLRequest(url: URL(string: Self.tokenProxyURL)!) + let baseURL = await WorkerEnvironment.shared.getBaseURL() + var request = URLRequest(url: URL(string: "\(baseURL)/transcribe-token")!) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.data(for: request) diff --git a/leanring-buddy/ClaudeAPI.swift b/leanring-buddy/ClaudeAPI.swift index 0c7070b5..39153c60 100644 --- a/leanring-buddy/ClaudeAPI.swift +++ b/leanring-buddy/ClaudeAPI.swift @@ -10,12 +10,13 @@ class ClaudeAPI { private static let tlsWarmupLock = NSLock() private static var hasStartedTLSWarmup = false - private let apiURL: URL + var proxyURL: String + private var apiURL: URL { URL(string: proxyURL)! } var model: String private let session: URLSession init(proxyURL: String, model: String = "claude-sonnet-4-6") { - self.apiURL = URL(string: proxyURL)! + self.proxyURL = proxyURL self.model = model // Use .default instead of .ephemeral so TLS session tickets are cached. @@ -29,11 +30,6 @@ class ClaudeAPI { config.urlCache = nil config.httpCookieStorage = nil self.session = URLSession(configuration: config) - - // Fire a lightweight HEAD request in the background to pre-establish the TLS - // connection. This caches the TLS session ticket so the first real API call - // (which carries a large image payload) doesn't need a cold TLS handshake. - warmUpTLSConnectionIfNeeded() } private func makeAPIRequest() -> URLRequest { @@ -63,7 +59,7 @@ class ClaudeAPI { /// Sends a no-op HEAD request to the API host to establish and cache a TLS session. /// Failures are silently ignored — this is purely an optimization. - private func warmUpTLSConnectionIfNeeded() { + func warmUpTLSConnectionIfNeeded() { Self.tlsWarmupLock.lock() let shouldStartTLSWarmup = !Self.hasStartedTLSWarmup if shouldStartTLSWarmup { diff --git a/leanring-buddy/CompanionManager.swift b/leanring-buddy/CompanionManager.swift index 19250447..bf8c4404 100644 --- a/leanring-buddy/CompanionManager.swift +++ b/leanring-buddy/CompanionManager.swift @@ -70,14 +70,9 @@ final class CompanionManager: ObservableObject { /// Base URL for the Cloudflare Worker proxy. All API requests route /// through this so keys never ship in the app binary. - #if DEBUG - private static let workerBaseURL = "http://localhost:8787" - #else - private static let workerBaseURL = "https://your-worker-name.your-subdomain.workers.dev" - #endif - private lazy var claudeAPI: ClaudeAPI = { - return ClaudeAPI(proxyURL: "\(Self.workerBaseURL)/chat", model: selectedModel) + let defaultURL = "https://your-worker-name.your-subdomain.workers.dev" // Default placeholder, will be dynamically updated in start() + return ClaudeAPI(proxyURL: "\(defaultURL)/chat", model: selectedModel) }() private lazy var openAIAPI: OpenAIAPI = { @@ -85,7 +80,8 @@ final class CompanionManager: ObservableObject { }() private lazy var elevenLabsTTSClient: ElevenLabsTTSClient = { - return ElevenLabsTTSClient(proxyURL: "\(Self.workerBaseURL)/tts") + let defaultURL = "https://your-worker-name.your-subdomain.workers.dev" + return ElevenLabsTTSClient(proxyURL: "\(defaultURL)/tts") }() /// Conversation history so Claude remembers prior exchanges within a session. @@ -226,6 +222,17 @@ final class CompanionManager: ObservableObject { } func start() { + Task { + let baseURL = await WorkerEnvironment.shared.getBaseURL() + self.claudeAPI.proxyURL = "\(baseURL)/chat" + self.elevenLabsTTSClient.proxyURL = "\(baseURL)/tts" + + // Eagerly touch APIs so their TLS warmup handshakes complete + // well before the onboarding demo fires at ~40s into the video. + self.claudeAPI.warmUpTLSConnectionIfNeeded() + _ = self.openAIAPI + } + fetchAvailableModels() refreshAllPermissions() print("🔑 Clicky start — accessibility: \(hasAccessibilityPermission), screen: \(hasScreenRecordingPermission), mic: \(hasMicrophonePermission), screenContent: \(hasScreenContentPermission), onboarded: \(hasCompletedOnboarding)") @@ -233,10 +240,6 @@ final class CompanionManager: ObservableObject { bindVoiceStateObservation() bindAudioPowerLevel() bindShortcutTransitions() - // Eagerly touch APIs so their TLS warmup handshakes complete - // well before the onboarding demo fires at ~40s into the video. - _ = claudeAPI - _ = openAIAPI // If the user already completed onboarding AND all permissions are // still granted, show the cursor overlay immediately. If permissions diff --git a/leanring-buddy/ElevenLabsTTSClient.swift b/leanring-buddy/ElevenLabsTTSClient.swift index 35545c9d..f93752ea 100644 --- a/leanring-buddy/ElevenLabsTTSClient.swift +++ b/leanring-buddy/ElevenLabsTTSClient.swift @@ -12,7 +12,8 @@ import Foundation @MainActor final class ElevenLabsTTSClient { - private let proxyURL: URL + var proxyURL: String + private var apiURL: URL { URL(string: proxyURL)! } private let session: URLSession /// The audio player for the current TTS playback. Kept alive so the @@ -20,7 +21,7 @@ final class ElevenLabsTTSClient { private var audioPlayer: AVAudioPlayer? init(proxyURL: String) { - self.proxyURL = URL(string: proxyURL)! + self.proxyURL = proxyURL let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForRequest = 30 @@ -31,7 +32,7 @@ final class ElevenLabsTTSClient { /// Sends `text` to ElevenLabs TTS and plays the resulting audio. /// Throws on network or decoding errors. Cancellation-safe. func speakText(_ text: String) async throws { - var request = URLRequest(url: proxyURL) + var request = URLRequest(url: apiURL) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("audio/mpeg", forHTTPHeaderField: "Accept") diff --git a/leanring-buddy/WorkerEnvironment.swift b/leanring-buddy/WorkerEnvironment.swift new file mode 100644 index 00000000..88fbaa91 --- /dev/null +++ b/leanring-buddy/WorkerEnvironment.swift @@ -0,0 +1,52 @@ +// +// WorkerEnvironment.swift +// leanring-buddy +// +// Automatically detects if the local development Cloudflare Worker is running. +// If yes, it connects to localhost out-of-the-box. Otherwise, it falls back to production. +// + +import Foundation + +actor WorkerEnvironment { + static let shared = WorkerEnvironment() + + private var cachedBaseURL: String? + + // Replace this with your actual production Worker URL if known + private let defaultProdURL = "https://your-worker-name.your-subdomain.workers.dev" + + func getBaseURL() async -> String { + if let cached = cachedBaseURL { + return cached + } + + if let saved = UserDefaults.standard.string(forKey: "workerBaseURL"), !saved.isEmpty { + cachedBaseURL = saved + return saved // Allow optional user override + } + + let localURL = "http://localhost:8787" + var request = URLRequest(url: URL(string: localURL)!) + request.httpMethod = "OPTIONS" // Quick lightweight pre-flight + request.timeoutInterval = 0.5 // Fail very fast if it's not running + + do { + let (_, response) = try await URLSession.shared.data(for: request) + if (response as? HTTPURLResponse) != nil { + print("🌐 Auto-detected local worker. Using \(localURL)") + cachedBaseURL = localURL + return localURL + } + } catch { + print("🌐 Local worker unreachable. Falling back to prod: \(defaultProdURL)") + } + + cachedBaseURL = defaultProdURL + return defaultProdURL + } + + func resetCache() { + cachedBaseURL = nil + } +} \ No newline at end of file From 344d8688fed6097b6f3f7c3959ae19466d1444e5 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Wed, 8 Apr 2026 23:49:49 +0200 Subject: [PATCH 19/20] feat: add language code support to transcription providers and UI for language selection + add second panel for configuration model and language --- .../AppleSpeechTranscriptionProvider.swift | 6 +- ...mblyAIStreamingTranscriptionProvider.swift | 17 +- leanring-buddy/BuddyDictationManager.swift | 2 + .../BuddyTranscriptionProvider.swift | 1 + leanring-buddy/CompanionManager.swift | 15 +- leanring-buddy/CompanionPanelView.swift | 255 ++++++++++++------ .../OpenAIAudioTranscriptionProvider.swift | 7 +- 7 files changed, 208 insertions(+), 95 deletions(-) diff --git a/leanring-buddy/AppleSpeechTranscriptionProvider.swift b/leanring-buddy/AppleSpeechTranscriptionProvider.swift index 600594fa..51ed9713 100644 --- a/leanring-buddy/AppleSpeechTranscriptionProvider.swift +++ b/leanring-buddy/AppleSpeechTranscriptionProvider.swift @@ -24,12 +24,13 @@ final class AppleSpeechTranscriptionProvider: BuddyTranscriptionProvider { let unavailableExplanation: String? = nil func startStreamingSession( + languageCode: String, keyterms: [String], onTranscriptUpdate: @escaping (String) -> Void, onFinalTranscriptReady: @escaping (String) -> Void, onError: @escaping (Error) -> Void ) async throws -> any BuddyStreamingTranscriptionSession { - guard let speechRecognizer = Self.makeBestAvailableSpeechRecognizer() else { + guard let speechRecognizer = Self.makeBestAvailableSpeechRecognizer(languageCode: languageCode) else { throw AppleSpeechTranscriptionProviderError(message: "dictation is not available on this mac.") } @@ -41,8 +42,9 @@ final class AppleSpeechTranscriptionProvider: BuddyTranscriptionProvider { ) } - private static func makeBestAvailableSpeechRecognizer() -> SFSpeechRecognizer? { + private static func makeBestAvailableSpeechRecognizer(languageCode: String) -> SFSpeechRecognizer? { let preferredLocales = [ + Locale(identifier: languageCode), Locale.autoupdatingCurrent, Locale(identifier: "en-US") ] diff --git a/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift b/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift index 8d34845b..a8809015 100644 --- a/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift +++ b/leanring-buddy/AssemblyAIStreamingTranscriptionProvider.swift @@ -30,6 +30,7 @@ final class AssemblyAIStreamingTranscriptionProvider: BuddyTranscriptionProvider private let sharedWebSocketURLSession = URLSession(configuration: .default) func startStreamingSession( + languageCode: String, keyterms: [String], onTranscriptUpdate: @escaping (String) -> Void, onFinalTranscriptReady: @escaping (String) -> Void, @@ -44,6 +45,7 @@ final class AssemblyAIStreamingTranscriptionProvider: BuddyTranscriptionProvider temporaryToken: temporaryToken, urlSession: sharedWebSocketURLSession, keyterms: keyterms, + languageCode: languageCode, onTranscriptUpdate: onTranscriptUpdate, onFinalTranscriptReady: onFinalTranscriptReady, onError: onError @@ -114,6 +116,7 @@ private final class AssemblyAIStreamingTranscriptionSession: NSObject, BuddyStre private let apiKey: String? private let temporaryToken: String? private let keyterms: [String] + private let languageCode: String private let onTranscriptUpdate: (String) -> Void private let onFinalTranscriptReady: (String) -> Void private let onError: (Error) -> Void @@ -139,6 +142,7 @@ private final class AssemblyAIStreamingTranscriptionSession: NSObject, BuddyStre temporaryToken: String?, urlSession: URLSession, keyterms: [String], + languageCode: String, onTranscriptUpdate: @escaping (String) -> Void, onFinalTranscriptReady: @escaping (String) -> Void, onError: @escaping (Error) -> Void @@ -147,6 +151,7 @@ private final class AssemblyAIStreamingTranscriptionSession: NSObject, BuddyStre self.temporaryToken = temporaryToken self.urlSession = urlSession self.keyterms = keyterms + self.languageCode = languageCode self.onTranscriptUpdate = onTranscriptUpdate self.onFinalTranscriptReady = onFinalTranscriptReady self.onError = onError @@ -155,7 +160,8 @@ private final class AssemblyAIStreamingTranscriptionSession: NSObject, BuddyStre func open() async throws { let websocketURL = try Self.makeWebsocketURL( temporaryToken: temporaryToken, - keyterms: keyterms + keyterms: keyterms, + languageCode: languageCode ) var websocketRequest = URLRequest(url: websocketURL) @@ -433,7 +439,8 @@ private final class AssemblyAIStreamingTranscriptionSession: NSObject, BuddyStre private static func makeWebsocketURL( temporaryToken: String?, - keyterms: [String] + keyterms: [String], + languageCode: String ) throws -> URL { guard var websocketURLComponents = URLComponents(string: websocketBaseURLString) else { throw AssemblyAIStreamingTranscriptionProviderError( @@ -448,6 +455,12 @@ private final class AssemblyAIStreamingTranscriptionSession: NSObject, BuddyStre URLQueryItem(name: "speech_model", value: "u3-rt-pro") ] + if languageCode != "en" { + // AssemblyAI supports multiple language codes. + queryItems.append(URLQueryItem(name: "language_code", value: languageCode)) + // u3-rt-pro generally supports multi lingual. If any errors happen, we might need a different speech_model or drop it. + } + let normalizedKeyterms = keyterms .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } .filter { !$0.isEmpty } diff --git a/leanring-buddy/BuddyDictationManager.swift b/leanring-buddy/BuddyDictationManager.swift index 5bca2677..dc1152ef 100644 --- a/leanring-buddy/BuddyDictationManager.swift +++ b/leanring-buddy/BuddyDictationManager.swift @@ -517,7 +517,9 @@ final class BuddyDictationManager: NSObject, ObservableObject { print("🎙️ BuddyDictationManager: opening transcription provider \(transcriptionProvider.displayName)") + let languageCode = UserDefaults.standard.string(forKey: "selectedLanguageCode") ?? "en" let activeTranscriptionSession = try await transcriptionProvider.startStreamingSession( + languageCode: languageCode, keyterms: buildTranscriptionKeyterms(), onTranscriptUpdate: { [weak self] transcriptText in Task { @MainActor in diff --git a/leanring-buddy/BuddyTranscriptionProvider.swift b/leanring-buddy/BuddyTranscriptionProvider.swift index 0a75715d..800f7657 100644 --- a/leanring-buddy/BuddyTranscriptionProvider.swift +++ b/leanring-buddy/BuddyTranscriptionProvider.swift @@ -22,6 +22,7 @@ protocol BuddyTranscriptionProvider { var unavailableExplanation: String? { get } func startStreamingSession( + languageCode: String, keyterms: [String], onTranscriptUpdate: @escaping (String) -> Void, onFinalTranscriptReady: @escaping (String) -> Void, diff --git a/leanring-buddy/CompanionManager.swift b/leanring-buddy/CompanionManager.swift index bf8c4404..1bd45fb5 100644 --- a/leanring-buddy/CompanionManager.swift +++ b/leanring-buddy/CompanionManager.swift @@ -111,6 +111,13 @@ final class CompanionManager: ObservableObject { /// Used by the panel to show accurate status text ("Active" vs "Ready"). @Published private(set) var isOverlayVisible: Bool = false + /// The user's preferred spoken and transcribing language code (e.g. "en", "fr"). + @Published var selectedLanguageCode: String = UserDefaults.standard.string(forKey: "selectedLanguageCode") ?? "en" { + didSet { + UserDefaults.standard.set(selectedLanguageCode, forKey: "selectedLanguageCode") + } + } + /// The Claude model used for voice responses. Persisted to UserDefaults. @Published var selectedModel: String = UserDefaults.standard.string(forKey: "selectedGemmaModel") ?? "" @@ -674,7 +681,7 @@ final class CompanionManager: ObservableObject { if selectedModel.lowercased().contains("claude") { (fullResponseText, duration) = try await claudeAPI.analyzeImageStreaming( images: labeledImages, - systemPrompt: Self.companionVoiceResponseSystemPrompt, + systemPrompt: Self.companionVoiceResponseSystemPrompt + "\n\nCRITICAL: Respond EXCLUSIVELY in the following language code: \(selectedLanguageCode)", conversationHistory: historyForAPI, userPrompt: transcript, onTextChunk: { _ in @@ -684,7 +691,7 @@ final class CompanionManager: ObservableObject { } else { (fullResponseText, duration) = try await openAIAPI.analyzeImageStreaming( images: labeledImages, - systemPrompt: Self.companionVoiceResponseSystemPrompt, + systemPrompt: Self.companionVoiceResponseSystemPrompt + "\n\nCRITICAL: Respond EXCLUSIVELY in the following language code: \(selectedLanguageCode)", conversationHistory: historyForAPI, userPrompt: transcript, onTextChunk: { _ in @@ -1061,14 +1068,14 @@ final class CompanionManager: ObservableObject { if selectedModel.lowercased().contains("claude") { (fullResponseText, duration) = try await claudeAPI.analyzeImageStreaming( images: labeledImages, - systemPrompt: Self.onboardingDemoSystemPrompt, + systemPrompt: Self.onboardingDemoSystemPrompt + "\n\nCRITICAL: Respond EXCLUSIVELY in the following language code: \(selectedLanguageCode)", userPrompt: "look around my screen and find something interesting to point at", onTextChunk: { _ in } ) } else { (fullResponseText, duration) = try await openAIAPI.analyzeImageStreaming( images: labeledImages, - systemPrompt: Self.onboardingDemoSystemPrompt, + systemPrompt: Self.onboardingDemoSystemPrompt + "\n\nCRITICAL: Respond EXCLUSIVELY in the following language code: \(selectedLanguageCode)", userPrompt: "look around my screen and find something interesting to point at", onTextChunk: { _ in } ) diff --git a/leanring-buddy/CompanionPanelView.swift b/leanring-buddy/CompanionPanelView.swift index 7c5ad982..8d235eff 100644 --- a/leanring-buddy/CompanionPanelView.swift +++ b/leanring-buddy/CompanionPanelView.swift @@ -13,8 +13,8 @@ import SwiftUI struct CompanionPanelView: View { @ObservedObject var companionManager: CompanionManager @State private var emailInput: String = "" - @State private var isShowingLocalAPIKeyField: Bool = false @State private var isAPIKeyVisible: Bool = false + @State private var isShowingSettings: Bool = false var body: some View { VStack(alignment: .leading, spacing: 0) { @@ -23,17 +23,92 @@ struct CompanionPanelView: View { .background(DS.Colors.borderSubtle) .padding(.horizontal, 16) - permissionsCopySection - .padding(.top, 16) + if isShowingSettings { + settingsView + } else { + mainContentView + } + + Spacer() + .frame(height: 12) + + Divider() + .background(DS.Colors.borderSubtle) .padding(.horizontal, 16) - if companionManager.hasCompletedOnboarding && companionManager.allPermissionsGranted { - Spacer() - .frame(height: 12) + footerSection // <-- This is now defined below + .padding(.horizontal, 16) + .padding(.vertical, 12) + } + .frame(width: 320) + .background(panelBackground) // <-- This is now defined below + } + + private var settingsView: some View { + VStack(alignment: .leading, spacing: 16) { + modelPickerRow + localAPIKeyRow + languagePickerRow + } + .padding(.horizontal, 16) + .padding(.top, 16) + } + + private var localAPIKeyRow: some View { + HStack { + HStack(spacing: 8) { + ZStack(alignment: .leading) { + if companionManager.lmStudioAPIKey.isEmpty { + Text("LM Studio API Key (leave empty if none)") + .font(.system(size: 11)) + .foregroundColor(.white.opacity(0.6)) + } + + if isAPIKeyVisible { + TextField("", text: Binding( + get: { companionManager.lmStudioAPIKey }, + set: { companionManager.lmStudioAPIKey = $0 } + )) + .textFieldStyle(PlainTextFieldStyle()) + .font(.system(size: 11)) + .foregroundColor(.white) + } else { + SecureField("", text: Binding( + get: { companionManager.lmStudioAPIKey }, + set: { companionManager.lmStudioAPIKey = $0 } + )) + .textFieldStyle(PlainTextFieldStyle()) + .font(.system(size: 11)) + .foregroundColor(.white) + } + } - modelPickerRow - .padding(.horizontal, 16) + Button(action: { + isAPIKeyVisible.toggle() + }) { + Image(systemName: isAPIKeyVisible ? "eye.slash.fill" : "eye.fill") + .font(.system(size: 11)) + .foregroundColor(DS.Colors.textTertiary) + } + .buttonStyle(.plain) + .pointerCursor() } + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(Color.white.opacity(0.06)) + .cornerRadius(6) + .overlay( + RoundedRectangle(cornerRadius: 6, style: .continuous) + .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) + ) + } + } + + private var mainContentView: some View { + VStack(alignment: .leading, spacing: 0) { + permissionsCopySection + .padding(.top, 16) + .padding(.horizontal, 16) if !companionManager.allPermissionsGranted { Spacer() @@ -67,20 +142,7 @@ struct CompanionPanelView: View { dmFarzaButton // <-- This is now defined below .padding(.horizontal, 16) } - - Spacer() - .frame(height: 12) - - Divider() - .background(DS.Colors.borderSubtle) - .padding(.horizontal, 16) - - footerSection // <-- This is now defined below - .padding(.horizontal, 16) - .padding(.vertical, 12) } - .frame(width: 320) - .background(panelBackground) // <-- This is now defined below } // MARK: - Header @@ -105,6 +167,25 @@ struct CompanionPanelView: View { .font(.system(size: 12, weight: .medium)) .foregroundColor(DS.Colors.textTertiary) + if companionManager.hasCompletedOnboarding && companionManager.allPermissionsGranted { + Button(action: { + withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { + isShowingSettings.toggle() + } + }) { + Image(systemName: "gear") + .font(.system(size: 11, weight: .semibold)) + .foregroundColor(isShowingSettings ? DS.Colors.textPrimary : DS.Colors.textTertiary) + .frame(width: 20, height: 20) + .background( + Circle() + .fill(isShowingSettings ? Color.white.opacity(0.12) : Color.white.opacity(0.08)) + ) + } + .buttonStyle(.plain) + .pointerCursor() + } + Button(action: { NotificationCenter.default.post(name: .clickyDismissPanel, object: nil) }) { @@ -598,6 +679,71 @@ struct CompanionPanelView: View { .padding(.vertical, 4) } + // MARK: - Language Picker + + private var languagePickerRow: some View { + HStack { + HStack(spacing: 8) { + Image(systemName: "globe") + .font(.system(size: 12, weight: .medium)) + .foregroundColor(DS.Colors.textTertiary) + .frame(width: 16) + + Text("Language") + .font(.system(size: 13, weight: .medium)) + .foregroundColor(DS.Colors.textSecondary) + } + + Spacer() + + Menu { + // ElevenLabs supports up to 32 base languages automatically on flash 2.5 + Button("🇺🇸 English") { companionManager.selectedLanguageCode = "en" } + Button("🇫🇷 French") { companionManager.selectedLanguageCode = "fr" } + Button("🇪🇸 Spanish") { companionManager.selectedLanguageCode = "es" } + Button("🇩🇪 German") { companionManager.selectedLanguageCode = "de" } + Button("🇮🇹 Italian") { companionManager.selectedLanguageCode = "it" } + Button("🇯🇵 Japanese") { companionManager.selectedLanguageCode = "ja" } + Button("🇵🇹 Portuguese") { companionManager.selectedLanguageCode = "pt" } + // AssemblyAI supports these natively + } label: { + HStack(spacing: 4) { + Text(flagForLanguage(companionManager.selectedLanguageCode)) + .font(.system(size: 14)) + Image(systemName: "chevron.down") + .font(.system(size: 9)) + .foregroundColor(DS.Colors.textTertiary) + } + .padding(.horizontal, 10) + .padding(.vertical, 5) + .background( + RoundedRectangle(cornerRadius: 6, style: .continuous) + .fill(Color.white.opacity(0.06)) + ) + .overlay( + RoundedRectangle(cornerRadius: 6, style: .continuous) + .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) + ) + } + .menuStyle(.borderlessButton) + .menuIndicator(.hidden) + .frame(maxWidth: 80) + } + .padding(.vertical, 4) + } + + private func flagForLanguage(_ code: String) -> String { + switch code { + case "fr": return "🇫🇷" + case "es": return "🇪🇸" + case "de": return "🇩🇪" + case "it": return "🇮🇹" + case "ja": return "🇯🇵" + case "pt": return "🇵🇹" + default: return "🇺🇸" + } + } + // MARK: - Model Picker private var modelPickerRow: some View { @@ -607,18 +753,6 @@ struct CompanionPanelView: View { .font(.system(size: 13, weight: .medium)) .foregroundColor(DS.Colors.textSecondary) - Button(action: { - withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { - isShowingLocalAPIKeyField.toggle() - } - }) { - Image(systemName: "gear") - .font(.system(size: 11)) - .foregroundColor(isShowingLocalAPIKeyField ? DS.Colors.textPrimary : DS.Colors.textTertiary) - } - .buttonStyle(.plain) - .pointerCursor() - Spacer() Menu { @@ -677,7 +811,7 @@ struct CompanionPanelView: View { .lineLimit(1) .truncationMode(.middle) - Image(systemName: "chevron.up.chevron.down") + Image(systemName: "chevron.down") .font(.system(size: 9)) .foregroundColor(DS.Colors.textTertiary) } @@ -693,60 +827,9 @@ struct CompanionPanelView: View { ) } .menuStyle(.borderlessButton) + .menuIndicator(.hidden) .frame(maxWidth: 200) } - - if isShowingLocalAPIKeyField { - HStack { - HStack(spacing: 8) { - ZStack(alignment: .leading) { - if companionManager.lmStudioAPIKey.isEmpty { - Text("LM Studio API Key (leave empty if none)") - .font(.system(size: 11)) - .foregroundColor(.white.opacity(0.6)) - } - - if isAPIKeyVisible { - TextField("", text: Binding( - get: { companionManager.lmStudioAPIKey }, - set: { companionManager.lmStudioAPIKey = $0 } - )) - .textFieldStyle(PlainTextFieldStyle()) - .font(.system(size: 11)) - .foregroundColor(.white) - } else { - SecureField("", text: Binding( - get: { companionManager.lmStudioAPIKey }, - set: { companionManager.lmStudioAPIKey = $0 } - )) - .textFieldStyle(PlainTextFieldStyle()) - .font(.system(size: 11)) - .foregroundColor(.white) - } - } - - Button(action: { - isAPIKeyVisible.toggle() - }) { - Image(systemName: isAPIKeyVisible ? "eye.slash.fill" : "eye.fill") - .font(.system(size: 11)) - .foregroundColor(DS.Colors.textTertiary) - } - .buttonStyle(.plain) - .pointerCursor() - } - .padding(.horizontal, 10) - .padding(.vertical, 6) - .background(Color.white.opacity(0.06)) - .cornerRadius(6) - .overlay( - RoundedRectangle(cornerRadius: 6, style: .continuous) - .stroke(DS.Colors.borderSubtle, lineWidth: 0.5) - ) - } - .transition(.opacity.combined(with: .move(edge: .top))) - .padding(.top, 4) - } } .padding(.vertical, 4) } diff --git a/leanring-buddy/OpenAIAudioTranscriptionProvider.swift b/leanring-buddy/OpenAIAudioTranscriptionProvider.swift index 75092092..54dab211 100644 --- a/leanring-buddy/OpenAIAudioTranscriptionProvider.swift +++ b/leanring-buddy/OpenAIAudioTranscriptionProvider.swift @@ -34,6 +34,7 @@ final class OpenAIAudioTranscriptionProvider: BuddyTranscriptionProvider { } func startStreamingSession( + languageCode: String, keyterms: [String], onTranscriptUpdate: @escaping (String) -> Void, onFinalTranscriptReady: @escaping (String) -> Void, @@ -49,6 +50,7 @@ final class OpenAIAudioTranscriptionProvider: BuddyTranscriptionProvider { apiKey: apiKey, modelName: modelName, keyterms: keyterms, + languageCode: languageCode, onTranscriptUpdate: onTranscriptUpdate, onFinalTranscriptReady: onFinalTranscriptReady, onError: onError @@ -69,6 +71,7 @@ private final class OpenAIAudioTranscriptionSession: BuddyStreamingTranscription private let apiKey: String private let modelName: String private let keyterms: [String] + private let languageCode: String private let onTranscriptUpdate: (String) -> Void private let onFinalTranscriptReady: (String) -> Void private let onError: (Error) -> Void @@ -89,6 +92,7 @@ private final class OpenAIAudioTranscriptionSession: BuddyStreamingTranscription apiKey: String, modelName: String, keyterms: [String], + languageCode: String, onTranscriptUpdate: @escaping (String) -> Void, onFinalTranscriptReady: @escaping (String) -> Void, onError: @escaping (Error) -> Void @@ -96,6 +100,7 @@ private final class OpenAIAudioTranscriptionSession: BuddyStreamingTranscription self.apiKey = apiKey self.modelName = modelName self.keyterms = keyterms + self.languageCode = languageCode self.onTranscriptUpdate = onTranscriptUpdate self.onFinalTranscriptReady = onFinalTranscriptReady self.onError = onError @@ -234,7 +239,7 @@ private final class OpenAIAudioTranscriptionSession: BuddyStreamingTranscription ) requestBodyData.appendMultipartFormField( named: "language", - value: "en", + value: languageCode, usingBoundary: boundary ) requestBodyData.appendMultipartFormField( From 7920677ff6b207a7f29c2d308687531ae3852292 Mon Sep 17 00:00:00 2001 From: MathisZerbib Date: Thu, 9 Apr 2026 00:02:12 +0200 Subject: [PATCH 20/20] feat: add Russian language option to language picker in CompanionPanelView --- leanring-buddy/CompanionPanelView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/leanring-buddy/CompanionPanelView.swift b/leanring-buddy/CompanionPanelView.swift index 8d235eff..a65b28d5 100644 --- a/leanring-buddy/CompanionPanelView.swift +++ b/leanring-buddy/CompanionPanelView.swift @@ -705,6 +705,7 @@ struct CompanionPanelView: View { Button("🇮🇹 Italian") { companionManager.selectedLanguageCode = "it" } Button("🇯🇵 Japanese") { companionManager.selectedLanguageCode = "ja" } Button("🇵🇹 Portuguese") { companionManager.selectedLanguageCode = "pt" } + Button("🇷🇺 Russian") { companionManager.selectedLanguageCode = "ru" } // AssemblyAI supports these natively } label: { HStack(spacing: 4) { @@ -740,6 +741,7 @@ struct CompanionPanelView: View { case "it": return "🇮🇹" case "ja": return "🇯🇵" case "pt": return "🇵🇹" + case "ru": return "🇷🇺" default: return "🇺🇸" } }