diff --git a/Dockerfile.llamaswap-rocm b/Dockerfile.llamaswap-rocm
index 72bd488..fff3911 100644
--- a/Dockerfile.llamaswap-rocm
+++ b/Dockerfile.llamaswap-rocm
@@ -1,31 +1,7 @@
# Multi-stage build for llama-swap with ROCm support
-# Stage 1: Build llama.cpp with ROCm (requires ROCm 6.1+)
-FROM rocm/dev-ubuntu-22.04:6.2.4 AS llama-builder
+# Now using official llama.cpp ROCm image (PR #18439 merged Dec 29, 2025)
-WORKDIR /build
-
-# Install build dependencies including ROCm/HIP development libraries
-RUN apt-get update && apt-get install -y \
- git \
- build-essential \
- cmake \
- wget \
- libcurl4-openssl-dev \
- hip-dev \
- hipblas-dev \
- rocblas-dev \
- && rm -rf /var/lib/apt/lists/*
-
-# Clone and build llama.cpp with HIP/ROCm support (gfx1030 = RX 6800)
-RUN git clone https://github.com/ggml-org/llama.cpp.git && \
- cd llama.cpp && \
- HIPCXX="$(hipconfig -l)/clang" HIP_PATH="$(hipconfig -R)" \
- cmake -S . -B build -DGGML_HIP=ON -DGPU_TARGETS=gfx1030 -DCMAKE_BUILD_TYPE=Release && \
- cmake --build build --config Release -- -j$(nproc) && \
- cp build/bin/llama-server /build/llama-server && \
- find build -name "*.so*" -exec cp {} /build/ \;
-
-# Stage 2: Build llama-swap UI and binary
+# Stage 1: Build llama-swap UI
FROM node:22-alpine AS ui-builder
WORKDIR /build
@@ -36,11 +12,11 @@ RUN apk add --no-cache git
# Clone llama-swap
RUN git clone https://github.com/mostlygeek/llama-swap.git
-# Build UI
-WORKDIR /build/llama-swap/ui
+# Build UI (now in ui-svelte directory)
+WORKDIR /build/llama-swap/ui-svelte
RUN npm install && npm run build
-# Stage 3: Build llama-swap binary
+# Stage 2: Build llama-swap binary
FROM golang:1.23-alpine AS swap-builder
WORKDIR /build
@@ -55,27 +31,19 @@ COPY --from=ui-builder /build/llama-swap /build/llama-swap
WORKDIR /build/llama-swap
RUN GOTOOLCHAIN=auto go build -o /build/llama-swap-binary .
-# Stage 4: Final runtime image
-FROM rocm/dev-ubuntu-22.04:6.2.4
+# Stage 3: Final runtime image using official llama.cpp ROCm image
+FROM ghcr.io/ggml-org/llama.cpp:server-rocm
WORKDIR /app
-# Install runtime dependencies including additional ROCm libraries
-RUN apt-get update && apt-get install -y \
- curl \
- ca-certificates \
- rocm-libs \
- && rm -rf /var/lib/apt/lists/*
-
-# Copy built binaries and shared libraries from previous stages
-COPY --from=llama-builder /build/llama-server /app/llama-server
-COPY --from=llama-builder /build/*.so* /app/
+# Copy llama-swap binary from builder
COPY --from=swap-builder /build/llama-swap-binary /app/llama-swap
-# Make binaries executable
-RUN chmod +x /app/llama-server /app/llama-swap
+# Make binary executable
+RUN chmod +x /app/llama-swap
-# Create user and add to GPU access groups (using host GIDs)
+# Create non-root user and add to GPU access groups
+# The official llama.cpp image already has llama-server installed
# GID 187 = render group on host, GID 989 = video/kfd group on host
RUN groupadd -g 187 hostrender && \
groupadd -g 989 hostvideo && \
@@ -86,7 +54,6 @@ RUN groupadd -g 187 hostrender && \
ENV HSA_OVERRIDE_GFX_VERSION=10.3.0
ENV ROCM_PATH=/opt/rocm
ENV HIP_VISIBLE_DEVICES=0
-ENV LD_LIBRARY_PATH=/opt/rocm/lib:/app:$LD_LIBRARY_PATH
USER llamaswap
diff --git a/bot/static/index.html b/bot/static/index.html
index aace144..19294a0 100644
--- a/bot/static/index.html
+++ b/bot/static/index.html
@@ -1548,9 +1548,6 @@
-
-
-
Logs
diff --git a/bot/utils/cat_client.py b/bot/utils/cat_client.py
index 2c05433..3a75e0f 100644
--- a/bot/utils/cat_client.py
+++ b/bot/utils/cat_client.py
@@ -21,7 +21,7 @@ from typing import Optional, Dict, Any, List
import globals
from utils.logger import get_logger
-logger = get_logger('cat_client')
+logger = get_logger('llm') # Use existing 'llm' logger component
class CatAdapter:
@@ -254,24 +254,36 @@ class CatAdapter:
async def get_memory_stats(self) -> Optional[Dict[str, Any]]:
"""
- Get memory collection statistics from Cat.
+ Get memory collection statistics with actual counts from Qdrant.
Returns dict with collection names and point counts.
"""
try:
- async with aiohttp.ClientSession() as session:
- async with session.get(
- f"{self._base_url}/memory/collections",
- headers=self._get_headers(),
- timeout=aiohttp.ClientTimeout(total=15)
- ) as response:
- if response.status == 200:
- data = await response.json()
- return data
- else:
- logger.error(f"Failed to get memory stats: {response.status}")
- return None
+ # Query Qdrant directly for accurate counts
+ qdrant_host = self._base_url.replace("http://cheshire-cat:80", "http://cheshire-cat-vector-memory:6333")
+
+ collections_data = []
+ for collection_name in ["episodic", "declarative", "procedural"]:
+ async with aiohttp.ClientSession() as session:
+ async with session.get(
+ f"{qdrant_host}/collections/{collection_name}",
+ timeout=aiohttp.ClientTimeout(total=10)
+ ) as response:
+ if response.status == 200:
+ data = await response.json()
+ count = data.get("result", {}).get("points_count", 0)
+ collections_data.append({
+ "name": collection_name,
+ "vectors_count": count
+ })
+ else:
+ collections_data.append({
+ "name": collection_name,
+ "vectors_count": 0
+ })
+
+ return {"collections": collections_data}
except Exception as e:
- logger.error(f"Error getting memory stats: {e}")
+ logger.error(f"Error getting memory stats from Qdrant: {e}")
return None
async def get_memory_points(
@@ -281,28 +293,33 @@ class CatAdapter:
offset: Optional[str] = None
) -> Optional[Dict[str, Any]]:
"""
- Get all points from a memory collection.
+ Get all points from a memory collection via Qdrant.
+ Cat doesn't expose /memory/collections/{id}/points, so we query Qdrant directly.
Returns paginated list of memory points.
"""
try:
- params = {"limit": limit}
+ # Use Qdrant directly (Cat's vector memory backend)
+ # Qdrant is accessible at the same host, port 6333 internally
+ qdrant_host = self._base_url.replace("http://cheshire-cat:80", "http://cheshire-cat-vector-memory:6333")
+
+ payload = {"limit": limit, "with_payload": True, "with_vector": False}
if offset:
- params["offset"] = offset
+ payload["offset"] = offset
async with aiohttp.ClientSession() as session:
- async with session.get(
- f"{self._base_url}/memory/collections/{collection}/points",
- headers=self._get_headers(),
- params=params,
+ async with session.post(
+ f"{qdrant_host}/collections/{collection}/points/scroll",
+ json=payload,
timeout=aiohttp.ClientTimeout(total=30)
) as response:
if response.status == 200:
- return await response.json()
+ data = await response.json()
+ return data.get("result", {})
else:
- logger.error(f"Failed to get {collection} points: {response.status}")
+ logger.error(f"Failed to get {collection} points from Qdrant: {response.status}")
return None
except Exception as e:
- logger.error(f"Error getting memory points: {e}")
+ logger.error(f"Error getting memory points from Qdrant: {e}")
return None
async def get_all_facts(self) -> List[Dict[str, Any]]:
@@ -344,22 +361,24 @@ class CatAdapter:
return all_facts
async def delete_memory_point(self, collection: str, point_id: str) -> bool:
- """Delete a single memory point by ID."""
+ """Delete a single memory point by ID via Qdrant."""
try:
+ qdrant_host = self._base_url.replace("http://cheshire-cat:80", "http://cheshire-cat-vector-memory:6333")
+
async with aiohttp.ClientSession() as session:
- async with session.delete(
- f"{self._base_url}/memory/collections/{collection}/points/{point_id}",
- headers=self._get_headers(),
+ async with session.post(
+ f"{qdrant_host}/collections/{collection}/points/delete",
+ json={"points": [point_id]},
timeout=aiohttp.ClientTimeout(total=15)
) as response:
if response.status == 200:
- logger.info(f"Deleted point {point_id} from {collection}")
+ logger.info(f"Deleted memory point {point_id} from {collection}")
return True
else:
logger.error(f"Failed to delete point: {response.status}")
return False
except Exception as e:
- logger.error(f"Error deleting point: {e}")
+ logger.error(f"Error deleting memory point: {e}")
return False
async def wipe_all_memories(self) -> bool:
diff --git a/cat-plugins/discord_bridge/discord_bridge.py b/cat-plugins/discord_bridge/discord_bridge.py
index f6b665b..ccd3e5e 100644
--- a/cat-plugins/discord_bridge/discord_bridge.py
+++ b/cat-plugins/discord_bridge/discord_bridge.py
@@ -52,6 +52,14 @@ def before_cat_reads_message(user_message_json: dict, cat) -> dict:
cat.working_memory['mood'] = mood
cat.working_memory['response_type'] = response_type
+ # If we have an author name, prepend it to the message text so the LLM can see it
+ # This ensures Miku knows who is talking to her
+ if author_name and 'text' in user_message_json:
+ original_text = user_message_json['text']
+ # Don't add name if it's already in the message
+ if not original_text.lower().startswith(author_name.lower()):
+ user_message_json['text'] = f"[{author_name} says:] {original_text}"
+
return user_message_json