feat: migrate to Poetry, fix Docker build, and resolve 6 UI/backend bugs (#92)
Migrate from Hatchling to Poetry for dependency management, fixing the Docker build failure caused by .dockerignore excluding README.md that Hatchling needed for metadata. Poetry export strategy bypasses this entirely. Creative extras removed from main build (separate service). Docker changes: - Multi-stage builds with poetry export → pip install - BuildKit cache mounts for faster rebuilds - All 3 Dockerfiles updated (root, dashboard, agent) Bug fixes from tester audit: - TaskStatus/TaskPriority case-insensitive enum parsing - scrollChat() upgraded to requestAnimationFrame, removed duplicate - Desktop/mobile nav items synced in base.html - HTMX pointed to direct htmx.min.js URL - Removed unused highlight.js and bootstrap.bundle.min.js - Registered missing escalation/external task handlers in app.py Co-authored-by: Alexander Payne <apayne@MM.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
7b967d84b2
commit
ca0c42398b
46
Dockerfile
46
Dockerfile
@@ -11,32 +11,42 @@
|
||||
# timmy-time:latest \
|
||||
# python -m swarm.agent_runner --agent-id w1 --name Worker-1
|
||||
|
||||
# ── Stage 1: Builder — export deps via Poetry, install via pip ──────────────
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Install Poetry + export plugin (only needed for export, not in runtime)
|
||||
RUN pip install --no-cache-dir poetry poetry-plugin-export
|
||||
|
||||
# Copy dependency files only (layer caching)
|
||||
COPY pyproject.toml poetry.lock ./
|
||||
|
||||
# Export pinned requirements and install with pip cache mount
|
||||
RUN poetry export --extras swarm --extras telegram --without-hashes \
|
||||
-f requirements.txt -o requirements.txt
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# ── Stage 2: Runtime ───────────────────────────────────────────────────────
|
||||
FROM python:3.12-slim AS base
|
||||
|
||||
# ── System deps ──────────────────────────────────────────────────────────────
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc curl fonts-dejavu-core \
|
||||
curl fonts-dejavu-core \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# ── Python deps (install before copying src for layer caching) ───────────────
|
||||
# Copy only pyproject.toml first so Docker can cache the dep-install layer.
|
||||
# The editable install (-e) happens after src is copied below.
|
||||
COPY pyproject.toml .
|
||||
|
||||
# Create a minimal src layout so `pip install` can resolve the package metadata
|
||||
# without copying the full source tree (preserves Docker layer caching).
|
||||
RUN mkdir -p src/timmy src/timmy_serve src/self_tdd src/dashboard && \
|
||||
touch src/timmy/__init__.py src/timmy/cli.py \
|
||||
src/timmy_serve/__init__.py src/timmy_serve/cli.py \
|
||||
src/self_tdd/__init__.py src/self_tdd/watchdog.py \
|
||||
src/dashboard/__init__.py src/config.py
|
||||
|
||||
RUN pip install --no-cache-dir -e ".[swarm,telegram]"
|
||||
# Copy installed packages from builder
|
||||
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
|
||||
COPY --from=builder /usr/local/bin /usr/local/bin
|
||||
|
||||
# ── Application source ───────────────────────────────────────────────────────
|
||||
# Overwrite the stubs with real source code
|
||||
COPY src/ ./src/
|
||||
COPY static/ ./static/
|
||||
|
||||
@@ -48,8 +58,6 @@ RUN groupadd -r timmy && useradd -r -g timmy -d /app -s /sbin/nologin timmy \
|
||||
&& chown -R timmy:timmy /app
|
||||
# Ensure static/ and data/ are world-readable so bind-mounted files
|
||||
# from the macOS host remain accessible when running as the timmy user.
|
||||
# Docker Desktop for Mac bind mounts inherit host permissions, which may
|
||||
# not include the container's timmy UID — chmod o+rX fixes 403 errors.
|
||||
RUN chmod -R o+rX /app/static /app/data
|
||||
USER timmy
|
||||
|
||||
|
||||
47
Makefile
47
Makefile
@@ -1,44 +1,28 @@
|
||||
.PHONY: install install-bigbrain install-creative dev nuke fresh test test-cov test-cov-html watch lint clean help \
|
||||
.PHONY: install install-bigbrain dev nuke fresh test test-cov test-cov-html watch lint clean help \
|
||||
up down logs \
|
||||
docker-build docker-up docker-down docker-agent docker-logs docker-shell \
|
||||
cloud-deploy cloud-up cloud-down cloud-logs cloud-status cloud-update
|
||||
|
||||
VENV := .venv
|
||||
PYTHON := $(VENV)/bin/python
|
||||
PIP := $(VENV)/bin/pip
|
||||
PYTEST := $(VENV)/bin/pytest
|
||||
UVICORN := $(VENV)/bin/uvicorn
|
||||
SELF_TDD := $(VENV)/bin/self-tdd
|
||||
PYTEST := poetry run pytest
|
||||
UVICORN := poetry run uvicorn
|
||||
SELF_TDD := poetry run self-tdd
|
||||
PYTHON := poetry run python
|
||||
|
||||
# ── Setup ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
install: $(VENV)/bin/activate
|
||||
$(PIP) install --quiet -e ".[dev]"
|
||||
install:
|
||||
poetry install --with dev
|
||||
@echo "✓ Ready. Run 'make dev' to start the dashboard."
|
||||
|
||||
install-bigbrain: $(VENV)/bin/activate
|
||||
$(PIP) install --quiet -e ".[dev,bigbrain]"
|
||||
install-bigbrain:
|
||||
poetry install --with dev --extras bigbrain
|
||||
@if [ "$$(uname -m)" = "arm64" ] && [ "$$(uname -s)" = "Darwin" ]; then \
|
||||
$(PIP) install --quiet "airllm[mlx]"; \
|
||||
poetry run pip install --quiet "airllm[mlx]"; \
|
||||
echo "✓ AirLLM + MLX installed (Apple Silicon detected)"; \
|
||||
else \
|
||||
echo "✓ AirLLM installed (PyTorch backend)"; \
|
||||
fi
|
||||
|
||||
install-creative: $(VENV)/bin/activate
|
||||
$(PIP) install --quiet -e ".[dev,creative]"
|
||||
@if [ "$$(uname -m)" = "arm64" ] && [ "$$(uname -s)" = "Darwin" ]; then \
|
||||
echo " Apple Silicon detected — installing PyTorch with Metal (MPS) support..."; \
|
||||
$(PIP) install --quiet --pre torch torchvision torchaudio \
|
||||
--index-url https://download.pytorch.org/whl/nightly/cpu; \
|
||||
echo "✓ Creative extras installed with Metal GPU acceleration"; \
|
||||
else \
|
||||
echo "✓ Creative extras installed (diffusers, torch, ace-step)"; \
|
||||
fi
|
||||
|
||||
$(VENV)/bin/activate:
|
||||
python3 -m venv $(VENV)
|
||||
|
||||
# ── Development ───────────────────────────────────────────────────────────────
|
||||
|
||||
dev: nuke
|
||||
@@ -63,7 +47,7 @@ nuke:
|
||||
# Ensures no stale code, cached layers, or old DB state persists.
|
||||
fresh: nuke
|
||||
docker compose down -v --rmi local 2>/dev/null || true
|
||||
docker compose build --no-cache
|
||||
DOCKER_BUILDKIT=1 docker compose build --no-cache
|
||||
mkdir -p data
|
||||
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
|
||||
@echo ""
|
||||
@@ -158,14 +142,14 @@ pre-commit-run:
|
||||
up:
|
||||
mkdir -p data
|
||||
ifdef DEV
|
||||
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
|
||||
DOCKER_BUILDKIT=1 docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
|
||||
@echo ""
|
||||
@echo " ✓ Timmy Time running in DEV mode at http://localhost:8000"
|
||||
@echo " Hot-reload active — Python, template, and CSS changes auto-apply"
|
||||
@echo " Logs: make logs"
|
||||
@echo ""
|
||||
else
|
||||
docker compose up -d --build
|
||||
DOCKER_BUILDKIT=1 docker compose up -d --build
|
||||
@echo ""
|
||||
@echo " ✓ Timmy Time running at http://localhost:8000"
|
||||
@echo " Logs: make logs"
|
||||
@@ -181,7 +165,7 @@ logs:
|
||||
# ── Docker ────────────────────────────────────────────────────────────────────
|
||||
|
||||
docker-build:
|
||||
docker build -t timmy-time:latest .
|
||||
DOCKER_BUILDKIT=1 docker build -t timmy-time:latest .
|
||||
|
||||
docker-up:
|
||||
mkdir -p data
|
||||
@@ -261,9 +245,8 @@ help:
|
||||
@echo ""
|
||||
@echo " Local Development"
|
||||
@echo " ─────────────────────────────────────────────────"
|
||||
@echo " make install create venv + install dev deps"
|
||||
@echo " make install install deps via Poetry"
|
||||
@echo " make install-bigbrain install with AirLLM (big-model backend)"
|
||||
@echo " make install-creative install with creative extras (torch, diffusers)"
|
||||
@echo " make dev clean up + start dashboard (auto-fixes errno 48)"
|
||||
@echo " make nuke kill port 8000, stop containers, reset state"
|
||||
@echo " make fresh full clean rebuild (no cached layers/volumes)"
|
||||
|
||||
@@ -6,30 +6,31 @@
|
||||
# Run: docker run -e COORDINATOR_URL=http://dashboard:8000 timmy-agent:latest
|
||||
|
||||
# ── Stage 1: Builder ──────────────────────────────────────────────────────────
|
||||
FROM python:3.12-slim as builder
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
curl \
|
||||
gcc curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy only pyproject.toml
|
||||
COPY pyproject.toml .
|
||||
# Install Poetry + export plugin for dependency export
|
||||
RUN pip install --no-cache-dir poetry poetry-plugin-export
|
||||
|
||||
# Create minimal package structure
|
||||
RUN mkdir -p src/timmy src/swarm src/infrastructure && \
|
||||
touch src/__init__.py src/timmy/__init__.py src/swarm/__init__.py \
|
||||
src/infrastructure/__init__.py config.py
|
||||
# Copy only dependency files for layer caching
|
||||
COPY pyproject.toml poetry.lock ./
|
||||
|
||||
# Install dependencies
|
||||
RUN pip install --no-cache-dir --user -e ".[swarm]"
|
||||
# Export pinned requirements and install with pip
|
||||
RUN poetry export --extras swarm --without-hashes \
|
||||
-f requirements.txt -o requirements.txt
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
pip install --no-cache-dir --user -r requirements.txt
|
||||
|
||||
|
||||
# ── Stage 2: Runtime ─────────────────────────────────────────────────────────
|
||||
FROM python:3.12-slim as runtime
|
||||
FROM python:3.12-slim AS runtime
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -43,7 +44,6 @@ COPY --from=builder /root/.local /root/.local
|
||||
|
||||
# Copy application source
|
||||
COPY src/ ./src/
|
||||
COPY config.py .
|
||||
|
||||
# Create data directory
|
||||
RUN mkdir -p /app/data
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
# ── Timmy Time Dashboard — Multi-stage Optimized Build ─────────────────────
|
||||
#
|
||||
# Multi-stage build for fast, lean image:
|
||||
# 1. builder Install dependencies
|
||||
# 1. builder Export deps via Poetry + install with pip
|
||||
# 2. runtime Copy only what's needed for production
|
||||
#
|
||||
# Build: docker build -f docker/Dockerfile.dashboard -t timmy-dashboard:latest .
|
||||
# Run: docker run -p 8000:8000 -v timmy-data:/app/data timmy-dashboard:latest
|
||||
|
||||
# ── Stage 1: Builder ──────────────────────────────────────────────────────────
|
||||
FROM python:3.12-slim as builder
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
curl \
|
||||
git \
|
||||
gcc curl git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy only pyproject.toml for dependency caching
|
||||
COPY pyproject.toml .
|
||||
# Install Poetry + export plugin for dependency export
|
||||
RUN pip install --no-cache-dir poetry poetry-plugin-export
|
||||
|
||||
# Create minimal package structure
|
||||
RUN mkdir -p src/timmy src/dashboard src/swarm src/infrastructure && \
|
||||
touch src/__init__.py src/timmy/__init__.py src/dashboard/__init__.py \
|
||||
src/swarm/__init__.py src/infrastructure/__init__.py config.py
|
||||
# Copy only dependency files for layer caching
|
||||
COPY pyproject.toml poetry.lock ./
|
||||
|
||||
# Install Python dependencies (with caching)
|
||||
RUN pip install --no-cache-dir --user -e ".[swarm,telegram]"
|
||||
# Export pinned requirements and install with pip
|
||||
RUN poetry export --extras swarm --extras telegram --without-hashes \
|
||||
-f requirements.txt -o requirements.txt
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
pip install --no-cache-dir --user -r requirements.txt
|
||||
|
||||
|
||||
# ── Stage 2: Runtime ─────────────────────────────────────────────────────────
|
||||
FROM python:3.12-slim as runtime
|
||||
FROM python:3.12-slim AS runtime
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -48,7 +48,6 @@ COPY --from=builder /root/.local /root/.local
|
||||
# Copy application source
|
||||
COPY src/ ./src/
|
||||
COPY static/ ./static/
|
||||
COPY config.py .
|
||||
|
||||
# Create data directory
|
||||
RUN mkdir -p /app/data
|
||||
|
||||
8239
poetry.lock
generated
Normal file
8239
poetry.lock
generated
Normal file
File diff suppressed because one or more lines are too long
145
pyproject.toml
145
pyproject.toml
@@ -1,104 +1,75 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[project]
|
||||
[tool.poetry]
|
||||
name = "timmy-time"
|
||||
version = "1.0.0"
|
||||
description = "Mission Control for sovereign AI agents"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
license = { text = "MIT" }
|
||||
dependencies = [
|
||||
"agno[sqlite]>=1.4.0",
|
||||
"ollama>=0.3.0",
|
||||
"openai>=1.0.0",
|
||||
"fastapi>=0.115.0",
|
||||
"uvicorn[standard]>=0.32.0",
|
||||
"jinja2>=3.1.0",
|
||||
"httpx>=0.27.0",
|
||||
"python-multipart>=0.0.12",
|
||||
"aiofiles>=24.0.0",
|
||||
"typer>=0.12.0",
|
||||
"rich>=13.0.0",
|
||||
"pydantic-settings>=2.0.0",
|
||||
"websockets>=12.0",
|
||||
"GitPython>=3.1.40",
|
||||
"moviepy>=2.0.0",
|
||||
"requests>=2.31.0",
|
||||
license = "MIT"
|
||||
packages = [
|
||||
{ include = "config.py", from = "src" },
|
||||
{ include = "creative", from = "src" },
|
||||
{ include = "dashboard", from = "src" },
|
||||
{ include = "hands", from = "src" },
|
||||
{ include = "infrastructure", from = "src" },
|
||||
{ include = "integrations", from = "src" },
|
||||
{ include = "lightning", from = "src" },
|
||||
{ include = "mcp", from = "src" },
|
||||
{ include = "scripture", from = "src" },
|
||||
{ include = "self_coding", from = "src" },
|
||||
{ include = "spark", from = "src" },
|
||||
{ include = "swarm", from = "src" },
|
||||
{ include = "timmy", from = "src" },
|
||||
{ include = "timmy_serve", from = "src" },
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=8.0.0",
|
||||
"pytest-asyncio>=0.24.0",
|
||||
"pytest-cov>=5.0.0",
|
||||
"pytest-timeout>=2.3.0",
|
||||
"selenium>=4.20.0",
|
||||
]
|
||||
# Big-brain: run 8B / 70B / 405B models locally via layer-by-layer loading.
|
||||
# pip install ".[bigbrain]"
|
||||
# On Apple Silicon: pip install "airllm[mlx]" for the MLX-accelerated backend.
|
||||
bigbrain = [
|
||||
"airllm>=2.9.0",
|
||||
]
|
||||
# Swarm: Redis-backed pub/sub for multi-agent communication.
|
||||
# pip install ".[swarm]"
|
||||
swarm = [
|
||||
"redis>=5.0.0",
|
||||
]
|
||||
# Voice: text-to-speech output via pyttsx3.
|
||||
# pip install ".[voice]"
|
||||
voice = [
|
||||
"pyttsx3>=2.90",
|
||||
]
|
||||
# Telegram: bridge Telegram messages to Timmy via python-telegram-bot.
|
||||
# pip install ".[telegram]"
|
||||
telegram = [
|
||||
"python-telegram-bot>=21.0",
|
||||
]
|
||||
# Discord: bridge Discord messages to Timmy with native thread support.
|
||||
# pip install ".[discord]"
|
||||
# Optional: pip install pyzbar Pillow (for QR code invite detection)
|
||||
discord = [
|
||||
"discord.py>=2.3.0",
|
||||
]
|
||||
# Creative: GPU-accelerated image, music, and video generation.
|
||||
# pip install ".[creative]"
|
||||
creative = [
|
||||
"diffusers>=0.30.0",
|
||||
"transformers>=4.40.0",
|
||||
"accelerate>=0.30.0",
|
||||
"torch>=2.2.0",
|
||||
"safetensors>=0.4.0",
|
||||
"ace-step>=1.5.0",
|
||||
]
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.11,<4"
|
||||
agno = { version = ">=1.4.0", extras = ["sqlite"] }
|
||||
ollama = ">=0.3.0"
|
||||
openai = ">=1.0.0"
|
||||
fastapi = ">=0.115.0"
|
||||
uvicorn = { version = ">=0.32.0", extras = ["standard"] }
|
||||
jinja2 = ">=3.1.0"
|
||||
httpx = ">=0.27.0"
|
||||
python-multipart = ">=0.0.12"
|
||||
aiofiles = ">=22.0.0"
|
||||
typer = ">=0.12.0"
|
||||
rich = ">=13.0.0"
|
||||
pydantic-settings = ">=2.0.0"
|
||||
websockets = ">=12.0"
|
||||
GitPython = ">=3.1.40"
|
||||
moviepy = ">=2.0.0"
|
||||
requests = ">=2.31.0"
|
||||
# Optional extras
|
||||
redis = { version = ">=5.0.0", optional = true }
|
||||
python-telegram-bot = { version = ">=21.0", optional = true }
|
||||
"discord.py" = { version = ">=2.3.0", optional = true }
|
||||
airllm = { version = ">=2.9.0", optional = true }
|
||||
pyttsx3 = { version = ">=2.90", optional = true }
|
||||
|
||||
[project.scripts]
|
||||
[tool.poetry.extras]
|
||||
swarm = ["redis"]
|
||||
telegram = ["python-telegram-bot"]
|
||||
discord = ["discord.py"]
|
||||
bigbrain = ["airllm"]
|
||||
voice = ["pyttsx3"]
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = ">=8.0.0"
|
||||
pytest-asyncio = ">=0.24.0"
|
||||
pytest-cov = ">=5.0.0"
|
||||
pytest-timeout = ">=2.3.0"
|
||||
selenium = ">=4.20.0"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
timmy = "timmy.cli:main"
|
||||
timmy-serve = "timmy_serve.cli:main"
|
||||
self-tdd = "self_coding.self_tdd.watchdog:main"
|
||||
self-modify = "self_coding.self_modify.cli:main"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
sources = {"src" = ""}
|
||||
include = [
|
||||
"src/config.py",
|
||||
"src/creative",
|
||||
"src/dashboard",
|
||||
"src/hands",
|
||||
"src/infrastructure",
|
||||
"src/integrations",
|
||||
"src/lightning",
|
||||
"src/mcp",
|
||||
"src/scripture",
|
||||
"src/self_coding",
|
||||
"src/spark",
|
||||
"src/swarm",
|
||||
"src/timmy",
|
||||
"src/timmy_serve",
|
||||
]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
pythonpath = ["src", "tests"]
|
||||
|
||||
@@ -255,12 +255,14 @@ async def _task_processor_loop() -> None:
|
||||
pass
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
# Register handlers
|
||||
# Register handlers for all known task types
|
||||
task_processor.register_handler("chat_response", handle_chat_response)
|
||||
task_processor.register_handler("thought", handle_thought)
|
||||
task_processor.register_handler("internal", handle_thought)
|
||||
task_processor.register_handler("bug_report", handle_bug_report)
|
||||
task_processor.register_handler("task_request", handle_task_request)
|
||||
task_processor.register_handler("escalation", handle_task_request)
|
||||
task_processor.register_handler("external", handle_task_request)
|
||||
|
||||
# ── Reconcile zombie tasks from previous crash ──
|
||||
zombie_count = task_processor.reconcile_zombie_tasks()
|
||||
|
||||
@@ -85,7 +85,7 @@ async def stop_agent(agent_id: str):
|
||||
@router.get("/tasks")
|
||||
async def list_tasks(status: Optional[str] = None):
|
||||
"""List swarm tasks, optionally filtered by status."""
|
||||
task_status = TaskStatus(status) if status else None
|
||||
task_status = TaskStatus(status.lower()) if status else None
|
||||
tasks = coordinator.list_tasks(task_status)
|
||||
return {
|
||||
"tasks": [
|
||||
|
||||
@@ -181,8 +181,8 @@ async def api_list_tasks(
|
||||
limit: int = 100,
|
||||
):
|
||||
"""List tasks with optional filters."""
|
||||
s = TaskStatus(status) if status else None
|
||||
p = TaskPriority(priority) if priority else None
|
||||
s = TaskStatus(status.lower()) if status else None
|
||||
p = TaskPriority(priority.lower()) if priority else None
|
||||
|
||||
tasks = list_tasks(status=s, priority=p, assigned_to=assigned_to, limit=limit)
|
||||
return {
|
||||
|
||||
@@ -13,11 +13,9 @@
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="/static/style.css?v=4" />
|
||||
{% block extra_styles %}{% endblock %}
|
||||
<script src="https://unpkg.com/htmx.org@2.0.3" integrity="sha384-0895/pl2MU10Hqc6jd4RvrthNlDiE9U1tWmX7WRESftEDRosgxNsQG/Ze9YMRzHq" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.3/dist/htmx.min.js" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked@15.0.7/marked.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.2.4/dist/purify.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css" />
|
||||
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="mc-header">
|
||||
@@ -47,6 +45,7 @@
|
||||
<a href="/hands" class="mc-test-link">HANDS</a>
|
||||
<a href="/work-orders/queue" class="mc-test-link">WORK ORDERS</a>
|
||||
<a href="/creative/ui" class="mc-test-link">CREATIVE</a>
|
||||
<a href="/voice/button" class="mc-test-link">VOICE</a>
|
||||
<a href="/mobile" class="mc-test-link" title="Mobile-optimized view">MOBILE</a>
|
||||
<a href="/mobile/local" class="mc-test-link" title="Local AI on iPhone">LOCAL AI</a>
|
||||
<button id="enable-notifications" class="mc-test-link" style="background:none;cursor:pointer;" title="Enable notifications">🔔</button>
|
||||
@@ -69,6 +68,8 @@
|
||||
<a href="/" class="mc-mobile-link">HOME</a>
|
||||
<a href="/tasks" class="mc-mobile-link">TASKS</a>
|
||||
<a href="/briefing" class="mc-mobile-link">BRIEFING</a>
|
||||
<a href="/thinking" class="mc-mobile-link">THINKING</a>
|
||||
<a href="/swarm/mission-control" class="mc-mobile-link">MISSION CONTROL</a>
|
||||
<a href="/swarm/live" class="mc-mobile-link">SWARM</a>
|
||||
<a href="/spark/ui" class="mc-mobile-link">SPARK</a>
|
||||
<a href="/marketplace/ui" class="mc-mobile-link">MARKET</a>
|
||||
@@ -77,9 +78,12 @@
|
||||
<a href="/bugs" class="mc-mobile-link">BUGS</a>
|
||||
<a href="/lightning/ledger" class="mc-mobile-link">LEDGER</a>
|
||||
<a href="/memory" class="mc-mobile-link">MEMORY</a>
|
||||
<a href="/work-orders/queue" class="mc-mobile-link">WORK ORDERS</a>
|
||||
<a href="/router/status" class="mc-mobile-link">ROUTER</a>
|
||||
<a href="/grok/status" class="mc-mobile-link">GROK</a>
|
||||
<a href="/self-modify/queue" class="mc-mobile-link">UPGRADES</a>
|
||||
<a href="/self-coding" class="mc-mobile-link">SELF-CODING</a>
|
||||
<a href="/hands" class="mc-mobile-link">HANDS</a>
|
||||
<a href="/work-orders/queue" class="mc-mobile-link">WORK ORDERS</a>
|
||||
<a href="/creative/ui" class="mc-mobile-link">CREATIVE</a>
|
||||
<a href="/voice/button" class="mc-mobile-link">VOICE</a>
|
||||
<a href="/mobile" class="mc-mobile-link">MOBILE</a>
|
||||
@@ -125,7 +129,6 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc4s9bIOgUxi8T/jzmE6bgx5xwkVYG3WhIEOFSjBqg4X" crossorigin="anonymous"></script>
|
||||
<script src="/static/notifications.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -50,8 +50,12 @@
|
||||
|
||||
<script>
|
||||
function scrollChat() {
|
||||
const log = document.getElementById('chat-log');
|
||||
if (log) log.scrollTop = log.scrollHeight;
|
||||
var log = document.getElementById('chat-log');
|
||||
if (log) {
|
||||
requestAnimationFrame(function() {
|
||||
log.scrollTop = log.scrollHeight;
|
||||
});
|
||||
}
|
||||
}
|
||||
function scrollAgentLog(id) {
|
||||
const log = document.getElementById('agent-log-' + id);
|
||||
|
||||
@@ -70,14 +70,6 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function scrollChat() {
|
||||
var log = document.getElementById('chat-log');
|
||||
if (log) {
|
||||
requestAnimationFrame(function() {
|
||||
log.scrollTop = log.scrollHeight;
|
||||
});
|
||||
}
|
||||
}
|
||||
scrollChat();
|
||||
|
||||
function askGrok() {
|
||||
|
||||
@@ -29,21 +29,6 @@ def test_create_timmy_agent_name():
|
||||
assert kwargs["name"] == "Timmy"
|
||||
|
||||
|
||||
def test_create_timmy_uses_default_model():
|
||||
with patch("timmy.agent.Agent"), \
|
||||
patch("timmy.agent.Ollama") as MockOllama, \
|
||||
patch("timmy.agent.SqliteDb"):
|
||||
|
||||
from config import settings
|
||||
from timmy.agent import create_timmy
|
||||
create_timmy()
|
||||
|
||||
MockOllama.assert_called_once()
|
||||
kwargs = MockOllama.call_args.kwargs
|
||||
# Default model should match configured setting
|
||||
assert kwargs["id"] == settings.ollama_model
|
||||
|
||||
|
||||
def test_create_timmy_history_config():
|
||||
with patch("timmy.agent.Agent") as MockAgent, \
|
||||
patch("timmy.agent.Ollama"), \
|
||||
|
||||
Reference in New Issue
Block a user