-
Notifications
You must be signed in to change notification settings - Fork 206
Refactor external api feature #211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@flukexp is attempting to deploy a commit to the heyamica Team on Vercel. A member of the Team first needs to authorize it. |
…ndling in Twitter client
WalkthroughMigrates external API from file-based SSE to a Supabase-backed, session-scoped realtime system; adds session handling, per-session memory and server context, new credential fields in settings, replaces local JSON storage and dataHandler endpoints, and updates API routes, processors, and docs to use Supabase realtime and session IDs. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as UI (Settings/Chat)
participant Chat as Chat Module
participant RT as Supabase Realtime
participant API as /api/amicaHandler
participant DB as Supabase (external-api)
User->>UI: Toggle External API / Provide Credentials
UI->>DB: handleConfig() upsert configs (session_id)
UI->>Chat: initialize(sessionId, setSubconciousLogs)
Chat->>RT: initRealtime(sessionId channel) subscribe
Note over Chat,RT: Realtime events: normal, animation, playback,<br/>subconscious, systemPrompt
User->>UI: Send message / action
UI->>API: POST /api/amicaHandler { sessionId, inputType, payload }
API->>DB: readStore/updateStore per inputType
API-->>RT: addClientEvents(sessionId, type, data)
RT-->>Chat: deliver event
Chat->>UI: speak/playback/animate/update logs
sequenceDiagram
autonumber
actor Client
participant MH as /api/mediaHandler
participant Ctx as ServerContext
participant MS as memoryStore
participant DB as Supabase
participant LLM as Vision/Transcription
Client->>MH: multipart/form-data (sessionId, file, inputType)
MH->>Ctx: runWithServerContext({sessionId})
MH->>MS: readStore(sessionId, "configs")
MH->>MS: writeServerConfig(sessionId, configs)
alt voice input
MH->>LLM: transcribe wav from PCM
else image input
MH->>LLM: process image (vision_openai/...)
end
MH->>DB: updateStore(sessionId, "chat_logs"/"logs")
MH-->>Client: JSON { sessionId, outputType, response }
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 24
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/utils/askLlm.ts (1)
101-106: Fix no-op stream-index guards in src/utils/askLlm.tscurrentStreamIdx is compared to itself (always-true/false). Compare against chat.currentStreamIdx to cancel stale streams and to decide when to clear processing.
- if (currentStreamIdx !== currentStreamIdx) { + if (chat && currentStreamIdx !== chat.currentStreamIdx) { console.log("Wrong stream idx"); break; }- if (currentStreamIdx === currentStreamIdx) { + if (!chat || currentStreamIdx === chat.currentStreamIdx) { setChatProcessing(false); }Applies to src/utils/askLlm.ts (≈ lines 101–106 and 170–172).
src/pages/index.tsx (1)
191-197: Guard viewer.stopStreaming() when viewer is undefinedIn the else branch,
viewercan be falsy; callingviewer.stopStreaming()will throw.- if (viewer && videoRef.current && showStreamWindow) { - viewer.startStreaming(videoRef.current); - } else { - viewer.stopStreaming(); - } + if (viewer && videoRef.current && showStreamWindow) { + viewer.startStreaming(videoRef.current); + } else { + viewer?.stopStreaming(); + }src/features/amicaLife/amicaLife.ts (1)
51-56: Initialize signature updated — update all callersEnsure every call to amicaLife.initialize passes the new third parameter (setSubconciousLogs) and the boolean isChatSpeaking as the final arg. Found: src/pages/index.tsx:322 calls amicaLife.initialize(viewer, bot) — update to amicaLife.initialize(viewer, bot, setSubconciousLogs, isChatSpeaking).
src/utils/config.ts (1)
55-55: Wrong env var used forvision_openai_model.Should read from NEXT_PUBLIC_VISION_OPENAI_MODEL, not URL.
Apply this diff:
- vision_openai_model: process.env.NEXT_PUBLIC_VISION_OPENAI_URL ?? 'gpt-4-vision-preview', + vision_openai_model: process.env.NEXT_PUBLIC_VISION_OPENAI_MODEL ?? 'gpt-4-vision-preview',
🧹 Nitpick comments (40)
src/features/externalAPI/processors/imageProcessor.ts (3)
12-20: Preprocess for orientation, size cap, and alpha handling before JPEG.Helps LLM OCR/vision quality, avoids huge uploads, and prevents black halos from transparent PNGs.
Apply:
- const jpegBuffer = await sharp(payload).jpeg({quality: 70}).toBuffer(); + const jpegBuffer = await sharp(payload) + .rotate() // honor EXIF orientation + .resize({ width: 1280, height: 1280, fit: 'inside', withoutEnlargement: true }) // cap dimensions + .flatten({ background: '#fff' }) // drop alpha with a sane background + .jpeg({ quality: 70, progressive: true }) + .toBuffer();
15-16: Consider returning a data URL to make the MIME explicit.Prevents ambiguity at call sites and matches common Vision API expectations. Only do this if
askVisionLLMaccepts data URLs.- return jpegBuffer.toString('base64'); + const b64 = jpegBuffer.toString('base64'); + return `data:image/jpeg;base64,${b64}`;
4-10: Preserve error cause for observability.Right now the original error context is lost to callers. Propagate the cause for better tracing.
export async function processImage(payload: Buffer): Promise<string> { - const jpegImg = await convertToJpeg(payload); - if (!jpegImg) { - throw new Error('Failed to process image'); - } - return await askVisionLLM(jpegImg); + try { + const jpegImg = await convertToJpeg(payload); + if (!jpegImg) throw new Error('convertToJpeg returned null'); + return await askVisionLLM(jpegImg); + } catch (err) { + throw new Error('Failed to process image', { cause: err as Error }); + } }src/utils/askLlm.ts (1)
186-193: Reduce duplication across vision backends.You build
messagesfor LLaMA/Ollama and then rebuild for OpenAI. Consider normalizing once and branching only for transport-specific shapes (string vs array content) to avoid drift.Also applies to: 199-219
src/utils/supabase.ts (2)
11-25: Fix return type and nullability mismatch in getEaiSupabaseThe function never returns null (it throws if env vars are missing). Make the return type non-null and assert the cached instance.
-export function getEaiSupabase(): SupabaseClient | null { +export function getEaiSupabase(): SupabaseClient { if (!_eaiSupabase) { const url = process.env.NEXT_PUBLIC_EAI_SUPABASE_URL; const key = process.env.NEXT_PUBLIC_EAI_SUPABASE_ANON_KEY; if (!url || !key) { throw new Error("Missing environment variables for EAI Supabase."); } _eaiSupabase = createClient(url, key); } - return _eaiSupabase; + return _eaiSupabase!; }
3-6: Harden default Supabase client initializationAvoid unsafe casts; validate env at module load to fail fast and get clearer errors at startup.
-export const supabase = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL as string, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY as string, -); +const _url = process.env.NEXT_PUBLIC_SUPABASE_URL; +const _key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; +if (!_url || !_key) { + throw new Error("Missing environment variables for Supabase."); +} +export const supabase = createClient(_url, _key);docs/api/api-reference.md (4)
138-144: Docs inconsistency: SSE vs Supabase RealtimeThis section says “via Supabase Realtime Client” but the methods list still documents “GET: Establishes an SSE connection.” Update to reflect the new realtime mechanism or remove the GET/SSE note.
-### Supported HTTP Methods: - -- **GET**: Establishes an SSE connection. -- **POST**: Processes various input types based on the `inputType` provided in the request body. +### Supported HTTP Methods: + +- **POST**: Processes various input types based on the `inputType` provided in the request body. + Realtime updates are delivered via Supabase Realtime channels (no SSE GET endpoint).
17-21: Modernize bullets and terminologyThe intro still mentions SSE; switch to Supabase Realtime and fix markdownlint MD004 by using dash bullets.
-- Real-Time Interaction: Establish and manage connections through Server-Sent Events (SSE). +- Real-Time Interaction: Establish and manage connections through Supabase Realtime channels.
440-456: Clarify error handling and feature gatingAdd that the External API feature is gated by the presence of EAI Supabase env vars and a valid sessionId; also remove ambiguous “503 for disabled API” if not applicable post‑refactor.
-- Returns appropriate HTTP status codes (e.g., 400 for bad requests, 503 for disabled API). +- Returns appropriate HTTP status codes (e.g., 400 for bad requests). External API endpoints require: + - `sessionId` present and valid. + - EAI Supabase configured (see Local Supabase Setup); otherwise the feature is disabled in UI and endpoints return 400 with an explanatory error.
34-49: Nit: fix markdownlint (bullets and hr)Switch asterisks to dashes for list items and use
---for horizontal rules to satisfy MD004/MD035.-* **Session ID**: This must be copied and included in **every API call** across all routes. -* **X and TG Credentials**: These are required for APIs using the **Reasoning** type. + - **Session ID**: This must be copied and included in every API call across all routes. + - **X and TG Credentials**: Required for APIs using the Reasoning type. - ---- +---.env.local (1)
7-8: Order keys to satisfy dotenv-linter (cosmetic)Place
NEXT_PUBLIC_EAI_SUPABASE_ANON_KEYbeforeNEXT_PUBLIC_EAI_SUPABASE_URLas suggested by the linter.-NEXT_PUBLIC_EAI_SUPABASE_URL= -NEXT_PUBLIC_EAI_SUPABASE_ANON_KEY= +NEXT_PUBLIC_EAI_SUPABASE_ANON_KEY= +NEXT_PUBLIC_EAI_SUPABASE_URL=src/pages/index.tsx (1)
299-313: Include all referenced deps in initialize effect
alertis used inside the effect but not listed as a dependency. Add it to avoid stale closures.- }, [bot, viewer]); + }, [bot, viewer, alert]);src/components/settings.tsx (2)
311-312: Fix double comma in effect deps (sparse array)
rvcFilterRadius,,rvcResampleSrintroduces a hole in the dependency array. Not harmful but untidy.- rvcUrl,rvcEnabled,rvcModelName,rvcIndexPath,rvcF0upKey,rvcF0Method,rvcIndexRate,rvcFilterRadius,,rvcResampleSr,rvcRmsMixRate,rvcProtect, + rvcUrl,rvcEnabled,rvcModelName,rvcIndexPath,rvcF0upKey,rvcF0Method,rvcIndexRate,rvcFilterRadius,rvcResampleSr,rvcRmsMixRate,rvcProtect,
752-771: Setter naming consistency and typo (setXAcessToken)Use consistent camelCase across setters and fix the missing “c” in
setXAcessToken.- const [xAccessToken, setXAcessToken] = useState(config("x_access_token")); + const [xAccessToken, setXAccessToken] = useState(config("x_access_token")); ... - setXAccessToken={setXAcessToken} + setXAccessToken={setXAccessToken}Optionally normalize
setXAPIKey/setXAPISecrettosetXApiKey/setXApiSecretfor consistency.src/features/externalAPI/utils/socialMediaHandler.ts (2)
5-7: Parameter ‘req’ is unused; either use or mark as intentionally unused.Rename to
_reqto silence linters without widening surface area.Apply:
-import { NextApiRequest } from "next"; +import { NextApiRequest } from "next"; ... -export const handleSocialMediaActions = async ( - sessionId: string, - req: NextApiRequest, +export const handleSocialMediaActions = async ( + sessionId: string, + _req: NextApiRequest,Also applies to: 1-1
28-29: Await side-effect to ensure ordering and error propagation.
addClientEventsis async; without await, caller may observe success before the insert completes.Apply:
- addClientEvents(sessionId, "normal", message); + await addClientEvents(sessionId, "normal", message);src/pages/api/mediaHandler.ts (1)
31-33: Set explicit formidable limits to prevent large uploads/DoS.Bound file size and number of files.
Apply:
- const form = formidable({}); + const form = formidable({ + multiples: false, + maxFiles: 1, + maxFileSize: 10 * 1024 * 1024, // 10MB + });src/features/externalAPI/processors/chatProcessor.ts (3)
31-37: Await event inserts for deterministic sequencing.Without awaits, client may receive success before events are stored.
Apply:
- if (playback) { - addClientEvents(sessionId,"playback", "10000"); - } + if (playback) { + await addClientEvents(sessionId, "playback", "10000"); + } - if (animation) { - addClientEvents(sessionId, "animation", animation); - } + if (animation) { + await addClientEvents(sessionId, "animation", animation); + }
12-15:reqis unused; align with handler signature or mark as_req.Mirror the change in socialMediaHandler to avoid unused param noise.
Apply:
export const triggerAmicaActions = async ( sessionId: string, - req: NextApiRequest, + _req: NextApiRequest, payload: any, ) => {
57-64: Await systemPrompt event publish; surface errors if any.Return the resolved result, not a bare Promise.
Apply:
export const updateSystemPrompt = async ( sessionId: string, payload: any, ): Promise<any> => { const { prompt } = payload; - let response = addClientEvents(sessionId,"systemPrompt", prompt); - return response; + return await addClientEvents(sessionId, "systemPrompt", prompt); };src/features/amicaLife/eventHandler.ts (2)
52-54: Guard against empty animation list.If
animationListis empty,Math.floor(Math.random() * 0)is NaN; add a fast return.Apply:
do { - randomAnimation = - animationList[Math.floor(Math.random() * animationList.length)]; + if (animationList.length === 0) { + console.warn("No animations available."); + amicaLife.eventProcessing = false; + return; + } + randomAnimation = animationList[Math.floor(Math.random() * animationList.length)];
201-225: High-frequency Supabase writes from UI path; consider debouncing/batching.Every subconscious event persists the full
updatedLogs. This can be write-heavy and costly; batch, debounce, or diff-persist instead.Option: debounce
handleSubconscious(sessionId, updatedLogs)(e.g., 2–5s window) or only upsert the new entry and trim server-side via RPC/trigger.src/pages/api/amicaHandler.ts (3)
49-51: Fix grammar in validation message.Use singular and include HTTP 400 consistently.
Apply this diff:
- if (!inputType) { - return sendError(res, sessionId, "inputType are required."); - } + if (!inputType) { + return sendError(res, sessionId, "inputType is required.", 400); + }
29-31: Remove unusednoProcessChat(or implement its behavior).Dead param causes confusion.
Apply this diff to remove it:
- const { sessionId, inputType, payload, noProcessChat = false } = req.body; + const { sessionId, inputType, payload } = req.body;If you intend to gate chat processing, branch on it in processRequest.
81-90: Validate payload types per inputType (string expected for Normal Chat).
processNormalChat(payload)expects a string; guard and error early if not.Apply this diff:
switch (inputType) { case "Normal Chat Message": - return { response: await processNormalChat(payload), outputType: "Chat" }; + if (typeof payload !== "string") { + throw new Error("payload must be a string for Normal Chat Message"); + } + return { response: await processNormalChat(payload), outputType: "Chat" };src/components/settings/ExternalAPIPage.tsx (3)
56-56: MakecanEnableExternalApiexplicitly boolean.Current expression yields a string.
Apply this diff:
- const canEnableExternalApi = process.env.NEXT_PUBLIC_EAI_SUPABASE_URL && process.env.NEXT_PUBLIC_EAI_SUPABASE_ANON_KEY; + const canEnableExternalApi = Boolean(process.env.NEXT_PUBLIC_EAI_SUPABASE_URL && process.env.NEXT_PUBLIC_EAI_SUPABASE_ANON_KEY);
96-100: Minor copy: “develop” → “development”.Tiny UX polish.
Apply this diff:
- <NotUsingAlert> - External API currently in develop. - </NotUsingAlert> + <NotUsingAlert>External API currently in development.</NotUsingAlert>
117-139: Optional: handle clipboard errors.Older browsers or http context can reject clipboard writes; consider try/catch with a toast.
Would you like a small helper to abstract copy-to-clipboard with fallback?
src/utils/config.ts (2)
136-150: Guard server-config read when sessionId is unavailable.Avoid initializing memory under an "undefined" key and reduce noise.
Apply this diff:
- if (typeof window === "undefined") { - let sessionId: string | undefined = undefined; - try { - const { - getServerSessionId, - } = require("@/features/externalAPI/serverContext"); - sessionId = getServerSessionId(); - } catch (err) { - console.warn("Could not load server session ID:", err); - } - const serverConfig = readServerConfig(sessionId!); - if (serverConfig && serverConfig.hasOwnProperty(key)) { - return serverConfig[key]; - } - } + if (typeof window === "undefined") { + try { + const { getServerSessionId } = require("@/features/externalAPI/serverContext"); + const sessionId: string | undefined = getServerSessionId?.(); + if (sessionId) { + const serverConfig = readServerConfig(sessionId); + if (serverConfig && serverConfig.hasOwnProperty(key)) { + return serverConfig[key]; + } + } + } catch (err) { + console.warn("Could not load server session ID:", err); + } + }
169-171: Confirm server sync uses the correct session id.Relies on localStorage-backed
config("session_id"); ensure it’s set before calling updateConfig from UI.If needed, I can add a helper
setSessionId(id: string)to centralize updating both localStorage and server state.src/features/externalAPI/memoryStore.ts (3)
31-33: Unreachable null checks for Supabase client.
getEaiSupabase()throws on missing env; theseif(!eaiSupabase)branches won’t run.Either remove the checks or change
getEaiSupabaseto return null rather than throwing. Keep behavior consistent.Also applies to: 70-73
5-14: In‑memoryserverConfigcan grow unbounded; add eviction.Long‑lived processes may leak memory across sessions.
Minimal eviction example (outside current hunks):
// Simple TTL map wrapper const TTL_MS = 30 * 60 * 1000; // 30 minutes const lastAccess: Record<string, number> = {}; function touch(sessionId: string) { lastAccess[sessionId] = Date.now(); } setInterval(() => { const now = Date.now(); for (const [sid, ts] of Object.entries(lastAccess)) { if (now - ts > TTL_MS) { delete serverConfig[sid]; delete lastAccess[sid]; } } }, 5 * 60 * 1000);Call
touch(sessionId)in read/write helpers.Also applies to: 130-140
44-53: Return shape ambiguity fromreadStore.For multi-row object/array merges you normalize, but empty result returns
{}. Callers expecting arrays (e.g., logs) may choke.Consider returning
[]for known list stores (logs,chat_logs,user_input_messages) or document the contract and normalize at call sites.src/features/chat/chat.ts (3)
547-567: Prevent duplicate log uploads; clear buffer after successful send.Currently the last 20 logs are resent every 30s. Clear uploaded logs to avoid duplicates.
- if (logs?.length && apiEnabled === "true" && sessionId) { + if (Array.isArray(logs) && logs.length && apiEnabled === "true" && sessionId) { try { - const last50 = logs.slice(-20) - await handleLogs(sessionId, last50); + const last20 = logs.slice(-20); + await handleLogs(sessionId, last20); + // drop the ones we just uploaded + (window as any).error_handler_logs = logs.slice(0, -last20.length); } catch (err) { console.error("Unexpected error during log upload:", err); } }
45-50: Remove unused import or use the type.SupabaseClient is only needed for typing (now used in the try/catch fix above). If not adopting that fix, drop the import.
470-476: Don’t throw in event handler; log instead.Throwing inside the realtime callback can destabilize the subscription.
(Handled in the switch-case diff by converting to console.error and early break.)
src/features/externalAPI/externalAPI.ts (4)
35-38: Avoid direct localStorage access on server.This function may be imported server-side; guard for SSR.
- for (const key in defaults) { - const localKey = prefixed(key); - data[key] = localStorage.getItem(localKey) ?? (defaults as any)[key]; - } + const hasLocalStorage = typeof localStorage !== "undefined"; + for (const key in defaults) { + const localKey = prefixed(key); + data[key] = hasLocalStorage + ? localStorage.getItem(localKey) ?? (defaults as any)[key] + : (defaults as any)[key]; + }
44-48: Ensure session_id exists before writing user_input_messages.If session_id isn’t initialized, you’ll upsert bad rows.
export async function handleUserInput(messages: Message[]) { - await upsertToTable("user_input_messages", { - session_id: config("session_id"), + const sessionId = config("session_id"); + if (!sessionId) { + console.warn("handleUserInput: missing session_id; skipping"); + return; + } + await upsertToTable("user_input_messages", { + session_id: sessionId, data: messages, }); }
72-79: Gate addClientEvents behind external_api_enabled for consistency.Align with other writers and avoid accidental writes when disabled.
export async function addClientEvents(sessionId: string, type: string, data: string) { + if (config("external_api_enabled") !== "true") return; const eaiSupabase = getEaiSupabase(); if (!eaiSupabase) return; await eaiSupabase .schema(SCHEMA) .from("events") .insert({ session_id: sessionId, type, data }); }
83-104: Return a consistent result if Supabase isn’t available.Currently returns undefined; prefer a structured failure.
- if(!eaiSupabase) return; + if(!eaiSupabase) { + return { success: false, errors: [{ table: "all", error: "Supabase client not available" }] }; + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (31)
.env.local(1 hunks)docs/api/api-reference.md(21 hunks)public/debugLogger.js(0 hunks)src/components/settings.tsx(3 hunks)src/components/settings/ExternalAPIPage.tsx(2 hunks)src/features/amicaLife/amicaLife.ts(2 hunks)src/features/amicaLife/eventHandler.ts(5 hunks)src/features/chat/chat.ts(8 hunks)src/features/externalAPI/dataHandlerStorage/chatLogs.json(0 hunks)src/features/externalAPI/dataHandlerStorage/config.json(0 hunks)src/features/externalAPI/dataHandlerStorage/logs.json(0 hunks)src/features/externalAPI/dataHandlerStorage/subconscious.json(0 hunks)src/features/externalAPI/dataHandlerStorage/userInputMessages.json(0 hunks)src/features/externalAPI/dataHelper.ts(0 hunks)src/features/externalAPI/externalAPI.ts(1 hunks)src/features/externalAPI/memoryStore.ts(1 hunks)src/features/externalAPI/processors/chatProcessor.ts(1 hunks)src/features/externalAPI/processors/imageProcessor.ts(1 hunks)src/features/externalAPI/serverContext.ts(1 hunks)src/features/externalAPI/socialMedia/telegramClient.ts(2 hunks)src/features/externalAPI/socialMedia/twitterClient.ts(1 hunks)src/features/externalAPI/utils/apiHelper.ts(1 hunks)src/features/externalAPI/utils/requestHandler.ts(0 hunks)src/features/externalAPI/utils/socialMediaHandler.ts(1 hunks)src/pages/api/amicaHandler.ts(1 hunks)src/pages/api/dataHandler.ts(0 hunks)src/pages/api/mediaHandler.ts(5 hunks)src/pages/index.tsx(1 hunks)src/utils/askLlm.ts(2 hunks)src/utils/config.ts(4 hunks)src/utils/supabase.ts(1 hunks)
💤 Files with no reviewable changes (9)
- src/features/externalAPI/dataHandlerStorage/chatLogs.json
- src/features/externalAPI/dataHandlerStorage/logs.json
- public/debugLogger.js
- src/features/externalAPI/dataHandlerStorage/config.json
- src/features/externalAPI/dataHandlerStorage/subconscious.json
- src/features/externalAPI/dataHandlerStorage/userInputMessages.json
- src/pages/api/dataHandler.ts
- src/features/externalAPI/utils/requestHandler.ts
- src/features/externalAPI/dataHelper.ts
🧰 Additional context used
🧬 Code graph analysis (15)
src/features/amicaLife/amicaLife.ts (2)
src/features/amicaLife/eventHandler.ts (1)
TimestampedPrompt(40-43)src/features/chat/chat.ts (1)
Chat(62-864)
src/features/externalAPI/memoryStore.ts (2)
src/utils/supabase.ts (1)
getEaiSupabase(11-25)src/features/externalAPI/externalAPI.ts (1)
addClientEvents(72-79)
src/features/externalAPI/utils/socialMediaHandler.ts (3)
src/features/externalAPI/socialMedia/twitterClient.ts (1)
getTwitterClient(55-60)src/features/externalAPI/socialMedia/telegramClient.ts (1)
getTelegramClient(39-44)src/features/externalAPI/externalAPI.ts (1)
addClientEvents(72-79)
src/utils/askLlm.ts (3)
src/features/chat/messages.ts (1)
Message(4-7)src/utils/config.ts (1)
config(130-157)src/features/chat/openAiChat.ts (1)
getOpenAiVisionChatResponse(105-121)
src/components/settings.tsx (1)
src/utils/config.ts (1)
config(130-157)
src/features/chat/chat.ts (6)
src/features/amicaLife/eventHandler.ts (2)
TimestampedPrompt(40-43)MAX_STORAGE_TOKENS(37-37)src/utils/config.ts (2)
config(130-157)updateConfig(159-175)src/features/externalAPI/externalAPI.ts (2)
handleUserInput(44-49)handleLogs(65-70)src/utils/supabase.ts (1)
getEaiSupabase(11-25)src/features/chat/echoChat.ts (1)
getEchoChatResponseStream(3-25)src/lib/VRMAnimation/loadVRMAnimation.ts (1)
loadVRMAnimation(8-15)
src/features/externalAPI/processors/chatProcessor.ts (4)
src/utils/askLlm.ts (1)
askLLM(15-176)src/utils/config.ts (1)
config(130-157)src/features/externalAPI/utils/socialMediaHandler.ts (1)
handleSocialMediaActions(4-33)src/features/externalAPI/externalAPI.ts (1)
addClientEvents(72-79)
src/utils/config.ts (2)
src/features/externalAPI/serverContext.ts (1)
getServerSessionId(13-15)src/features/externalAPI/memoryStore.ts (2)
readServerConfig(125-128)updateStore(67-122)
src/pages/api/amicaHandler.ts (5)
src/features/externalAPI/utils/apiHelper.ts (3)
apiLogEntry(11-18)ApiResponse(4-9)sendError(23-28)src/pages/api/mediaHandler.ts (2)
handler(25-70)config(18-22)src/features/externalAPI/serverContext.ts (1)
runWithServerContext(9-11)src/features/externalAPI/memoryStore.ts (3)
readStore(26-65)writeServerConfig(130-133)updateStore(67-122)src/features/externalAPI/processors/chatProcessor.ts (3)
processNormalChat(7-9)updateSystemPrompt(57-64)triggerAmicaActions(11-55)
src/features/externalAPI/socialMedia/twitterClient.ts (1)
src/utils/config.ts (1)
config(130-157)
src/components/settings/ExternalAPIPage.tsx (8)
src/features/chat/chatContext.ts (1)
ChatContext(6-6)src/features/alert/alertContext.ts (1)
AlertContext(6-6)src/utils/config.ts (2)
prefixed(126-128)updateConfig(159-175)src/features/externalAPI/externalAPI.ts (2)
handleConfig(31-42)deleteAllSessionData(82-104)src/components/settings/common.tsx (3)
BasicPage(51-72)NotUsingAlert(272-278)FormRow(74-88)src/components/switchBox.tsx (1)
SwitchBox(12-86)src/components/iconButton.tsx (1)
IconButton(9-30)src/components/secretTextInput.tsx (1)
SecretTextInput(10-83)
src/features/externalAPI/socialMedia/telegramClient.ts (1)
src/utils/config.ts (1)
config(130-157)
src/features/externalAPI/externalAPI.ts (5)
src/features/externalAPI/utils/apiHelper.ts (1)
generateSessionId(20-21)src/utils/config.ts (3)
config(130-157)defaults(3-124)prefixed(126-128)src/utils/supabase.ts (1)
getEaiSupabase(11-25)src/features/chat/messages.ts (1)
Message(4-7)src/features/amicaLife/eventHandler.ts (1)
TimestampedPrompt(40-43)
src/pages/api/mediaHandler.ts (4)
src/pages/api/amicaHandler.ts (2)
handler(25-79)apiLogs(22-22)src/features/externalAPI/utils/apiHelper.ts (2)
ApiResponse(4-9)sendError(23-28)src/features/externalAPI/memoryStore.ts (2)
readStore(26-65)writeServerConfig(130-133)src/features/externalAPI/serverContext.ts (1)
runWithServerContext(9-11)
src/features/amicaLife/eventHandler.ts (3)
scripts/generate_paths.js (1)
animationList(10-13)src/utils/config.ts (1)
config(130-157)src/features/externalAPI/externalAPI.ts (1)
handleSubconscious(58-63)
🪛 dotenv-linter (3.3.0)
.env.local
[warning] 8-8: [UnorderedKey] The NEXT_PUBLIC_EAI_SUPABASE_ANON_KEY key should go before the NEXT_PUBLIC_EAI_SUPABASE_URL key
(UnorderedKey)
🪛 markdownlint-cli2 (0.17.2)
docs/api/api-reference.md
40-40: Unordered list style
Expected: dash; Actual: asterisk
(MD004, ul-style)
41-41: Unordered list style
Expected: dash; Actual: asterisk
(MD004, ul-style)
45-45: Horizontal rule style
Expected: --- ; Actual: ---
(MD035, hr-style)
120-120: Unordered list style
Expected: dash; Actual: asterisk
(MD004, ul-style)
121-121: Unordered list style
Expected: dash; Actual: asterisk
(MD004, ul-style)
134-134: Horizontal rule style
Expected: --- ; Actual: ---
(MD035, hr-style)
🪛 Biome (2.1.2)
src/features/chat/chat.ts
[error] 460-464: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 465-465: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 471-471: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (9)
src/features/externalAPI/processors/imageProcessor.ts (1)
14-15: Explicit JPEG quality is good — keep it.
Verified: askVisionLLM constructs a data URL from the provided base64 (src/utils/askLlm.ts:213 hasurl: \data:image/jpeg;base64,${imageData}``), and imageProcessor passes raw base64 (src/features/externalAPI/processors/imageProcessor.ts:9), so returning jpegBuffer.toString('base64') is correct and compatible.src/utils/askLlm.ts (1)
5-5: Import wiring looks good.The new
getOpenAiVisionChatResponseimport is correctly referenced below.src/features/externalAPI/utils/apiHelper.ts (1)
20-28: LGTM: leaner server helperSimplified surface (session id gen + uniform error sender) looks good and aligns with the new realtime flow.
src/pages/index.tsx (1)
311-312: LGTM: wiring subconscious logs into bot.initializePassing a React state dispatcher is consistent with the new realtime updates path.
Please confirm
Chat.initializeaccepts this extra parameter across all call sites.src/features/amicaLife/amicaLife.ts (1)
25-26: Type update for setSubconciousLogs looks correctStoring the React dispatcher enables immutable state updates from realtime events.
src/features/externalAPI/serverContext.ts (1)
9-15: Confirmed: async_hooks usage runs under Node — no Edge runtime foundrunWithServerContext is imported by src/pages/api/mediaHandler.ts and src/pages/api/amicaHandler.ts; getServerSessionId is used in src/utils/config.ts; no pages/api files declare runtime: 'edge' or export const runtime = 'edge'.
src/pages/api/mediaHandler.ts (1)
68-69: Return a 400 status for non-multipart explicitly (consistency).You already default to 400; this notes intent.
If API clients rely on a specific status code here, confirm 400 is expected.
src/pages/api/amicaHandler.ts (1)
60-67: Cap apiLogs size and avoid retaining large/PII payloads.File: src/pages/api/amicaHandler.ts Lines: 60-67 (also 69-76). Unbounded in-memory growth and potential PII retention — trim oldest entries when exceeding 1000 and consider storing only metadata (drop
response) in prod.apiLogs.push({ sessionId, timestamp, inputType, outputType, response, }); + if (apiLogs.length > 1000) apiLogs.shift();apiLogs.push({ sessionId: sessionId, timestamp, inputType, outputType: "Error", error: String(error), }); + if (apiLogs.length > 1000) apiLogs.shift();src/features/chat/chat.ts (1)
397-404: Confirm intent: only user messages are persisted.You filter out assistant messages when persisting user_input_messages. If you intended full context, persist all except the trailing system prompt or store both streams. Otherwise, ignore.
| NEXT_PUBLIC_SUPABASE_URL=https://kzfcwefqffysqsjrlyld.supabase.co | ||
| NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imt6ZmN3ZWZxZmZ5c3FzanJseWxkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDEyNzA5NjUsImV4cCI6MjAxNjg0Njk2NX0.z9zm2mbJu6RkdZ6zkCUltThIF43-ava_bLvk9qFQsiA |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid committing real project URL/anon key
Even though the anon key is “public,” committing concrete project credentials is risky if RLS is misconfigured. Replace with placeholders in the repo and move real values to local .env.local.
-NEXT_PUBLIC_SUPABASE_URL=https://kzfcwefqffysqsjrlyld.supabase.co
-NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imt6ZmN3ZWZxZmZ5c3FzanJseWxkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDEyNzA5NjUsImV4cCI6MjAxNjg0Njk2NX0.z9zm2mbJu6RkdZ6zkCUltThIF43-ava_bLvk9qFQsiA
+NEXT_PUBLIC_SUPABASE_URL=
+NEXT_PUBLIC_SUPABASE_ANON_KEY=If these are already public, please confirm RLS policies are enforced on all tables.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| NEXT_PUBLIC_SUPABASE_URL=https://kzfcwefqffysqsjrlyld.supabase.co | |
| NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imt6ZmN3ZWZxZmZ5c3FzanJseWxkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDEyNzA5NjUsImV4cCI6MjAxNjg0Njk2NX0.z9zm2mbJu6RkdZ6zkCUltThIF43-ava_bLvk9qFQsiA | |
| NEXT_PUBLIC_SUPABASE_URL= | |
| NEXT_PUBLIC_SUPABASE_ANON_KEY= |
🧰 Tools
🪛 Gitleaks (8.27.2)
[high] 5-5: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.
(jwt)
🪛 dotenv-linter (3.3.0)
[warning] 5-5: [UnorderedKey] The NEXT_PUBLIC_SUPABASE_ANON_KEY key should go before the NEXT_PUBLIC_SUPABASE_URL key
(UnorderedKey)
🤖 Prompt for AI Agents
In .env.local around lines 4 to 5, the file currently contains a real Supabase
project URL and anon key which should not be committed; replace these concrete
values with placeholder variables (e.g.
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url and
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key) in the repository and move the real
credentials into the developer's local .env.local or a secrets manager;
additionally, if this project was already public, confirm and document that Row
Level Security (RLS) policies are enabled for all tables and remove or rotate
the exposed keys immediately.
| ```sql | ||
| create schema if not exists "external-api"; | ||
|
|
||
| create table "external-api".events ( | ||
| id uuid primary key default gen_random_uuid(), | ||
| session_id text not null, | ||
| type text not null, | ||
| data text not null, | ||
| created_at timestamptz default now() | ||
| ); | ||
|
|
||
| create table "external-api".configs ( | ||
| session_id text not null, | ||
| data jsonb not null, | ||
| created_at timestamptz default now(), | ||
| primary key (session_id) | ||
| ); | ||
|
|
||
| create table "external-api".subconscious ( | ||
| session_id text not null, | ||
| data jsonb not null, | ||
| created_at timestamptz default now(), | ||
| primary key (session_id) | ||
| ); | ||
|
|
||
| create table "external-api".logs ( | ||
| session_id text not null, | ||
| data jsonb not null, | ||
| created_at timestamptz default now(), | ||
| primary key (session_id) | ||
| ); | ||
|
|
||
| create table "external-api".user_input_messages ( | ||
| session_id text not null, | ||
| data jsonb not null, | ||
| created_at timestamptz default now(), | ||
| primary key (session_id) | ||
| ); | ||
|
|
||
| create table "external-api".chat_logs ( | ||
| session_id text not null, | ||
| data jsonb not null, | ||
| created_at timestamptz default now(), | ||
| primary key (session_id) | ||
| ); | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SQL schema: primary keys for time-series tables block multiple rows per session
Tables logs, user_input_messages, and chat_logs use primary key (session_id), which prevents storing multiple records per session. Use a UUID primary key (or composite with created_at) and index session_id.
create schema if not exists "external-api";
-create table "external-api".events (
- id uuid primary key default gen_random_uuid(),
- session_id text not null,
- type text not null,
- data text not null,
- created_at timestamptz default now()
-);
+create table "external-api".events (
+ id uuid primary key default gen_random_uuid(),
+ session_id text not null,
+ type text not null,
+ data text not null,
+ created_at timestamptz default now()
+);
+create index on "external-api".events (session_id, created_at);
-create table "external-api".configs (
+create table "external-api".configs (
session_id text not null,
data jsonb not null,
created_at timestamptz default now(),
primary key (session_id)
);
-create table "external-api".subconscious (
+create table "external-api".subconscious (
session_id text not null,
data jsonb not null,
created_at timestamptz default now(),
primary key (session_id)
);
-create table "external-api".logs (
- session_id text not null,
- data jsonb not null,
- created_at timestamptz default now(),
- primary key (session_id)
-);
+create table "external-api".logs (
+ id uuid primary key default gen_random_uuid(),
+ session_id text not null,
+ data jsonb not null,
+ created_at timestamptz default now()
+);
+create index on "external-api".logs (session_id, created_at);
-create table "external-api".user_input_messages (
- session_id text not null,
- data jsonb not null,
- created_at timestamptz default now(),
- primary key (session_id)
-);
+create table "external-api".user_input_messages (
+ id uuid primary key default gen_random_uuid(),
+ session_id text not null,
+ data jsonb not null,
+ created_at timestamptz default now()
+);
+create index on "external-api".user_input_messages (session_id, created_at);
-create table "external-api".chat_logs (
- session_id text not null,
- data jsonb not null,
- created_at timestamptz default now(),
- primary key (session_id)
-);
+create table "external-api".chat_logs (
+ id uuid primary key default gen_random_uuid(),
+ session_id text not null,
+ data jsonb not null,
+ created_at timestamptz default now()
+);
+create index on "external-api".chat_logs (session_id, created_at);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ```sql | |
| create schema if not exists "external-api"; | |
| create table "external-api".events ( | |
| id uuid primary key default gen_random_uuid(), | |
| session_id text not null, | |
| type text not null, | |
| data text not null, | |
| created_at timestamptz default now() | |
| ); | |
| create table "external-api".configs ( | |
| session_id text not null, | |
| data jsonb not null, | |
| created_at timestamptz default now(), | |
| primary key (session_id) | |
| ); | |
| create table "external-api".subconscious ( | |
| session_id text not null, | |
| data jsonb not null, | |
| created_at timestamptz default now(), | |
| primary key (session_id) | |
| ); | |
| create table "external-api".logs ( | |
| session_id text not null, | |
| data jsonb not null, | |
| created_at timestamptz default now(), | |
| primary key (session_id) | |
| ); | |
| create table "external-api".user_input_messages ( | |
| session_id text not null, | |
| data jsonb not null, | |
| created_at timestamptz default now(), | |
| primary key (session_id) | |
| ); | |
| create table "external-api".chat_logs ( | |
| session_id text not null, | |
| data jsonb not null, | |
| created_at timestamptz default now(), | |
| primary key (session_id) | |
| ); | |
| ``` | |
| create schema if not exists "external-api"; | |
| create table "external-api".events ( | |
| id uuid primary key default gen_random_uuid(), | |
| session_id text not null, | |
| type text not null, | |
| data text not null, | |
| created_at timestamptz default now() | |
| ); | |
| create index on "external-api".events (session_id, created_at); | |
| create table "external-api".configs ( | |
| session_id text not null, | |
| data jsonb not null, | |
| created_at timestamptz default now(), | |
| primary key (session_id) | |
| ); | |
| create table "external-api".subconscious ( | |
| session_id text not null, | |
| data jsonb not null, | |
| created_at timestamptz default now(), | |
| primary key (session_id) | |
| ); | |
| create table "external-api".logs ( | |
| id uuid primary key default gen_random_uuid(), | |
| session_id text not null, | |
| data jsonb not null, | |
| created_at timestamptz default now() | |
| ); | |
| create index on "external-api".logs (session_id, created_at); | |
| create table "external-api".user_input_messages ( | |
| id uuid primary key default gen_random_uuid(), | |
| session_id text not null, | |
| data jsonb not null, | |
| created_at timestamptz default now() | |
| ); | |
| create index on "external-api".user_input_messages (session_id, created_at); | |
| create table "external-api".chat_logs ( | |
| id uuid primary key default gen_random_uuid(), | |
| session_id text not null, | |
| data jsonb not null, | |
| created_at timestamptz default now() | |
| ); | |
| create index on "external-api".chat_logs (session_id, created_at); |
🤖 Prompt for AI Agents
In docs/api/api-reference.md around lines 65 to 110, the SQL schema defines
logs, user_input_messages, and chat_logs with primary key (session_id) which
prevents multiple rows per session; change each of those tables to use a proper
row-unique primary key (e.g., add an id uuid primary key with default
gen_random_uuid()) and keep session_id as a non-unique column with an index (or
make a composite key including created_at if you need uniqueness per timestamp)
so multiple records per session can be stored and queries on session_id remain
performant.
| const [xApiKey, setXApiKey] = useState(config("x_api_key")); | ||
| const [xApiSecret, setXAPISecret] = useState(config("x_api_secret")); | ||
| const [xAccessToken, setXAcessToken] = useState(config("x_access_token")); | ||
| const [xAccessSecret, setXAccessSecret] = useState(config("x_access_secret")); | ||
| const [xBearerToken, setXBearerToken] = useState(config("x_bearer_token")); | ||
| const [telegramBotToken, setTelegramBotToken] = useState(config("telegram_bot_token")); | ||
| const [telegramChatId, setTelegramChatId] = useState(config("telegram_chat_id")); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not persist 3rd‑party credentials in localStorage
X/Twitter and Telegram tokens in config() are stored client‑side, exposing them to XSS and browser extensions. Prefer server‑side storage (e.g., Supabase with RLS and a service role via server endpoints) or platform‑specific secure storage (e.g., Tauri keychain).
High‑level approach:
- Move setters to call a server API that stores secrets in a protected table (not accessible with anon key).
- Only keep boolean flags or masked hints in localStorage/UI.
| const [sessionId, setSessionId] = useState(localStorage.getItem(prefixed("session_id"))) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid SSR crash: guard localStorage with lazy initializer.
Direct access during SSR throws ReferenceError.
Apply this diff:
- const [sessionId, setSessionId] = useState(localStorage.getItem(prefixed("session_id")))
+ const [sessionId, setSessionId] = useState<string>(() =>
+ typeof window !== "undefined" ? localStorage.getItem(prefixed("session_id")) ?? "" : ""
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const [sessionId, setSessionId] = useState(localStorage.getItem(prefixed("session_id"))) | |
| const [sessionId, setSessionId] = useState<string>(() => | |
| typeof window !== "undefined" ? localStorage.getItem(prefixed("session_id")) ?? "" : "" | |
| ) |
🤖 Prompt for AI Agents
In src/components/settings/ExternalAPIPage.tsx around lines 54-55, the useState
currently reads localStorage directly which breaks SSR; change it to use a lazy
initializer function that first checks typeof window !== 'undefined' (or checks
for localStorage existence) and only then calls
localStorage.getItem(prefixed("session_id")), otherwise return null (or an
appropriate default), so the initial state is computed safely during server-side
rendering.
| useEffect(() => { | ||
| if (!canEnableExternalApi) { | ||
| alert.error("External API cannot be enabled.","Missing external api supabase environment variables: URL and/or ANON_KEY."); | ||
| return; | ||
| } | ||
| async function fetchSessionId() { | ||
| if (externalApiEnabled === true && !sessionId && sessionId?.trim() == "") { | ||
| const sessionId = await handleConfig(); | ||
| setSessionId(sessionId); | ||
| localStorage.setItem(prefixed("session_id"), sessionId!); | ||
| } | ||
| } | ||
| fetchSessionId(); | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix session bootstrap condition and effect deps.
The condition can never be true due to !sessionId && sessionId?.trim() == "". Also re-run when dependencies change.
Apply this diff:
- useEffect(() => {
+ useEffect(() => {
if (!canEnableExternalApi) {
alert.error("External API cannot be enabled.","Missing external api supabase environment variables: URL and/or ANON_KEY.");
return;
}
async function fetchSessionId() {
- if (externalApiEnabled === true && !sessionId && sessionId?.trim() == "") {
+ if (externalApiEnabled === true && (!sessionId || sessionId.trim() === "")) {
const sessionId = await handleConfig();
setSessionId(sessionId);
localStorage.setItem(prefixed("session_id"), sessionId!);
}
}
fetchSessionId();
- }, []);
+ }, [externalApiEnabled, sessionId, canEnableExternalApi]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| if (!canEnableExternalApi) { | |
| alert.error("External API cannot be enabled.","Missing external api supabase environment variables: URL and/or ANON_KEY."); | |
| return; | |
| } | |
| async function fetchSessionId() { | |
| if (externalApiEnabled === true && !sessionId && sessionId?.trim() == "") { | |
| const sessionId = await handleConfig(); | |
| setSessionId(sessionId); | |
| localStorage.setItem(prefixed("session_id"), sessionId!); | |
| } | |
| } | |
| fetchSessionId(); | |
| }, []); | |
| useEffect(() => { | |
| if (!canEnableExternalApi) { | |
| alert.error("External API cannot be enabled.","Missing external api supabase environment variables: URL and/or ANON_KEY."); | |
| return; | |
| } | |
| async function fetchSessionId() { | |
| if (externalApiEnabled === true && (!sessionId || sessionId.trim() === "")) { | |
| const sessionId = await handleConfig(); | |
| setSessionId(sessionId); | |
| localStorage.setItem(prefixed("session_id"), sessionId!); | |
| } | |
| } | |
| fetchSessionId(); | |
| }, [externalApiEnabled, sessionId, canEnableExternalApi]); |
🤖 Prompt for AI Agents
In src/components/settings/ExternalAPIPage.tsx around lines 66 to 79, the
session bootstrap condition uses "!sessionId && sessionId?.trim() == """, which
can never be true and also shadows sessionId by declaring a new const inside the
async function; update the condition to check for a missing or empty sessionId
like "(!sessionId || sessionId.trim() === \"\")", remove the inner redeclaration
so you call handleConfig() and assign its result to the existing state variable,
and change the useEffect dependency array to include canEnableExternalApi,
externalApiEnabled, sessionId, and alert (so the effect re-runs when these
values change).
| // Apply the config globally for the request | ||
| const configs = await readStore(sessionId, "configs"); | ||
| writeServerConfig(sessionId, configs); | ||
|
|
||
| const { sessionId, inputType, payload, noProcessChat = false } = req.body; | ||
| const currentSessionId = generateSessionId(sessionId); | ||
| const timestamp = new Date().toISOString(); | ||
| if (config("external_api_enabled") !== "true") { | ||
| return sendError(res, sessionId, "API is currently disabled.", 503); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrap config read/gating in try/catch; current errors bypass JSON error path.
readStore/config can throw (e.g., missing Supabase env, network); ensure we return structured 5xx.
Apply this diff:
- // Apply the config globally for the request
- const configs = await readStore(sessionId, "configs");
- writeServerConfig(sessionId, configs);
-
- if (config("external_api_enabled") !== "true") {
- return sendError(res, sessionId, "API is currently disabled.", 503);
- }
+ try {
+ // Apply the config globally for the request
+ const configs = await readStore(sessionId, "configs");
+ writeServerConfig(sessionId, configs);
+ if (config("external_api_enabled") !== "true") {
+ return sendError(res, sessionId, "API is currently disabled.", 503);
+ }
+ } catch (e: any) {
+ return sendError(res, sessionId, `Failed to load configs: ${String(e?.message ?? e)}`, 500);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Apply the config globally for the request | |
| const configs = await readStore(sessionId, "configs"); | |
| writeServerConfig(sessionId, configs); | |
| const { sessionId, inputType, payload, noProcessChat = false } = req.body; | |
| const currentSessionId = generateSessionId(sessionId); | |
| const timestamp = new Date().toISOString(); | |
| if (config("external_api_enabled") !== "true") { | |
| return sendError(res, sessionId, "API is currently disabled.", 503); | |
| } | |
| try { | |
| // Apply the config globally for the request | |
| const configs = await readStore(sessionId, "configs"); | |
| writeServerConfig(sessionId, configs); | |
| if (config("external_api_enabled") !== "true") { | |
| return sendError(res, sessionId, "API is currently disabled.", 503); | |
| } | |
| } catch (e: any) { | |
| return sendError(res, sessionId, `Failed to load configs: ${String(e?.message ?? e)}`, 500); | |
| } |
🤖 Prompt for AI Agents
In src/pages/api/amicaHandler.ts around lines 41 to 47, the call to readStore
and subsequent config gating can throw and currently bypasses the JSON error
response path; wrap the readStore(sessionId, "configs") and
writeServerConfig(sessionId, configs) plus the config("external_api_enabled")
check in a try/catch, catch any error, log it, and return sendError(res,
sessionId, an appropriate message including the error or a generic "Internal
Server Error"), with a 5xx status (e.g., 500) so callers always receive the
structured error; only call writeServerConfig and the enablement check if
readStore succeeded.
| const configFromToken = await readStore(sessionId, "configs") | ||
| const timestamp = new Date().toISOString(); | ||
|
|
||
| // Apply the config globally for the request | ||
| return runWithServerContext({ sessionId }, async () => { | ||
| writeServerConfig(sessionId, configFromToken!); | ||
| if (configs("external_api_enabled") !== "true") { | ||
| return sendError(res, "", "API is currently disabled.", 503); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defensive default for configs; avoid non-null assertion and include sessionId in error.
readStore can return undefined; passing that into writeServerConfig will throw. Also return the sessionId in the 503 error.
Apply:
- const configFromToken = await readStore(sessionId, "configs")
+ const configFromToken = (await readStore(sessionId, "configs")) ?? {};
...
- writeServerConfig(sessionId, configFromToken!);
- if (configs("external_api_enabled") !== "true") {
- return sendError(res, "", "API is currently disabled.", 503);
+ writeServerConfig(sessionId, configFromToken);
+ if (configs("external_api_enabled") !== "true") {
+ return sendError(res, sessionId, "API is currently disabled.", 503);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const configFromToken = await readStore(sessionId, "configs") | |
| const timestamp = new Date().toISOString(); | |
| // Apply the config globally for the request | |
| return runWithServerContext({ sessionId }, async () => { | |
| writeServerConfig(sessionId, configFromToken!); | |
| if (configs("external_api_enabled") !== "true") { | |
| return sendError(res, "", "API is currently disabled.", 503); | |
| } | |
| const configFromToken = (await readStore(sessionId, "configs")) ?? {}; | |
| const timestamp = new Date().toISOString(); | |
| // Apply the config globally for the request | |
| return runWithServerContext({ sessionId }, async () => { | |
| writeServerConfig(sessionId, configFromToken); | |
| if (configs("external_api_enabled") !== "true") { | |
| return sendError(res, sessionId, "API is currently disabled.", 503); | |
| } |
🤖 Prompt for AI Agents
In src/pages/api/mediaHandler.ts around lines 49 to 57, readStore(sessionId,
"configs") may return undefined so avoid the non-null assertion: check if
configFromToken is present before calling writeServerConfig (either provide a
safe default config or return an error), and when returning the 503 response
include the sessionId in the sendError call so the error carries the session
context.
| const wav = new WaveFile(); | ||
| wav.fromScratch(1, 16000, "32f", fileBuffer); | ||
| wav.toBitDepth("16"); | ||
| const audioFile = new File( | ||
| [new Uint8Array(wav.toBuffer())], | ||
| "input.wav", | ||
| { type: "audio/wav" }, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
File/PCM handling on Node: global File may be undefined; WaveFile expects typed arrays.
- Node API routes may not have global
File. Import fromundici. - Convert
BuffertoFloat32ArraybeforefromScratch("32f", …)to avoid corrupt audio.
Apply:
-import formidable from "formidable";
+import formidable from "formidable";
import fs from "fs";
-import { WaveFile } from "wavefile";
+import { WaveFile } from "wavefile";
+import { File } from "undici";- const wav = new WaveFile();
- wav.fromScratch(1, 16000, "32f", fileBuffer);
+ const wav = new WaveFile();
+ const f32 = new Float32Array(
+ fileBuffer.buffer,
+ fileBuffer.byteOffset,
+ Math.floor(fileBuffer.byteLength / 4),
+ );
+ wav.fromScratch(1, 16000, "32f", f32);
wav.toBitDepth("16");
const audioFile = new File(
[new Uint8Array(wav.toBuffer())],
"input.wav",
{ type: "audio/wav" },
);Also applies to: 11-16
🤖 Prompt for AI Agents
In src/pages/api/mediaHandler.ts around lines 99-106 (and also apply the same
fix in lines 11-16), the code uses the global File constructor which may be
undefined in Node and passes a Node Buffer directly to WaveFile.fromScratch with
format "32f" which expects a Float32Array; change to import File from 'undici'
(or otherwise polyfill) and convert fileBuffer (Buffer) to a Float32Array before
calling wav.fromScratch("32f", ...), then create the File using new File([...])
with the Uint8Array of wav.toBuffer() as before.
| } else if (visionBackend === "vision_openai") { | ||
| const message: Message[] = [ | ||
| { role: "user", content: config("vision_system_prompt") }, | ||
| { | ||
| role: "user", | ||
| // @ts-ignore normally this is a string | ||
| content: [ | ||
| { | ||
| type: "text", | ||
| text: "Describe the image as accurately as possible", | ||
| }, | ||
| { | ||
| type: "image_url", | ||
| image_url: { | ||
| url: `data:image/jpeg;base64,${imageData}`, | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| ]; | ||
| res = await getOpenAiVisionChatResponse(message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Verification inconclusive
Fix OpenAI Vision message construction (wrong role; unsafe ts-ignore; hard-coded JPEG).
- System prompt is sent as a user message. Use role "system".
- Avoid
// @ts-ignore. Instead, cast the message array or widen typings. - Don’t force JPEG; detect or allow a configured MIME type.
Proposed patch:
- } else if (visionBackend === "vision_openai") {
- const message: Message[] = [
- { role: "user", content: config("vision_system_prompt") },
- {
- role: "user",
- // @ts-ignore normally this is a string
- content: [
- {
- type: "text",
- text: "Describe the image as accurately as possible",
- },
- {
- type: "image_url",
- image_url: {
- url: `data:image/jpeg;base64,${imageData}`,
- },
- },
- ],
- },
- ];
- res = await getOpenAiVisionChatResponse(message);
+ } else if (visionBackend === "vision_openai") {
+ const imageUrl =
+ imageData.startsWith("data:")
+ ? imageData
+ : `data:${config("vision_image_mime") || "image/jpeg"};base64,${imageData}`;
+
+ const openAiMessages = [
+ { role: "system", content: config("vision_system_prompt") },
+ {
+ role: "user",
+ content: [
+ { type: "text", text: "Describe the image as accurately as possible" },
+ { type: "image_url", image_url: { url: imageUrl } },
+ ],
+ },
+ ] as unknown as Message[];
+
+ res = await getOpenAiVisionChatResponse(openAiMessages);If you prefer a type-safe approach, consider widening Message.content to a union that includes the OpenAI vision content shape instead of casting.
Fix OpenAI Vision message construction: use system role, remove ts-ignore, support configurable MIME.
File: src/utils/askLlm.ts Lines: 199-219
- Send system prompt with role "system" (not "user").
- Remove
// @ts-ignore; widenMessage.contenttyping or cast safely instead of ignoring types. - Accept an existing data URI or construct
data:${config("vision_image_mime") || "image/jpeg"};base64,...instead of hard-coding JPEG.
- } else if (visionBackend === "vision_openai") {
- const message: Message[] = [
- { role: "user", content: config("vision_system_prompt") },
- {
- role: "user",
- // @ts-ignore normally this is a string
- content: [
- {
- type: "text",
- text: "Describe the image as accurately as possible",
- },
- {
- type: "image_url",
- image_url: {
- url: `data:image/jpeg;base64,${imageData}`,
- },
- },
- ],
- },
- ];
- res = await getOpenAiVisionChatResponse(message);
+ } else if (visionBackend === "vision_openai") {
+ const imageUrl =
+ imageData.startsWith("data:")
+ ? imageData
+ : `data:${config("vision_image_mime") || "image/jpeg"};base64,${imageData}`;
+
+ const openAiMessages = [
+ { role: "system", content: config("vision_system_prompt") },
+ {
+ role: "user",
+ content: [
+ { type: "text", text: "Describe the image as accurately as possible" },
+ { type: "image_url", image_url: { url: imageUrl } },
+ ],
+ },
+ ] as unknown as Message[];
+
+ res = await getOpenAiVisionChatResponse(openAiMessages);Widen Message.content to include the vision content shape instead of casting where feasible.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } else if (visionBackend === "vision_openai") { | |
| const message: Message[] = [ | |
| { role: "user", content: config("vision_system_prompt") }, | |
| { | |
| role: "user", | |
| // @ts-ignore normally this is a string | |
| content: [ | |
| { | |
| type: "text", | |
| text: "Describe the image as accurately as possible", | |
| }, | |
| { | |
| type: "image_url", | |
| image_url: { | |
| url: `data:image/jpeg;base64,${imageData}`, | |
| }, | |
| }, | |
| ], | |
| }, | |
| ]; | |
| res = await getOpenAiVisionChatResponse(message); | |
| } else if (visionBackend === "vision_openai") { | |
| const imageUrl = | |
| imageData.startsWith("data:") | |
| ? imageData | |
| : `data:${config("vision_image_mime") || "image/jpeg"};base64,${imageData}`; | |
| const openAiMessages = [ | |
| { role: "system", content: config("vision_system_prompt") }, | |
| { | |
| role: "user", | |
| content: [ | |
| { type: "text", text: "Describe the image as accurately as possible" }, | |
| { type: "image_url", image_url: { url: imageUrl } }, | |
| ], | |
| }, | |
| ] as unknown as Message[]; | |
| res = await getOpenAiVisionChatResponse(openAiMessages); |
🤖 Prompt for AI Agents
In src/utils/askLlm.ts around lines 199 to 219, the OpenAI Vision message is
constructed incorrectly: the system prompt is sent with role "user", a ts-ignore
hides a typing mismatch, and the image MIME is hard-coded to JPEG. Change the
first message to role "system"; remove the // @ts-ignore and either widen
Message.content typing to include the vision content shape or explicitly cast
the content to the correct union type (avoid ignoring type errors); and build
the image URL by accepting an existing data URI if imageData already starts with
"data:" otherwise prefix with `data:${config("vision_image_mime") ||
"image/jpeg"};base64,` so MIME is configurable.
| const message: Message[] = [ | ||
| { role: "user", content: config("vision_system_prompt") }, | ||
| { | ||
| role: "user", | ||
| // @ts-ignore normally this is a string | ||
| content: [ | ||
| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
🧩 Analysis chain
Prefer types over ts-ignore.
If changing Message is viable, update it to a union:
// src/features/chat/messages.ts
export type MessageContent =
| string
| Array<
| { type: "text"; text: string }
| { type: "image_url"; image_url: { url: string; detail?: "low" | "high" } }
>;
export type Message = { role: Role; content: MessageContent };This removes the need for casts and enforces correctness at call sites.
🏁 Script executed:
#!/bin/bash
# Show current Message typing and all places relying on string-only content.
rg -n -C2 'export type Message\b'
rg -n -C2 '\bcontent:\s*string\b' --type=tsLength of output: 1956
Prefer types over ts-ignore.
Change Message.content to a discriminated union and remove the @ts-ignore in src/utils/askLlm.ts; update callsites that currently assume content: string.
// src/features/chat/messages.ts
export type MessageContent =
| string
| Array<
| { type: "text"; text: string }
| { type: "image_url"; image_url: { url: string; detail?: "low" | "high" } }
>;
export type Message = { role: Role; content: MessageContent };- Update these callsites that assume string: src/components/chatLog.tsx (exportMessagesToTxt), src/features/externalAPI/socialMedia/telegramClient.ts (postMessage), src/features/externalAPI/socialMedia/twitterClient.ts (postTweet). Either accept MessageContent or stringify structured content before using external APIs.
…ling in chat processor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
♻️ Duplicate comments (9)
src/features/chat/chat.ts (7)
69-69: Add correctly spelled setter (keep alias).
IntroducesetSubconsciousLogsand keepsetSubconciousLogsas a deprecated alias to stabilize the public API.- public setSubconciousLogs?: React.Dispatch<React.SetStateAction<TimestampedPrompt[]>>; + // Preferred + public setSubconsciousLogs?: React.Dispatch<React.SetStateAction<TimestampedPrompt[]>>; + // Deprecated alias (typo kept for backward-compat) + public setSubconciousLogs?: React.Dispatch<React.SetStateAction<TimestampedPrompt[]>>;Additionally, use a guarded setter at call sites:
const setter = this.setSubconsciousLogs ?? this.setSubconciousLogs; setter?.(prev => /* ... */);
149-149: Assign both the preferred and alias setters.- this.setSubconciousLogs = setSubconciousLogs + this.setSubconsciousLogs = setSubconciousLogs; + this.setSubconciousLogs = setSubconciousLogs; // alias
470-476: Wrap case and don’t throw on animation load failure.
Avoid uncaught exceptions from realtime payloads.- case "animation": - const animation = await loadVRMAnimation(`/animations/${data}`); - if (!animation) throw new Error("Loading animation failed"); - this.viewer?.model?.playAnimation(animation, data); - requestAnimationFrame(() => this.viewer?.resetCameraLerp()); - break; + case "animation": { + const animation = await loadVRMAnimation(`/animations/${data}`); + if (!animation) { + console.error("Loading animation failed"); + break; + } + this.viewer?.model?.playAnimation(animation, data); + requestAnimationFrame(() => this.viewer?.resetCameraLerp()); + break; + }
559-566: Also guard getEaiSupabase() in closeRealtime().
Avoid throwing during teardown when env is missing.- if (this.realtimeChannel) { - console.log("Closing existing Realtime channel..."); - const eaiSupabase = getEaiSupabase(); - await eaiSupabase?.removeChannel(this.realtimeChannel); - this.realtimeChannel = null; - } + if (this.realtimeChannel) { + console.log("Closing existing Realtime channel..."); + try { + const eaiSupabase = getEaiSupabase(); + await eaiSupabase?.removeChannel(this.realtimeChannel); + } catch (err) { + console.warn("Realtime close skipped; Supabase not configured:", err); + } + this.realtimeChannel = null; + }
432-435: Guard getEaiSupabase() to avoid throws when env is missing.
getEaiSupabase()throws on missing env; bail gracefully.- const sessionId = config("session_id"); - const eaiSupabase = getEaiSupabase(); - if (!eaiSupabase) return; + const sessionId = config("session_id"); + let eaiSupabase: SupabaseClient | null = null; + try { + eaiSupabase = getEaiSupabase(); + } catch (err) { + console.warn("Realtime init skipped; Supabase not configured:", err); + return; + } + if (!eaiSupabase) return;
458-469: Wrap switch cases in blocks and await handler (Biome noSwitchDeclarations).
Prevents declaration leakage across cases and races from fire-and-forget handling.- case "normal": - const messages: Message[] = [ + case "normal": { + const messages: Message[] = [ { role: "system", content: config("system_prompt") }, ...this.messageList!, { role: "user", content: data }, ]; - const stream = await getEchoChatResponseStream(messages); + const stream = await getEchoChatResponseStream(messages); this.streams.push(stream); - this.handleChatResponseStream(); - break; + await this.handleChatResponseStream(); + break; + }
520-543: Parse subconscious payload safely and use the guarded setter alias.
Prevents crashes on bad JSON and prefers the new setter while keeping alias.- case "subconscious": - if (config("amica_life_enabled") === "true") { - const prompt = JSON.parse(data) as TimestampedPrompt[]; - - if (this.setSubconciousLogs) { - this.setSubconciousLogs((prevLogs: TimestampedPrompt[]) => { + case "subconscious": { + if (config("amica_life_enabled") === "true") { + let prompt: TimestampedPrompt[] = []; + try { + prompt = JSON.parse(data) as TimestampedPrompt[]; + } catch (e) { + console.warn("Invalid subconscious payload:", e); + break; + } + + const setter = this.setSubconsciousLogs ?? this.setSubconciousLogs; + if (setter) { + setter((prevLogs: TimestampedPrompt[]) => { let updatedLogs = [...prompt]; @@ - return updatedLogs; - }); - } - } - break; + return updatedLogs; + }); + } + } + break; + }src/features/externalAPI/externalAPI.ts (2)
2-2: Do not import Node “crypto” in a browser-shared module; remove duplicate generateSessionId.This file touches localStorage and is likely bundled client-side; importing crypto and defining a local generateSessionId will break browser bundles and duplicates the helper.
Apply:
-import { randomBytes } from "crypto"; +import { generateSessionId } from "./utils/apiHelper"; @@ -export const generateSessionId = (sessionId?: string): string => - sessionId || randomBytes(8).toString("hex"); +// generateSessionId is provided by utils/apiHelper (browser-safe)Also applies to: 10-11
17-18: Harden getEaiSupabase against throws (env missing).getEaiSupabase() throws when env vars are unset. Let this helper swallow-and-log rather than crashing app code paths.
Apply:
- const eaiSupabase = getEaiSupabase() - if (!eaiSupabase) return; + let eaiSupabase = null; + try { + eaiSupabase = getEaiSupabase(); + } catch (err) { + console.warn("Supabase not configured; skipping upsert:", err); + return null; + } + if (!eaiSupabase) return null;
🧹 Nitpick comments (9)
src/features/chat/chat.ts (4)
102-104: Fix Timer type for browser compatibility.
NodeJS.Timeoutbreaks in DOM builds. UseReturnType<typeof setInterval>.- private logUploadInterval: NodeJS.Timeout | null = null; + private logUploadInterval: ReturnType<typeof setInterval> | null = null;
137-137: Param name is misspelled; keep but prefer the new one.
KeepsetSubconciousLogsfor now, but prefer initializing both properties (see assignment below).
545-547: Await updateConfig to avoid races.- case "systemPrompt": - updateConfig("system_prompt", data); - break; + case "systemPrompt": { + await updateConfig("system_prompt", data); + break; + }
573-594: Avoid overlapping log uploads; minor naming nit.
Add an in-flight guard; renamelast50to reflect slicing 20.public async startLogUpload() { @@ - this.logUploadInterval = setInterval(async () => { + let inFlight = false; + this.logUploadInterval = setInterval(async () => { + if (inFlight) return; + inFlight = true; const logs = (window as any).error_handler_logs; const sessionId = config("session_id"); const apiEnabled = config("external_api_enabled"); if (logs?.length && apiEnabled === "true" && sessionId) { try { - const last50 = logs.slice(-20) - await handleLogs(sessionId, last50); + const last20 = logs.slice(-20); + await handleLogs(sessionId, last20); } catch (err) { console.error("Unexpected error during log upload:", err); } } - }, 30_000); // every 30 seconds + inFlight = false; + }, 30_000); // every 30 secondssrc/features/externalAPI/externalAPI.ts (2)
72-79: Wrap Supabase init and consider feature flag gate.addClientEvents will throw if env is missing. Also consider honoring external_api_enabled like upsertToTable does.
Apply:
-export async function addClientEvents(sessionId: string, type: string, data: any) { - const eaiSupabase = getEaiSupabase(); - if (!eaiSupabase) return; +export async function addClientEvents(sessionId: string, type: string, data: any) { + if (config("external_api_enabled") !== "true") return; + let eaiSupabase = null; + try { + eaiSupabase = getEaiSupabase(); + } catch (err) { + console.warn("Supabase not configured; skipping addClientEvents:", err); + return; + } + if (!eaiSupabase) return; await eaiSupabase .schema(SCHEMA) .from("events") .insert({ session_id: sessionId, type, data }); }
83-101: Make deleteAllSessionData resilient and faster (parallel deletes).Catch Supabase init errors and run table deletes concurrently to reduce latency.
Apply:
export async function deleteAllSessionData(sessionId: string) { if (config("external_api_enabled") !== "true") return; - const errors = []; - const eaiSupabase = getEaiSupabase(); - - if(!eaiSupabase) return; - - for (const table of TABLES) { - const { error } = await eaiSupabase - .schema(SCHEMA) - .from(table) - .delete() - .eq("session_id", sessionId); - - if (error) { - console.error(`Failed to delete from "${table}" for session ${sessionId}:`, error.message); - errors.push({ table, error }); - } - } + const errors: any[] = []; + let eaiSupabase = null; + try { + eaiSupabase = getEaiSupabase(); + } catch (err) { + console.warn("Supabase not configured; skipping deleteAllSessionData:", err); + return { success: false, errors: [{ table: "ALL", error: String(err) }] }; + } + if (!eaiSupabase) return { success: false, errors: [{ table: "ALL", error: "No client" }] }; + + const results = await Promise.allSettled( + TABLES.map((table) => + eaiSupabase!.schema(SCHEMA).from(table).delete().eq("session_id", sessionId), + ), + ); + results.forEach((res, idx) => { + if (res.status === "rejected" || (res.status === "fulfilled" && (res.value as any)?.error)) { + const err = res.status === "rejected" ? res.reason : (res.value as any).error; + console.error(`Failed to delete from "${TABLES[idx]}" for session ${sessionId}:`, err?.message || err); + errors.push({ table: TABLES[idx], error: err }); + } + }); return errors.length ? { success: false, errors } : { success: true }; }src/features/externalAPI/processors/chatProcessor.ts (3)
35-41: Await client event inserts to avoid race with subsequent polling.Fire-and-forget risks the poll starting before the client sees the event.
Apply:
- addClientEvents(sessionId,"playback", {time:"10000", "uuid": uuid}); + await addClientEvents(sessionId, "playback", { time: 10000, uuid }); if (animation) { - addClientEvents(sessionId, "animation", animation); + await addClientEvents(sessionId, "animation", animation); }
19-31: Type of socialRes should not be forced to string.handleSocialMediaActions can return objects (e.g., Tweet response). Using string loses type safety.
Apply:
- let socialRes = ""; + let socialRes: unknown = null;
94-96: Await addClientEvents and return an explicit ack.Current function returns undefined; make it deterministic.
Apply:
- let response = addClientEvents(sessionId,"systemPrompt", prompt); - return response; + await addClientEvents(sessionId, "systemPrompt", prompt); + return { success: true };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (4)
package.json(1 hunks)src/features/chat/chat.ts(8 hunks)src/features/externalAPI/externalAPI.ts(1 hunks)src/features/externalAPI/processors/chatProcessor.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/features/externalAPI/processors/chatProcessor.ts (5)
src/utils/askLlm.ts (1)
askLLM(15-176)src/utils/config.ts (1)
config(130-157)src/features/externalAPI/utils/socialMediaHandler.ts (1)
handleSocialMediaActions(4-33)src/utils/supabase.ts (1)
getEaiSupabase(11-25)src/features/externalAPI/externalAPI.ts (1)
addClientEvents(72-79)
src/features/externalAPI/externalAPI.ts (5)
src/features/externalAPI/utils/apiHelper.ts (1)
generateSessionId(20-21)src/utils/config.ts (3)
config(130-157)defaults(3-124)prefixed(126-128)src/utils/supabase.ts (1)
getEaiSupabase(11-25)src/features/chat/messages.ts (1)
Message(4-7)src/features/amicaLife/eventHandler.ts (1)
TimestampedPrompt(40-43)
src/features/chat/chat.ts (6)
src/features/amicaLife/eventHandler.ts (2)
TimestampedPrompt(40-43)MAX_STORAGE_TOKENS(37-37)src/utils/config.ts (2)
config(130-157)updateConfig(159-175)src/features/externalAPI/externalAPI.ts (2)
handleUserInput(44-49)handleLogs(65-70)src/utils/supabase.ts (1)
getEaiSupabase(11-25)src/features/chat/messages.ts (1)
Message(4-7)src/features/chat/echoChat.ts (1)
getEchoChatResponseStream(3-25)
🪛 Biome (2.1.2)
src/features/chat/chat.ts
[error] 460-464: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 465-465: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 471-471: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 479-479: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 480-480: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 481-481: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 482-482: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (6)
src/features/chat/chat.ts (5)
45-45: LGTM: external API imports aligned with new flow.
47-49: LGTM: new types/imports for realtime and logs.
158-159: LGTM: realtime init hooked into initialize().
397-403: LGTM: user input upsert is gated by external_api_enabled and includes system + user-only history.
449-453: Confirm quoted schema "external-api" exists or rename to "external_api"
Repo docs and code consistently use the quoted schema "external-api" (docs/api/api-reference.md; src/features/externalAPI/*; src/features/chat/chat.ts:450). PostgreSQL requires hyphenated identifiers to be quoted — this will only work if the DB/schema/tables are created exactly as "external-api". Confirm the DB has that quoted schema, or rename to external_api (or use public) and update references.src/features/externalAPI/processors/chatProcessor.ts (1)
45-52: Confirm recordings table schema; query currently targets default schema.Elsewhere you use schema("external-api"). If recordings lives there, add .schema("external-api") here to avoid silent misses.
Would you like a patch to add .schema("external-api") once you confirm the table’s schema?
| "@types/react": "^18.3.10", | ||
| "@types/react-dom": "^18.3.0", | ||
| "@types/three": "^0.169.0", | ||
| "@types/uuid": "^10.0.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Remove @types/uuid and add runtime uuid dependency (current code imports uuid at runtime).
Type definitions are bundled with uuid; the separate @types package is unnecessary and can cause type conflicts. Also, uuid is not listed under dependencies, which will break at runtime in chatProcessor.
Apply this diff:
@@ "dependencies": {
"twitter-api-v2": "^1.19.0",
"typescript-collections": "^1.3.3",
"wavefile": "^11.0.0",
"webm-duration-fix": "^1.0.4",
"window.ai": "^0.2.4",
"ws": "^8.16.0"
+ ,"uuid": "^10.0.0"
},
@@ "devDependencies": {
- "@types/uuid": "^10.0.0",Alternatively (fewer deps): switch the code to use Node’s/Browsers’ crypto.randomUUID() and drop uuid entirely (see chatProcessor comment).
Run to confirm uuid usage and missing dep:
🏁 Script executed:
#!/bin/bash
jq -r '.dependencies.uuid // "MISSING"' package.json
rg -nP --type ts '\buuidv4\b|\bfrom\s+"uuid"' -SLength of output: 198
Add uuid to dependencies and remove @types/uuid from devDependencies
- package.json: add "uuid": "^10.0.0" under dependencies and remove "@types/uuid" from devDependencies (uuid bundles its types).
- Fix required: src/features/externalAPI/processors/chatProcessor.ts — import at line 6 and usage at line 35; missing runtime dep will break.
- Alternative: replace uuidv4() with crypto.randomUUID() to drop the dependency.
🤖 Prompt for AI Agents
In package.json around line 83, the project currently only has "@types/uuid" in
devDependencies which will break at runtime because
src/features/externalAPI/processors/chatProcessor.ts imports uuid at line 6 and
calls uuidv4() at line 35; either add "uuid": "^10.0.0" to dependencies and
remove "@types/uuid" from devDependencies (uuid bundles its types), or modify
chatProcessor.ts to remove the uuid import and replace uuidv4() with the
built-in crypto.randomUUID() to avoid adding the dependency.
| case "playback": | ||
| this.viewer?.startRecording(); | ||
| const payload = JSON.parse(data); | ||
| const recordingTimeout = Number(payload.time); | ||
| const fileId = `${sessionId}-${payload.uuid}`; | ||
| const fileName = `${fileId}.webm`; | ||
|
|
||
| setTimeout(async () => { | ||
| this.viewer?.stopRecording(async (videoBlob) => { | ||
| if (!videoBlob) { | ||
| console.error("No video blob available to upload."); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const { error: uploadError } = await eaiSupabase.storage | ||
| .from("files") | ||
| .upload(fileName, videoBlob, { contentType: "video/webm" }); | ||
|
|
||
| if (uploadError) throw uploadError; | ||
|
|
||
| const { data: urlData } = eaiSupabase.storage | ||
| .from("files") | ||
| .getPublicUrl(fileName); | ||
|
|
||
| if (!urlData?.publicUrl) throw new Error("Failed to get public URL for recording."); | ||
|
|
||
| const { error: dbError } = await eaiSupabase | ||
| .from("recordings") | ||
| .insert({ | ||
| id: fileId, | ||
| session_id: sessionId, | ||
| file_path: urlData.publicUrl, | ||
| }); | ||
|
|
||
| if (dbError) throw dbError; | ||
| } catch (err) { | ||
| console.error("Failed to upload recording:", err); | ||
| } | ||
| }); | ||
| }, recordingTimeout); | ||
| break; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Harden playback payload parsing and duration validation; wrap case.
Avoids crashes on bad JSON, name shadowing, and invalid timeouts.
- case "playback":
- this.viewer?.startRecording();
- const payload = JSON.parse(data);
- const recordingTimeout = Number(payload.time);
- const fileId = `${sessionId}-${payload.uuid}`;
- const fileName = `${fileId}.webm`;
-
- setTimeout(async () => {
+ case "playback": {
+ this.viewer?.startRecording();
+ let parsed: { time: unknown; uuid: string } | null = null;
+ try {
+ parsed = JSON.parse(data);
+ } catch (e) {
+ console.warn("Invalid playback payload:", e);
+ break;
+ }
+ const ms = Number((parsed as any).time);
+ if (!Number.isFinite(ms) || ms < 0) {
+ console.warn("Invalid playback duration:", (parsed as any).time);
+ break;
+ }
+ const fileId = `${sessionId}-${(parsed as any).uuid}`;
+ const fileName = `${fileId}.webm`;
+
+ setTimeout(async () => {
this.viewer?.stopRecording(async (videoBlob) => {
if (!videoBlob) {
console.error("No video blob available to upload.");
return;
}
@@
- }, recordingTimeout);
- break;
+ }, ms);
+ break;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| case "playback": | |
| this.viewer?.startRecording(); | |
| const payload = JSON.parse(data); | |
| const recordingTimeout = Number(payload.time); | |
| const fileId = `${sessionId}-${payload.uuid}`; | |
| const fileName = `${fileId}.webm`; | |
| setTimeout(async () => { | |
| this.viewer?.stopRecording(async (videoBlob) => { | |
| if (!videoBlob) { | |
| console.error("No video blob available to upload."); | |
| return; | |
| } | |
| try { | |
| const { error: uploadError } = await eaiSupabase.storage | |
| .from("files") | |
| .upload(fileName, videoBlob, { contentType: "video/webm" }); | |
| if (uploadError) throw uploadError; | |
| const { data: urlData } = eaiSupabase.storage | |
| .from("files") | |
| .getPublicUrl(fileName); | |
| if (!urlData?.publicUrl) throw new Error("Failed to get public URL for recording."); | |
| const { error: dbError } = await eaiSupabase | |
| .from("recordings") | |
| .insert({ | |
| id: fileId, | |
| session_id: sessionId, | |
| file_path: urlData.publicUrl, | |
| }); | |
| if (dbError) throw dbError; | |
| } catch (err) { | |
| console.error("Failed to upload recording:", err); | |
| } | |
| }); | |
| }, recordingTimeout); | |
| break; | |
| case "playback": { | |
| this.viewer?.startRecording(); | |
| let parsed: { time: unknown; uuid: string } | null = null; | |
| try { | |
| parsed = JSON.parse(data); | |
| } catch (e) { | |
| console.warn("Invalid playback payload:", e); | |
| break; | |
| } | |
| const ms = Number((parsed as any).time); | |
| if (!Number.isFinite(ms) || ms < 0) { | |
| console.warn("Invalid playback duration:", (parsed as any).time); | |
| break; | |
| } | |
| const fileId = `${sessionId}-${(parsed as any).uuid}`; | |
| const fileName = `${fileId}.webm`; | |
| setTimeout(async () => { | |
| this.viewer?.stopRecording(async (videoBlob) => { | |
| if (!videoBlob) { | |
| console.error("No video blob available to upload."); | |
| return; | |
| } | |
| try { | |
| const { error: uploadError } = await eaiSupabase.storage | |
| .from("files") | |
| .upload(fileName, videoBlob, { contentType: "video/webm" }); | |
| if (uploadError) throw uploadError; | |
| const { data: urlData } = eaiSupabase.storage | |
| .from("files") | |
| .getPublicUrl(fileName); | |
| if (!urlData?.publicUrl) throw new Error("Failed to get public URL for recording."); | |
| const { error: dbError } = await eaiSupabase | |
| .from("recordings") | |
| .insert({ | |
| id: fileId, | |
| session_id: sessionId, | |
| file_path: urlData.publicUrl, | |
| }); | |
| if (dbError) throw dbError; | |
| } catch (err) { | |
| console.error("Failed to upload recording:", err); | |
| } | |
| }); | |
| }, ms); | |
| break; | |
| } |
🧰 Tools
🪛 Biome (2.1.2)
[error] 479-479: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 480-480: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 481-481: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 482-482: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
| const sessionId = generateSessionId(); | ||
| let data: Record<string, string> = {}; | ||
|
|
||
| for (const key in defaults) { | ||
| const localKey = prefixed(key); | ||
| data[key] = localStorage.getItem(localKey) ?? (defaults as any)[key]; | ||
| } | ||
|
|
||
| fetch(userInputUrl, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| systemPrompt: config("system_prompt"), | ||
| message: message, | ||
| }), | ||
| await upsertToTable("configs", { session_id: sessionId, data }); | ||
| return sessionId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard localStorage for SSR and avoid for..in over objects.
On the server, localStorage is undefined. Also, prefer Object.keys to skip prototype props.
Apply:
- let data: Record<string, string> = {};
-
- for (const key in defaults) {
- const localKey = prefixed(key);
- data[key] = localStorage.getItem(localKey) ?? (defaults as any)[key];
- }
+ const data: Record<string, string> = {};
+ const hasLS = typeof window !== "undefined" && typeof localStorage !== "undefined";
+ for (const key of Object.keys(defaults)) {
+ const localKey = prefixed(key);
+ data[key] = hasLS ? (localStorage.getItem(localKey) ?? (defaults as any)[key]) : (defaults as any)[key];
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const sessionId = generateSessionId(); | |
| let data: Record<string, string> = {}; | |
| for (const key in defaults) { | |
| const localKey = prefixed(key); | |
| data[key] = localStorage.getItem(localKey) ?? (defaults as any)[key]; | |
| } | |
| fetch(userInputUrl, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| systemPrompt: config("system_prompt"), | |
| message: message, | |
| }), | |
| await upsertToTable("configs", { session_id: sessionId, data }); | |
| return sessionId; | |
| const sessionId = generateSessionId(); | |
| const data: Record<string, string> = {}; | |
| const hasLS = typeof window !== "undefined" && typeof localStorage !== "undefined"; | |
| for (const key of Object.keys(defaults)) { | |
| const localKey = prefixed(key); | |
| data[key] = hasLS ? (localStorage.getItem(localKey) ?? (defaults as any)[key]) : (defaults as any)[key]; | |
| } | |
| await upsertToTable("configs", { session_id: sessionId, data }); | |
| return sessionId; |
🤖 Prompt for AI Agents
In src/features/externalAPI/externalAPI.ts around lines 32 to 41, the loop reads
localStorage directly with a for..in loop which breaks in SSR and can enumerate
prototype properties; replace the for..in with Object.keys(defaults) and guard
localStorage access by checking typeof window !== "undefined" and typeof
window.localStorage !== "undefined" (or wrap getItem in a try/catch) before
calling getItem; for each key, compute localKey via prefixed(key), read
localStorage only when available (falling back to (defaults as any)[key]) and
assign into data using the key from Object.keys to avoid prototype pollution,
then continue to upsert and return sessionId as before.
| export async function handleUserInput(messages: Message[]) { | ||
| await upsertToTable("user_input_messages", { | ||
| session_id: config("session_id"), | ||
| data: messages, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
🧩 Analysis chain
Don’t rely on config("session_id") here; accept sessionId explicitly.
Config may not contain session_id and can throw, while other handlers already take sessionId. Make this API consistent.
Apply:
-export async function handleUserInput(messages: Message[]) {
+export async function handleUserInput(sessionId: string, messages: Message[]) {
await upsertToTable("user_input_messages", {
- session_id: config("session_id"),
+ session_id: sessionId,
data: messages,
});
}Update all call sites to pass sessionId. Run:
🏁 Script executed:
#!/bin/bash
rg -nP --glob '!**/node_modules/**' '\bhandleUserInput\s*\(' -S -C2Length of output: 148
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)"
echo "Top-level files:"
ls -la || true
echo
echo "----- rg: search for handleUserInput (calls) -----"
rg -nS '\bhandleUserInput\s*\(' -uu || true
echo
echo "----- rg: search for handleUserInput (definition) -----"
rg -nS 'function\s+handleUserInput\b' -uu || true
echo
echo "----- rg: search for user_input_messages -----"
rg -nS 'user_input_messages' -uu || true
echo
echo "----- rg: search for upsertToTable -----"
rg -nS 'upsertToTable\(' -uu || true
echo
echo "----- rg: search for session_id occurrences -----"
rg -nS 'session_id' -uu || true
echo
echo "----- rg: search for config usages -----"
rg -nS 'config\(' -uu || true
echo
echo "----- Show src/features/externalAPI/externalAPI.ts if present -----"
if [ -f src/features/externalAPI/externalAPI.ts ]; then
sed -n '1,240p' src/features/externalAPI/externalAPI.ts
else
echo "file not found: src/features/externalAPI/externalAPI.ts"
fiLength of output: 62025
Make handleUserInput (and similar handlers) accept sessionId explicitly and update call sites.
Don't rely on config("session_id") — it can be missing; other handlers accept sessionId. Change signatures and use the passed sessionId.
Apply:
-export async function handleUserInput(messages: Message[]) {
+export async function handleUserInput(sessionId: string, messages: Message[]) {
await upsertToTable("user_input_messages", {
- session_id: config("session_id"),
+ session_id: sessionId,
data: messages,
});
}Also update handleChatLogs:
-export async function handleChatLogs(messages: Message[]) {
+export async function handleChatLogs(sessionId: string, messages: Message[]) {
await upsertToTable("chat_logs", {
- session_id: config("session_id"),
+ session_id: sessionId,
data: messages,
});
}Update all call sites to pass sessionId (call found at src/features/chat/chat.ts:398). Use grep to find remaining usages:
rg -nP --glob '!/node_modules/' '\bhandle(UserInput|ChatLogs)\s*(' -S -C2
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function handleUserInput(messages: Message[]) { | |
| await upsertToTable("user_input_messages", { | |
| session_id: config("session_id"), | |
| data: messages, | |
| }); | |
| } | |
| export async function handleUserInput(sessionId: string, messages: Message[]) { | |
| await upsertToTable("user_input_messages", { | |
| session_id: sessionId, | |
| data: messages, | |
| }); | |
| } | |
| export async function handleChatLogs(sessionId: string, messages: Message[]) { | |
| await upsertToTable("chat_logs", { | |
| session_id: sessionId, | |
| data: messages, | |
| }); | |
| } |
🤖 Prompt for AI Agents
In src/features/externalAPI/externalAPI.ts around lines 44 to 49, change
handleUserInput to accept a sessionId parameter instead of reading
config("session_id"), update the function to use that sessionId when calling
upsertToTable, and do the same for handleChatLogs (make both handlers accept
sessionId explicitly and use it in their table upserts); then update all call
sites to pass the sessionId (notably the call in src/features/chat/chat.ts at
line ~398) and run the suggested grep command to find and fix any remaining
usages, ensuring TypeScript signatures/types are updated accordingly.
| await upsertToTable("chat_logs", { | ||
| session_id: config("session_id"), | ||
| data: messages, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue: pass sessionId as param for chat logs.
Apply:
-export async function handleChatLogs(messages: Message[]) {
+export async function handleChatLogs(sessionId: string, messages: Message[]) {
await upsertToTable("chat_logs", {
- session_id: config("session_id"),
+ session_id: sessionId,
data: messages,
});
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| await upsertToTable("chat_logs", { | |
| session_id: config("session_id"), | |
| data: messages, | |
| }); | |
| } | |
| export async function handleChatLogs(sessionId: string, messages: Message[]) { | |
| await upsertToTable("chat_logs", { | |
| session_id: sessionId, | |
| data: messages, | |
| }); | |
| } |
🤖 Prompt for AI Agents
In src/features/externalAPI/externalAPI.ts around lines 52 to 56, the chat log
upsert is using config("session_id") instead of the sessionId passed into the
caller; update the function to accept a sessionId parameter (if not already) and
replace config("session_id") with that sessionId variable when calling
upsertToTable("chat_logs", { session_id: sessionId, data: messages }); so the
correct session identifier is persisted.
|
|
||
| import { NextApiRequest } from "next"; | ||
| import { addClientEvents } from "../externalAPI"; | ||
| import { v4 as uuidv4 } from "uuid"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Runtime import of uuid but package.json lacks uuid dependency.
This will crash at runtime (“Cannot find module 'uuid'”). Either add uuid to dependencies (preferred if used elsewhere) or switch to crypto.randomUUID().
Option A (keep uuid; ensure dep present — see package.json comment).
Option B (drop dependency):
-import { v4 as uuidv4 } from "uuid";
+import { randomUUID } from "crypto";And below:
- const uuid = uuidv4().split("-")[0];
+ const uuid = randomUUID().split("-")[0];Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/features/externalAPI/processors/chatProcessor.ts around line 6, the file
imports uuid (import { v4 as uuidv4 } from "uuid") but package.json does not
include uuid, causing a runtime "Cannot find module 'uuid'"; either add uuid to
dependencies (preferred if used across the project) by running npm install
--save uuid (or yarn add uuid) and commit package.json/package-lock.json, or
remove the import and replace usages of uuidv4() with the built-in
crypto.randomUUID() (import crypto from 'crypto' or use
globalThis.crypto.randomUUID() depending on target runtime) to avoid adding a
dependency. Ensure tests/build run after the change and update any import/usage
sites consistently.
Summary by CodeRabbit
New Features
Documentation
Refactor
Chores