diff --git a/.github/workflows/sanity-check.yml b/.github/workflows/sanity-check.yml index 7a19885..028e332 100644 --- a/.github/workflows/sanity-check.yml +++ b/.github/workflows/sanity-check.yml @@ -24,7 +24,7 @@ jobs: with: fetch-depth: 1 - - uses: actions/setup-node@v3 + - uses: oven-sh/setup-bun@v2 - - run: npm install - - run: npm run check + - run: bun install + - run: bun run check diff --git a/README.md b/README.md index 871d043..d14d373 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ - Screenshot 2026-01-15 at 5 49 27 PM - Voice assistant with persistent memory powered by [Supermemory](https://supermemory.ai) and [Pipecat](https://pipecat.ai). ## Quick Start @@ -18,7 +16,7 @@ SUPERMEMORY_API_KEY= ```bash bun run dev:backend -bun run dev +bun run dev ``` ## Pipecat Memory Integration diff --git a/backend/requirements.txt b/backend/requirements.txt index 07c46f6..e33b4fb 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -3,6 +3,6 @@ uvicorn[standard] websockets python-dotenv loguru -pipecat-ai[openai,silero] +pipecat-ai[google,silero] protobuf supermemory-pipecat>=0.1.0 diff --git a/backend/server.py b/backend/server.py index a74a2c0..b82aa29 100644 --- a/backend/server.py +++ b/backend/server.py @@ -1,37 +1,37 @@ -""" -Pipecat + Supermemory Voice Bot Server - -This server uses Supermemory for memory storage. -Works with the official @pipecat-ai/client-js SDK. -Supports multiple users via query params. -""" - import os import sys import uuid -from dotenv import load_dotenv -from loguru import logger -from fastapi import FastAPI, WebSocket, Request, Query +from dotenv import load_dotenv +from fastapi import FastAPI, Query, Request, WebSocket from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse - +from google.genai.types import EndSensitivity, StartSensitivity +from loguru import logger from pipecat.audio.vad.silero import SileroVADAnalyzer from pipecat.frames.frames import LLMMessagesFrame from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.task import PipelineParams, PipelineTask -from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext -from pipecat.processors.frameworks.rtvi import RTVIConfig, RTVIObserver, RTVIProcessor +from pipecat.processors.aggregators.llm_response_universal import ( + LLMContextAggregatorPair, +) +from pipecat.processors.aggregators.llm_context import LLMContext +from pipecat.processors.frameworks.rtvi import ( + RTVIConfig, + RTVIObserver, + RTVIProcessor, +) from pipecat.serializers.protobuf import ProtobufFrameSerializer -from pipecat.services.openai.llm import OpenAILLMService -from pipecat.services.openai.tts import OpenAITTSService -from pipecat.services.openai.stt import OpenAISTTService +from pipecat.services.google.gemini_live.llm import ( + GeminiLiveLLMService, + GeminiVADParams, + InputParams, +) from pipecat.transports.websocket.fastapi import ( FastAPIWebsocketParams, FastAPIWebsocketTransport, ) - from supermemory_pipecat import SupermemoryPipecatService load_dotenv(override=True) @@ -41,7 +41,6 @@ app = FastAPI(title="Pipecat + Supermemory Voice Bot") -# Get allowed origins from env or default to all ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS", "*").split(",") app.add_middleware( @@ -55,12 +54,10 @@ SYSTEM_PROMPT = """You are a helpful voice assistant with memory capabilities. You remember information from past conversations and use it to provide personalized responses. Keep your responses brief and conversational - one or two sentences at most. -Your output will be converted to audio so don't include special characters.""" +Your output will be converted to audio so don't include special characters. Ask for their name and greet them. Only speak when you need to. When giving an introduction, just say something along the lines of 'I am a memory assistant powered by supermemory'. What's your name? (don't ask the name if you already know it but you get the point)""" async def run_bot(websocket_client, user_id: str, session_id: str): - """Run the voice bot pipeline with Supermemory for a specific user.""" - logger.info(f"Starting bot for user: {user_id}, session: {session_id}") transport = FastAPIWebsocketTransport( @@ -76,21 +73,26 @@ async def run_bot(websocket_client, user_id: str, session_id: str): ), ) - stt = OpenAISTTService( - api_key=os.getenv("OPENAI_API_KEY"), - ) - - llm = OpenAILLMService( - api_key=os.getenv("OPENAI_API_KEY"), - model="gpt-4o-mini", - ) - - tts = OpenAITTSService( - api_key=os.getenv("OPENAI_API_KEY"), - voice="alloy", + gemini_api_key = os.getenv("GEMINI_API_KEY") + if not gemini_api_key: + raise ValueError("GEMINI_API_KEY is not set") + + llm = GeminiLiveLLMService( + api_key=gemini_api_key, + voice_id="Puck", + system_instruction=SYSTEM_PROMPT, + inference_on_context_initialization=True, + params=InputParams( + vad=GeminiVADParams( + disabled=False, + start_sensitivity=StartSensitivity.START_SENSITIVITY_HIGH, + end_sensitivity=EndSensitivity.END_SENSITIVITY_HIGH, + prefix_padding_ms=300, + silence_duration_ms=500, + ), + ), ) - # Supermemory service with user-specific context memory = SupermemoryPipecatService( api_key=os.getenv("SUPERMEMORY_API_KEY"), user_id=user_id, @@ -99,32 +101,24 @@ async def run_bot(websocket_client, user_id: str, session_id: str): search_limit=10, search_threshold=0.1, mode="full", - add_memory="always", ), ) - messages = [ - { - "role": "system", - "content": SYSTEM_PROMPT, - }, - ] - context = OpenAILLMContext(messages) - context_aggregator = llm.create_context_aggregator(context) + context = LLMContext([{"role": "system", "content": SYSTEM_PROMPT}]) + user_aggregator, assistant_aggregator = LLMContextAggregatorPair(context) rtvi = RTVIProcessor(config=RTVIConfig(config=[])) + # Pipeline: RTVI before output transport for proper client communication pipeline = Pipeline( [ transport.input(), - stt, rtvi, - context_aggregator.user(), - memory, + user_aggregator, + memory, # Memory receives context frames with user messages llm, - tts, transport.output(), - context_aggregator.assistant(), + assistant_aggregator, ] ) @@ -142,11 +136,18 @@ async def run_bot(websocket_client, user_id: str, session_id: str): async def on_client_ready(rtvi): logger.info(f"Client ready for user: {user_id}") await rtvi.set_bot_ready() - await task.queue_frames([ - LLMMessagesFrame([ - {"role": "system", "content": "Greet the user warmly and briefly introduce yourself as a memory-enabled assistant."} - ]) - ]) + await task.queue_frames( + [ + LLMMessagesFrame( + [ + { + "role": "system", + "content": "Greet the user warmly and ask them a question (like their name) don't say much please.", + } + ] + ) + ] + ) @transport.event_handler("on_client_connected") async def on_client_connected(transport, client): @@ -167,22 +168,17 @@ async def connect( userId: str = Query(None), sessionId: str = Query(None), ): - """ - Client calls this to get WebSocket connection info. - Pass userId and sessionId to maintain user context. - """ - # Generate IDs if not provided user_id = userId or f"user-{uuid.uuid4().hex[:12]}" session_id = sessionId or f"session-{uuid.uuid4().hex[:8]}" - # Get the host from request or use env ws_host = os.getenv("WS_HOST", request.headers.get("host", "localhost:8001")) ws_protocol = "wss" if os.getenv("USE_SSL", "false").lower() == "true" else "ws" - # Pipecat client only expects wsUrl in response - embed userId/sessionId in URL - return JSONResponse({ - "wsUrl": f"{ws_protocol}://{ws_host}/ws?userId={user_id}&sessionId={session_id}", - }) + return JSONResponse( + { + "wsUrl": f"{ws_protocol}://{ws_host}/ws?userId={user_id}&sessionId={session_id}", + } + ) @app.websocket("/ws") @@ -193,7 +189,6 @@ async def websocket_endpoint( ): await websocket.accept() - # Generate IDs if not provided user_id = userId or f"user-{uuid.uuid4().hex[:12]}" session_id = sessionId or f"session-{uuid.uuid4().hex[:8]}" diff --git a/biome.json b/biome.json index 68df3d5..fd3c9a1 100644 --- a/biome.json +++ b/biome.json @@ -53,7 +53,12 @@ "enabled": true, "rules": { "a11y": { - "useValidAnchor": "warn" + "useValidAnchor": "warn", + "noStaticElementInteractions": "warn", + "useMediaCaption": "warn" + }, + "suspicious": { + "noExplicitAny": "warn" }, "correctness": { "useYield": "warn", diff --git a/bun.lock b/bun.lock index 0e94696..d9d5d05 100644 --- a/bun.lock +++ b/bun.lock @@ -50,7 +50,6 @@ "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.2", "drizzle-kit": "^0.31.8", - "patch-package": "^8.0.1", "prettier": "^3.7.4", "tailwindcss": "^4.1.18", "typescript": "^5.9.3", @@ -695,8 +694,6 @@ "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], - "@yarnpkg/lockfile": ["@yarnpkg/lockfile@1.1.0", "", {}, "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ=="], - "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], @@ -717,7 +714,7 @@ "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -743,8 +740,6 @@ "bowser": ["bowser@2.13.1", "", {}, "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw=="], - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], @@ -753,8 +748,6 @@ "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], @@ -765,8 +758,6 @@ "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], @@ -781,8 +772,6 @@ "chevrotain-allstar": ["chevrotain-allstar@0.3.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^11.0.0" } }, "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw=="], - "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], - "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], @@ -923,8 +912,6 @@ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], - "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], - "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], "delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="], @@ -1025,12 +1012,8 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], - "find-yarn-workspace-root": ["find-yarn-workspace-root@2.0.0", "", { "dependencies": { "micromatch": "^4.0.2" } }, "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ=="], - "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="], @@ -1043,8 +1026,6 @@ "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], - "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -1073,10 +1054,6 @@ "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], @@ -1139,26 +1116,18 @@ "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], - "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], "is-plain-object": ["is-plain-object@2.0.4", "", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="], "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], - "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], - - "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "isobject": ["isobject@3.0.1", "", {}, "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="], @@ -1183,22 +1152,14 @@ "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - "json-stable-stringify": ["json-stable-stringify@1.3.0", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "isarray": "^2.0.5", "jsonify": "^0.0.1", "object-keys": "^1.1.1" } }, "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg=="], - "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], - - "jsonify": ["jsonify@0.0.1", "", {}, "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg=="], - "katex": ["katex@0.16.27", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw=="], "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], - "klaw-sync": ["klaw-sync@6.0.0", "", { "dependencies": { "graceful-fs": "^4.1.11" } }, "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ=="], - "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], "kysely": ["kysely@0.28.9", "", {}, "sha512-3BeXMoiOhpOwu62CiVpO6lxfq4eS6KMYfQdMsN/2kUCRNuF2YiEr7u0HLHaQU+O4Xu8YXE3bHVkwaQ85i72EuA=="], @@ -1357,8 +1318,6 @@ "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -1399,8 +1358,6 @@ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], - "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], - "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], @@ -1409,8 +1366,6 @@ "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="], - "open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="], - "openai": ["openai@4.104.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA=="], "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], @@ -1425,8 +1380,6 @@ "partysocket": ["partysocket@1.1.10", "", { "dependencies": { "event-target-polyfill": "^0.0.4" } }, "sha512-ACfn0P6lQuj8/AqB4L5ZDFcIEbpnIteNNObrlxqV1Ge80GTGhjuJ2sNKwNQlFzhGi4kI7fP/C1Eqh8TR78HjDQ=="], - "patch-package": ["patch-package@8.0.1", "", { "dependencies": { "@yarnpkg/lockfile": "^1.1.0", "chalk": "^4.1.2", "ci-info": "^3.7.0", "cross-spawn": "^7.0.3", "find-yarn-workspace-root": "^2.0.0", "fs-extra": "^10.0.0", "json-stable-stringify": "^1.0.2", "klaw-sync": "^6.0.0", "minimist": "^1.2.6", "open": "^7.4.2", "semver": "^7.5.3", "slash": "^2.0.0", "tmp": "^0.2.4", "yaml": "^2.2.2" }, "bin": { "patch-package": "index.js" } }, "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw=="], - "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -1545,8 +1498,6 @@ "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], - "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], - "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], "shallow-clone": ["shallow-clone@3.0.1", "", { "dependencies": { "kind-of": "^6.0.2" } }, "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA=="], @@ -1571,8 +1522,6 @@ "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], - "slash": ["slash@2.0.0", "", {}, "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="], - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -1607,7 +1556,7 @@ "supermemory": ["supermemory@3.14.0", "", {}, "sha512-gy1C6B4wUHEIOjmvDqW6GRttEdr0TZFFZ2YVU5eTCXELPQ0zjxgwudmg2kLPI6dEIITUxw1Q6n1c+vm4ro0KSg=="], - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], "svix": ["svix@1.76.1", "", { "dependencies": { "@stablelib/base64": "^1.0.0", "@types/node": "^22.7.5", "es6-promise": "^4.2.8", "fast-sha256": "^1.3.0", "url-parse": "^1.5.10", "uuid": "^10.0.0" } }, "sha512-CRuDWBTgYfDnBLRaZdKp9VuoPcNUq9An14c/k+4YJ15Qc5Grvf66vp0jvTltd4t7OIRj+8lM1DAgvSgvf7hdLw=="], @@ -1633,10 +1582,6 @@ "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], - "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], - - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], @@ -1679,8 +1624,6 @@ "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], - "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], @@ -1805,8 +1748,6 @@ "@jridgewell/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], - "@protobuf-ts/plugin/typescript": ["typescript@3.9.10", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q=="], "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], @@ -1899,8 +1840,6 @@ "mermaid/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "miniflare/workerd": ["workerd@1.20251217.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251217.0", "@cloudflare/workerd-darwin-arm64": "1.20251217.0", "@cloudflare/workerd-linux-64": "1.20251217.0", "@cloudflare/workerd-linux-arm64": "1.20251217.0", "@cloudflare/workerd-windows-64": "1.20251217.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-s3mHDSWwHTduyY8kpHOsl27ZJ4ziDBJlc18PfBvNMqNnhO7yBeemlxH7bo7yQyU1foJrIZ6IENHDDg0Z9N8zQA=="], "miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], @@ -1929,8 +1868,6 @@ "wrangler/miniflare": ["miniflare@4.20260103.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20260103.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "^3.25.76" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-iuSU0e+KMuFD7gxuPKoJXFi6cvDu/w/lQP4Wayq3v+YsmZ0dVMAJY9LMZ0TKMLicdAj2So9WcReAhJmJJ9Ppnw=="], - "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "@cloudflare/vite-plugin/wrangler/workerd": ["workerd@1.20251217.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251217.0", "@cloudflare/workerd-darwin-arm64": "1.20251217.0", "@cloudflare/workerd-linux-64": "1.20251217.0", "@cloudflare/workerd-linux-arm64": "1.20251217.0", "@cloudflare/workerd-windows-64": "1.20251217.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-s3mHDSWwHTduyY8kpHOsl27ZJ4ziDBJlc18PfBvNMqNnhO7yBeemlxH7bo7yQyU1foJrIZ6IENHDDg0Z9N8zQA=="], "@cloudflare/vitest-pool-workers/wrangler/workerd": ["workerd@1.20251217.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251217.0", "@cloudflare/workerd-darwin-arm64": "1.20251217.0", "@cloudflare/workerd-linux-64": "1.20251217.0", "@cloudflare/workerd-linux-arm64": "1.20251217.0", "@cloudflare/workerd-windows-64": "1.20251217.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-s3mHDSWwHTduyY8kpHOsl27ZJ4ziDBJlc18PfBvNMqNnhO7yBeemlxH7bo7yQyU1foJrIZ6IENHDDg0Z9N8zQA=="], diff --git a/package.json b/package.json index 9236e94..d834202 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,7 @@ "test": "vitest", "types": "wrangler types env.d.ts --include-runtime false", "format": "prettier --write .", - "check": "prettier . --check && biome lint && tsc", - "postinstall": "patch-package" + "check": "biome ci" }, "keywords": [ "cloudflare", @@ -37,7 +36,6 @@ "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.2", "drizzle-kit": "^0.31.8", - "patch-package": "^8.0.1", "prettier": "^3.7.4", "tailwindcss": "^4.1.18", "typescript": "^5.9.3", diff --git a/patches/@modelcontextprotocol+sdk+1.23.0.patch b/patches/@modelcontextprotocol+sdk+1.23.0.patch deleted file mode 100644 index 0ac54bd..0000000 --- a/patches/@modelcontextprotocol+sdk+1.23.0.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js b/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js -index 02762f0..7deca89 100644 ---- a/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js -+++ b/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js -@@ -1,7 +1,7 @@ - /** - * AJV-based JSON Schema validator provider - */ --import { Ajv } from 'ajv'; -+import Ajv from 'ajv'; // modifying this because vitest is struggling to pick up the import - import _addFormats from 'ajv-formats'; - function createDefaultAjvInstance() { - const ajv = new Ajv({ diff --git a/src/app.tsx b/src/app.tsx index 8fe4d3e..e19fff9 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,11 +1,11 @@ -import { useCallback, useEffect, useRef, useState } from "react" import { PipecatClient, RTVIEvent } from "@pipecat-ai/client-js" import { WebSocketTransport } from "@pipecat-ai/websocket-transport" import { type DocumentWithMemories, - MemoryGraph, injectStyles, + MemoryGraph, } from "@supermemory/memory-graph" +import { useCallback, useEffect, useRef, useState } from "react" import "./voice.css" // Backend URL - configurable via env @@ -97,7 +97,6 @@ interface TranscriptEntry { timestamp: Date } - export default function VoiceApp() { // Generate userId on first load (persists in localStorage) const [userId] = useState(() => { @@ -117,7 +116,7 @@ export default function VoiceApp() { const [isConnected, setIsConnected] = useState(false) const [isConnecting, setIsConnecting] = useState(false) const [isBotReady, setIsBotReady] = useState(false) - const [isBotSpeaking, setIsBotSpeaking] = useState(false) + const [_isBotSpeaking, setIsBotSpeaking] = useState(false) const [error, setError] = useState(null) // Transcript @@ -129,7 +128,9 @@ export default function VoiceApp() { const [searchQuery, setSearchQuery] = useState("") // Graph documents - const [documentsData, setDocumentsData] = useState<{ documents: DocumentWithMemories[] } | null>(null) + const [documentsData, setDocumentsData] = useState<{ + documents: DocumentWithMemories[] + } | null>(null) const [isLoadingGraph, setIsLoadingGraph] = useState(false) // Profile data @@ -145,6 +146,8 @@ export default function VoiceApp() { const clientRef = useRef(null) const audioRef = useRef(null) const messagesEndRef = useRef(null) + const ttsBufferRef = useRef("") // Buffer for TTS text chunks + const lastBotMessageIndexRef = useRef(-1) // Track last bot message for updating // Inject memory-graph styles once useEffect(() => { @@ -154,7 +157,7 @@ export default function VoiceApp() { // Auto-scroll messages useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) - }, [transcripts]) + }, []) const addTranscript = useCallback((role: "user" | "bot", text: string) => { setTranscripts((prev) => [...prev, { role, text, timestamp: new Date() }]) @@ -184,7 +187,12 @@ export default function VoiceApp() { const data = await response.json() // Only update if we got valid data (no error field, and memories array exists) - if (!data.error && data.memories && Array.isArray(data.memories) && data.memories.length > 0) { + if ( + !data.error && + data.memories && + Array.isArray(data.memories) && + data.memories.length > 0 + ) { setMemories(data.memories) } } catch (err) { @@ -197,79 +205,82 @@ export default function VoiceApp() { ) // Fetch profile data from Supermemory API (preserves data on error) - const fetchProfile = useCallback( - async () => { - try { - const response = await fetch(`/api/profile?userId=${userId}`) - if (!response.ok) return // Don't clear data on error - const data = await response.json() - // Only update if we got valid profile data (no error field) - if (!data.error && data.profile) { - setProfileData(data) - } - } catch (err) { - console.error("Failed to fetch profile:", err) - // Don't clear data on error + const fetchProfile = useCallback(async () => { + try { + const response = await fetch(`/api/profile?userId=${userId}`) + if (!response.ok) return // Don't clear data on error + const data = await response.json() + // Only update if we got valid profile data (no error field) + if (!data.error && data.profile) { + setProfileData(data) } - }, - [userId], - ) + } catch (err) { + console.error("Failed to fetch profile:", err) + // Don't clear data on error + } + }, [userId]) // Fetch session documents (preserves data on error) - const fetchSessionDocs = useCallback( - async () => { - try { - const response = await fetch(`/api/documents?userId=${userId}&page=1&limit=10`) - if (!response.ok) return // Don't clear data on error - const data = await response.json() - // Only update if we got valid documents (no error field) - if (!data.error && data.documents && Array.isArray(data.documents) && data.documents.length > 0) { - const docs = data.documents.map((doc: any) => ({ - id: doc.id, - title: doc.title || "Untitled Session", - summary: doc.summary || "", - createdAt: doc.createdAt || new Date().toISOString(), - })) - setSessionDocs(docs) - } - } catch (err) { - console.error("Failed to fetch session docs:", err) - // Don't clear data on error + const fetchSessionDocs = useCallback(async () => { + try { + const response = await fetch( + `/api/documents?userId=${userId}&page=1&limit=10`, + ) + if (!response.ok) return // Don't clear data on error + const data = await response.json() + // Only update if we got valid documents (no error field) + if ( + !data.error && + data.documents && + Array.isArray(data.documents) && + data.documents.length > 0 + ) { + const docs = data.documents.map((doc: any) => ({ + id: doc.id, + title: doc.title || "Untitled Session", + summary: doc.summary || "", + createdAt: doc.createdAt || new Date().toISOString(), + })) + setSessionDocs(docs) } - }, - [userId], - ) + } catch (err) { + console.error("Failed to fetch session docs:", err) + // Don't clear data on error + } + }, [userId]) // Fetch documents for memory graph (preserves data on error) - const fetchDocuments = useCallback( - async () => { - // Only show loading spinner on first load when no data exists - const isFirstLoad = !documentsData - if (isFirstLoad) setIsLoadingGraph(true) + const fetchDocuments = useCallback(async () => { + // Only show loading spinner on first load when no data exists + const isFirstLoad = !documentsData + if (isFirstLoad) setIsLoadingGraph(true) - try { - const response = await fetch( - `/api/documents?userId=${userId}&page=1&limit=100`, - ) - if (!response.ok) { - // Don't clear data on error - if (isFirstLoad) setIsLoadingGraph(false) - return - } - const data = await response.json() - // Only update if we got valid data (no error field) - if (!data.error && data.documents && Array.isArray(data.documents) && data.documents.length > 0) { - setDocumentsData(data) - } - } catch (err) { - console.error("Failed to fetch documents:", err) + try { + const response = await fetch( + `/api/documents?userId=${userId}&page=1&limit=100`, + ) + if (!response.ok) { // Don't clear data on error + if (isFirstLoad) setIsLoadingGraph(false) + return } + const data = await response.json() + // Only update if we got valid data (no error field) + if ( + !data.error && + data.documents && + Array.isArray(data.documents) && + data.documents.length > 0 + ) { + setDocumentsData(data) + } + } catch (err) { + console.error("Failed to fetch documents:", err) + // Don't clear data on error + } - if (isFirstLoad) setIsLoadingGraph(false) - }, - [userId, documentsData], - ) + if (isFirstLoad) setIsLoadingGraph(false) + }, [userId, documentsData]) // Auto-refresh graph every 10 seconds when graph tab is active useEffect(() => { @@ -308,7 +319,14 @@ export default function VoiceApp() { clearInterval(profileInterval) clearInterval(sessionInterval) } - }, [hasEverStarted, activeTab, fetchProfile, fetchSessionDocs, fetchMemories, searchQuery]) + }, [ + hasEverStarted, + activeTab, + fetchProfile, + fetchSessionDocs, + fetchMemories, + searchQuery, + ]) // Start conversation const startConversation = useCallback(async () => { @@ -346,13 +364,44 @@ export default function VoiceApp() { }, onUserTranscript: (data) => { if (data.final) { + // Reset TTS buffer for new response + ttsBufferRef.current = "" + lastBotMessageIndexRef.current = -1 + addTranscript("user", data.text) // Refresh memories after user speaks setTimeout(() => fetchMemories(), 3000) } }, - onBotTranscript: (data) => { - addTranscript("bot", data.text) + onBotTtsText: (data) => { + // Accumulate TTS chunks and update single bot message + const chunk = data.text || "" + ttsBufferRef.current += chunk + + setTranscripts((prev) => { + const newTranscripts = [...prev] + + if ( + lastBotMessageIndexRef.current >= 0 && + lastBotMessageIndexRef.current < newTranscripts.length + ) { + // Update existing bot message + newTranscripts[lastBotMessageIndexRef.current] = { + ...newTranscripts[lastBotMessageIndexRef.current], + text: ttsBufferRef.current, + } + } else { + // Add new bot message + lastBotMessageIndexRef.current = newTranscripts.length + newTranscripts.push({ + role: "bot", + text: ttsBufferRef.current, + timestamp: new Date(), + }) + } + + return newTranscripts + }) }, onError: (err) => { console.error("Client error:", err) @@ -375,7 +424,8 @@ export default function VoiceApp() { const connectUrl = `${BACKEND_URL}/connect?userId=${userId}&sessionId=${sessionId}` await client.startBotAndConnect({ endpoint: connectUrl }) } catch (err: unknown) { - const errorMessage = err instanceof Error ? err.message : "Failed to connect" + const errorMessage = + err instanceof Error ? err.message : "Failed to connect" console.error("Connection failed:", err) setError(errorMessage) setIsConnecting(false) @@ -412,7 +462,7 @@ export default function VoiceApp() { } }, []) - const formatTime = (date: Date) => { + const _formatTime = (date: Date) => { return date.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", @@ -438,14 +488,14 @@ export default function VoiceApp() { if (!hasEverStarted) { return (
-