Skip to content

Conversation

@xampleth
Copy link

@xampleth xampleth commented Jul 15, 2025

  • Removed old dataHandlerStorage JSON files and related utilities
  • Updated ExternalAPIPage to validate environment variables before enabling
  • Replaced requestHandler and dataHelper with Supabase-backed logic
  • Updated AmicaLife, processors, and social media clients to use new API
  • Adjusted chat and media handling for new architecture

Summary by CodeRabbit

  • New Features

    • External API now uses Supabase Realtime with a session-based workflow; Session ID shown with copy/refresh.
    • Settings UI adds credential fields for X/Twitter and Telegram.
    • Per-session media handling and a new OpenAI Vision backend for image processing.
  • Documentation

    • API docs updated to require sessionId across routes; removed the old dataHandler route.
    • Added “Local Supabase Setup (For Developers)” and revised External API instructions.
  • Refactor

    • Replaced SSE/local JSON storage and client remote logging with Supabase-backed persistence.
  • Chores

    • Added public env vars for Supabase URL and Anon Key.

@vercel
Copy link

vercel bot commented Jul 15, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
amica Ready Ready Preview Comment Sep 18, 2025 5:40pm
amica-agent-framework Error Error Sep 18, 2025 5:40pm
arbius.heyamica.com Ready Ready Preview Comment Sep 18, 2025 5:40pm

@vercel
Copy link

vercel bot commented Jul 15, 2025

@flukexp is attempting to deploy a commit to the heyamica Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

Walkthrough

Migrates 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

Cohort / File(s) Summary of Changes
Environment & Supabase
/.env.local, src/utils/supabase.ts
Added NEXT_PUBLIC_EAI_SUPABASE_URL and NEXT_PUBLIC_EAI_SUPABASE_ANON_KEY; added getEaiSupabase() that lazily builds/caches a Supabase client.
Documentation
docs/api/api-reference.md
Reworked External API docs: replaced SSE wording with Supabase Realtime, added Local Supabase setup, standardized sessionId usage, removed /api/dataHandler docs.
Debug Logging
public/debugLogger.js
Removed client POSTs to /api/dataHandler; retained accumulation in window.error_handler_logs.
Settings UI
src/components/settings.tsx, src/components/settings/ExternalAPIPage.tsx
Added X/TG credential props & setters, sessionId state, env guard (canEnableExternalApi), realtime init/close lifecycle, Refresh token flow, and SecretTextInput fields.
AmicaLife & Event Handling
src/features/amicaLife/amicaLife.ts, src/features/amicaLife/eventHandler.ts
Converted setSubconciousLogs to React state dispatcher; centralized subconscious log updates via AmicaLife setter; removed module-scoped storedSubconcious; call external handler with sessionId.
Chat Realtime Migration
src/features/chat/chat.ts, src/pages/index.tsx
Replaced SSE with Supabase Realtime (initRealtime/closeRealtime), added startLogUpload, new realtime event types (normal, animation, playback, subconscious, systemPrompt), subconscious token capping, and initialize now accepts setSubconciousLogs.
External API Storage Overhaul
src/features/externalAPI/externalAPI.ts, src/features/externalAPI/memoryStore.ts, src/features/externalAPI/serverContext.ts, src/features/externalAPI/utils/apiHelper.ts, src/features/externalAPI/utils/requestHandler.ts, src/features/externalAPI/dataHelper.ts, src/features/externalAPI/dataHandlerStorage/*
Removed file-based data helpers and JSON storage; added Supabase-backed upsert model, generateSessionId, handleLogs/addClientEvents/deleteAllSessionData; added memoryStore read/update helpers and AsyncLocalStorage server context; removed SSE/file IO helpers.
Processors & Social Media
src/features/externalAPI/processors/chatProcessor.ts, src/features/externalAPI/utils/socialMediaHandler.ts, src/features/externalAPI/socialMedia/twitterClient.ts, src/features/externalAPI/socialMedia/telegramClient.ts, src/features/externalAPI/processors/imageProcessor.ts
APIs now accept sessionId (and req when needed); social handlers use getTwitterClient/getTelegramClient lazy factories and config-based credentials; post methods return responses/errors; image conversion sets JPEG quality to 70.
API Routes
src/pages/api/amicaHandler.ts, src/pages/api/mediaHandler.ts, src/pages/api/dataHandler.ts
Removed /api/dataHandler; amicaHandler reworked to require sessionId, use memoryStore, return standardized responses and export apiLogs; mediaHandler reworked to parse multipart, runWithServerContext(sessionId), use per-session configs, tighter validation, and structured logging.
Config System
src/utils/config.ts
Defaults extended (session_id, telegram_chat_id), server-side reads now use serverContext + readServerConfig, updateConfig persists via updateStore only when external_api_enabled is "true", and removed startup side-effects.
LLM Vision & Utils
src/utils/askLlm.ts, src/features/chat/openAiChat.ts
Added vision_openai path using getOpenAiVisionChatResponse; introduced getOpenAiVisionChatResponse(messages) → Promise.
Package Types
package.json
Added devDependency @types/uuid.

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
Loading
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 }
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Poem

I twitch my nose, a session springs,
Supabase threads and realtime wings.
Logs hop in burrows, tidy, neat,
SSE retired — what a feat!
I nibble keys and cheer, then dash — swift feet. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Refactor external api feature" succinctly and accurately reflects the PR's primary change: a broad refactor of the external API subsystem. The raw_summary and PR objectives show cohesive, high-impact work (Supabase-backed persistence, removal of legacy data handlers/storage, new server context, and updates to clients and processors) that align with the title. It is concise and clear enough that a teammate scanning history will understand the main intent.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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.ts

currentStreamIdx 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 undefined

In the else branch, viewer can be falsy; calling viewer.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 callers

Ensure 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 for vision_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 askVisionLLM accepts 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 messages for 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 getEaiSupabase

The 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 initialization

Avoid 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 Realtime

This 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 terminology

The 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 gating

Add 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_KEY before NEXT_PUBLIC_EAI_SUPABASE_URL as 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

alert is 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,,rvcResampleSr introduces 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/setXAPISecret to setXApiKey/setXApiSecret for consistency.

src/features/externalAPI/utils/socialMediaHandler.ts (2)

5-7: Parameter ‘req’ is unused; either use or mark as intentionally unused.

Rename to _req to 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.

addClientEvents is 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: req is 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 animationList is 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 unused noProcessChat (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: Make canEnableExternalApi explicitly 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; these if(!eaiSupabase) branches won’t run.

Either remove the checks or change getEaiSupabase to return null rather than throwing. Keep behavior consistent.

Also applies to: 70-73


5-14: In‑memory serverConfig can 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 from readStore.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 75c19b9 and 9cef8d7.

📒 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 has url: \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 getOpenAiVisionChatResponse import is correctly referenced below.

src/features/externalAPI/utils/apiHelper.ts (1)

20-28: LGTM: leaner server helper

Simplified 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.initialize

Passing a React state dispatcher is consistent with the new realtime updates path.

Please confirm Chat.initialize accepts this extra parameter across all call sites.

src/features/amicaLife/amicaLife.ts (1)

25-26: Type update for setSubconciousLogs looks correct

Storing 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 found

runWithServerContext 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.

Comment on lines 4 to 5
NEXT_PUBLIC_SUPABASE_URL=https://kzfcwefqffysqsjrlyld.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imt6ZmN3ZWZxZmZ5c3FzanJseWxkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDEyNzA5NjUsImV4cCI6MjAxNjg0Njk2NX0.z9zm2mbJu6RkdZ6zkCUltThIF43-ava_bLvk9qFQsiA
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +65 to +110
```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)
);
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
```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.

Comment on lines +180 to 187
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"));

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Comment on lines +54 to +55
const [sessionId, setSessionId] = useState(localStorage.getItem(prefixed("session_id")))

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +66 to +79
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();
}, []);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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).

Comment on lines +41 to +47
// 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);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested 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);
}
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.

Comment on lines +49 to +57
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);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +99 to +106
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" },
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

File/PCM handling on Node: global File may be undefined; WaveFile expects typed arrays.

  • Node API routes may not have global File. Import from undici.
  • Convert Buffer to Float32Array before fromScratch("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.

Comment on lines +199 to +219
} 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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

❓ 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; widen Message.content typing 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.

Suggested change
} 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.

Comment on lines +200 to +206
const message: Message[] = [
{ role: "user", content: config("vision_system_prompt") },
{
role: "user",
// @ts-ignore normally this is a string
content: [
{
Copy link

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=ts

Length 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.

Copy link

@coderabbitai coderabbitai bot left a 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).
Introduce setSubconsciousLogs and keep setSubconciousLogs as 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.Timeout breaks in DOM builds. Use ReturnType<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.
Keep setSubconciousLogs for 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; rename last50 to 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 seconds
src/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

📥 Commits

Reviewing files that changed from the base of the PR and between 9cef8d7 and 1c3a2e4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is 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",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 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"' -S

Length 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.

Comment on lines +477 to +519
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;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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)

Comment on lines +32 to +41
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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +44 to 49
export async function handleUserInput(messages: Message[]) {
await upsertToTable("user_input_messages", {
session_id: config("session_id"),
data: messages,
});
}
Copy link

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 -C2

Length 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"
fi

Length 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.

Suggested change
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.

Comment on lines +52 to 56
await upsertToTable("chat_logs", {
session_id: config("session_id"),
data: messages,
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants