diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock deleted file mode 100644 index a6a0907..0000000 --- a/.claude/scheduled_tasks.lock +++ /dev/null @@ -1 +0,0 @@ -{"sessionId":"e480a2be-e00b-4a51-8cfc-f525e281eccb","pid":17506,"acquiredAt":1776357371970} \ No newline at end of file diff --git a/.env.example b/.env.example index 695865e..5ed921d 100644 --- a/.env.example +++ b/.env.example @@ -17,9 +17,9 @@ MEILI_KEY=shop-dev-master-key # Ollama OLLAMA_URL=http://localhost:11434 -OLLAMA_CHAT_MODEL=llama3.1 -OLLAMA_EMBED_MODEL=nomic-embed-text -OLLAMA_EMBED_DIM=768 +OLLAMA_CHAT_MODEL=qwen2.5:7b +OLLAMA_EMBED_MODEL=bge-m3 +OLLAMA_EMBED_DIM=1024 # Mail (Mailhog in dev) SMTP_HOST=localhost diff --git a/CLAUDE.md b/CLAUDE.md index 3e1c626..acc409f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,7 @@ Custom E-Commerce Shopsystem (B2C + B2B), weltweit einsetzbar, als verkaufbares - **Frontend**: Vue 3 + Vite + TypeScript + Pinia + Vue Router — Tooling: `pnpm`, `eslint`, `prettier`, `vitest`. Nur Web, responsives Design. **Zwei getrennte Vue-Apps mit separatem Build:** `shop` und `admin`. - **Auth**: JWT (Access + Refresh), `argon2` für Passwort-Hashing -- **KI**: Ollama (lokal) via RAG — Standard-Modelle `llama3.1` (Chat) + `nomic-embed-text` (Embeddings), per Abstraktion austauschbar +- **KI**: Ollama (lokal) via RAG — Standard-Modelle `qwen2.5:7b` (Chat) + `bge-m3` (Embeddings), per Abstraktion austauschbar - **Suche**: austauschbar per Such-Abstraktion (Standard Meilisearch) - **DB-Migrationen**: Alembic (SQLAlchemy) - **i18n**: DE + EN initial diff --git a/backend/apps/ai_admin/planner.py b/backend/apps/ai_admin/planner.py index 5d96a20..0cf4be8 100644 --- a/backend/apps/ai_admin/planner.py +++ b/backend/apps/ai_admin/planner.py @@ -8,6 +8,7 @@ from core.db import SessionLocal from apps.ai_core.ollama_client import get_llm from apps.ai_core.tools import describe_for_prompt, get_tool, validate_args from apps.catalog.models import Category, Product +from apps.orders.models import Order SYSTEM_PROMPT = """You are an admin assistant for an e-commerce shop. @@ -44,7 +45,11 @@ def _shop_state_snapshot() -> dict: {"id": c.id, "slug": c.slug, "name_de": (c.name or {}).get("de", "")} for c in db.query(Category).order_by(Category.id).all() ] - return {"products": products, "categories": categories} + orders = [ + {"id": o.id, "status": o.status, "total": float(o.total)} + for o in db.query(Order).order_by(Order.id.desc()).limit(20).all() + ] + return {"products": products, "categories": categories, "orders": orders} finally: db.close() @@ -54,7 +59,7 @@ def build_plan(user_prompt: str) -> list[dict]: state = _shop_state_snapshot() user_msg = ( f"TOOL CATALOG (JSON):\n{json.dumps(tools, ensure_ascii=False)}\n\n" - f"SHOP STATE (current products & categories):\n{json.dumps(state, ensure_ascii=False)}\n\n" + f"SHOP STATE (current products, categories, recent orders):\n{json.dumps(state, ensure_ascii=False)}\n\n" f"USER REQUEST:\n{user_prompt}\n\n" "Reply with ONLY the JSON object described in the rules." ) diff --git a/backend/apps/ai_admin/tool_defs.py b/backend/apps/ai_admin/tool_defs.py index 6f090f7..ff6f78a 100644 --- a/backend/apps/ai_admin/tool_defs.py +++ b/backend/apps/ai_admin/tool_defs.py @@ -216,10 +216,49 @@ CATEGORY_CREATE = ToolSpec( ) +# ---- orders.update_status ----------------------------------------- + + +def _handler_order_status(args: dict, db: Session) -> dict: + from apps.orders.models import Order, OrderStatusHistory + + order_id = int(args["id"]) + o = db.get(Order, order_id) + if not o: + raise ValueError(f"Order {order_id} not found") + new_status = args["status"] + note = _coalesce(args.get("note"), "") + o.status = new_status + db.add(OrderStatusHistory(order_id=order_id, status=new_status, note=note)) + db.commit() + event_bus.publish("order.status_changed", {"id": order_id, "status": new_status}, db=db) + return {"id": order_id, "status": new_status} + + +ORDER_STATUS = ToolSpec( + name="orders.update_status", + description="Change the status of an order (e.g. paid, packed, shipped, delivered, cancelled).", + args_schema={ + "type": "object", + "required": ["id", "status"], + "properties": { + "id": {"type": "integer", "description": "Order ID"}, + "status": { + "type": "string", + "description": "New status: paid, packed, shipped, delivered, cancelled", + }, + "note": {"type": "string", "description": "Optional note"}, + }, + }, + handler=_handler_order_status, + examples=[{"id": 1, "status": "shipped", "note": "DHL versendet"}], +) + + # ---- registration ------------------------------------------------- -ALL_TOOLS = [SETTINGS_UPDATE, PRODUCT_CREATE, PRODUCT_UPDATE, CATEGORY_CREATE] +ALL_TOOLS = [SETTINGS_UPDATE, PRODUCT_CREATE, PRODUCT_UPDATE, CATEGORY_CREATE, ORDER_STATUS] def register_all() -> None: diff --git a/backend/core/config.py b/backend/core/config.py index 65e1934..0a871d5 100644 --- a/backend/core/config.py +++ b/backend/core/config.py @@ -12,9 +12,9 @@ class Settings(BaseSettings): MEILI_KEY: str = "shop-dev-master-key" OLLAMA_URL: str = "http://localhost:11434" - OLLAMA_CHAT_MODEL: str = "llama3.1" - OLLAMA_EMBED_MODEL: str = "nomic-embed-text" - OLLAMA_EMBED_DIM: int = 768 + OLLAMA_CHAT_MODEL: str = "qwen2.5:7b" + OLLAMA_EMBED_MODEL: str = "bge-m3" + OLLAMA_EMBED_DIM: int = 1024 SMTP_HOST: str = "localhost" SMTP_PORT: int = 1025 diff --git a/backend/core/migrations/versions/0007_ai_embed_1024.py b/backend/core/migrations/versions/0007_ai_embed_1024.py new file mode 100644 index 0000000..d3f1bc8 --- /dev/null +++ b/backend/core/migrations/versions/0007_ai_embed_1024.py @@ -0,0 +1,28 @@ +"""ai_core: change embedding dim from 768 to 1024 (bge-m3) + +Revision ID: 0007_embed1024 +Revises: 0006_ai_core +Create Date: 2026-04-17 + +""" +from collections.abc import Sequence + +import sqlalchemy as sa +from alembic import op + +revision: str = "0007_embed1024" +down_revision: str | None = "0006_ai_core" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + # Drop and recreate with new dimension — truncating old embeddings is fine, + # reindex will rebuild them. + op.execute("DELETE FROM ai_documents") + op.execute("ALTER TABLE ai_documents ALTER COLUMN embedding TYPE vector(1024)") + + +def downgrade() -> None: + op.execute("DELETE FROM ai_documents") + op.execute("ALTER TABLE ai_documents ALTER COLUMN embedding TYPE vector(768)") diff --git a/base.md b/base.md index c6acfe7..55965cd 100644 --- a/base.md +++ b/base.md @@ -23,7 +23,7 @@ Kommunikation zwischen Apps: **Events** (lose Kopplung) + **Dependency Injection - **Datenbank**: PostgreSQL (Write-Store) + Redis (Read-Store) - **Frontend**: Vue 3 + Vite + TypeScript + Pinia + Vue Router (Tooling: `pnpm`, `eslint`, `prettier`, `vitest`) — nur Web, responsives Design für Mobile. **Zwei getrennte Vue-Apps mit eigenem Build:** `shop` (Kundensicht) und `admin`. -- **KI**: Lokale KI via **Ollama** (Standard-Modelle z.B. `llama3.1` für Chat, `nomic-embed-text` für Embeddings), RAG über Shop-/Produkt-/Einstellungs-Daten. Per Abstraktion austauschbar. +- **KI**: Lokale KI via **Ollama** (Standard-Modelle z.B. `qwen2.5:7b` für Chat, `bge-m3` für Embeddings), RAG über Shop-/Produkt-/Einstellungs-Daten. Per Abstraktion austauschbar. - **Auth**: JWT (Access + Refresh), Passwort-Hashing via `argon2` - **Migrationen**: Alembic (SQLAlchemy) - **Suche**: austauschbar per Such-Abstraktion (Standard Meilisearch) diff --git a/doc/core/authentication/features.md b/doc/core/authentication/features.md new file mode 100644 index 0000000..bdae93b --- /dev/null +++ b/doc/core/authentication/features.md @@ -0,0 +1,6 @@ +- authentication + - "who are you?" + - password hashing with argon2 + - JWT (15 min access) with refresh (30 days) + - identity dependencies (current_user_claims, optional_user, get_current_user_id, oauth2_scheme) + - room for growth: OAuth/SSO, API-tokens for third-party apps, 2FA, refresh-token rotation, impersonation \ No newline at end of file diff --git a/doc/core/authentication/specs.md b/doc/core/authentication/specs.md new file mode 100644 index 0000000..a12f0bb --- /dev/null +++ b/doc/core/authentication/specs.md @@ -0,0 +1 @@ +- authentication.py diff --git a/doc/core/authorization/features.md b/doc/core/authorization/features.md new file mode 100644 index 0000000..b4c4836 --- /dev/null +++ b/doc/core/authorization/features.md @@ -0,0 +1,4 @@ +- authorization + - "what are you allowed to do?" + - role checks (require_admin today) + - room for growth: require_role, require_permission, per-resource checks (owner-of), B2B approval workflows, per-app permissions for marketplace apps \ No newline at end of file diff --git a/doc/core/authorization/specs.md b/doc/core/authorization/specs.md new file mode 100644 index 0000000..1b4d348 --- /dev/null +++ b/doc/core/authorization/specs.md @@ -0,0 +1 @@ +- authorization.py diff --git a/doc/core/i18n/features.md b/doc/core/i18n/features.md new file mode 100644 index 0000000..1327da2 --- /dev/null +++ b/doc/core/i18n/features.md @@ -0,0 +1,2 @@ +- i18n + - internationalisation helper for DE/EN text fields \ No newline at end of file diff --git a/doc/core/i18n/specs.md b/doc/core/i18n/specs.md new file mode 100644 index 0000000..7042fe6 --- /dev/null +++ b/doc/core/i18n/specs.md @@ -0,0 +1 @@ +- i18n.py diff --git a/doc/core/middleware/features.md b/doc/core/middleware/features.md new file mode 100644 index 0000000..596c630 --- /dev/null +++ b/doc/core/middleware/features.md @@ -0,0 +1,4 @@ +- middleware + - central place to install FastAPI middlewares (install_middlewares(app)) + - today: CORS (allowed origins from .env) + - room for growth: request-id, access logging, rate-limit, security headers (HSTS/CSP), compression \ No newline at end of file diff --git a/doc/core/middleware/specs.md b/doc/core/middleware/specs.md new file mode 100644 index 0000000..8fd640b --- /dev/null +++ b/doc/core/middleware/specs.md @@ -0,0 +1 @@ +- middleware.py diff --git a/doc/core/migrations/features.md b/doc/core/migrations/features.md new file mode 100644 index 0000000..ef4adec --- /dev/null +++ b/doc/core/migrations/features.md @@ -0,0 +1,5 @@ +- migrations + - orchestrator (migrations.py): discover per-app migration folders (apps//migrations/), configure alembic version_locations dynamically, coordinate multi-head merging + - startup check: fail fast if schema is not up to date + - migrations/ directory: alembic version store (today still holds all migrations centrally; per-app folders are the target state) + - use alembic \ No newline at end of file diff --git a/doc/core/migrations/specs.md b/doc/core/migrations/specs.md new file mode 100644 index 0000000..26be887 --- /dev/null +++ b/doc/core/migrations/specs.md @@ -0,0 +1,2 @@ +- migrations.py +- migrations/ (Alembic version store) diff --git a/doc/core/seed/features.md b/doc/core/seed/features.md new file mode 100644 index 0000000..f7089d5 --- /dev/null +++ b/doc/core/seed/features.md @@ -0,0 +1,2 @@ +- seed + - demo data (admin, demo customer, categories, products) \ No newline at end of file diff --git a/doc/core/seed/specs.md b/doc/core/seed/specs.md new file mode 100644 index 0000000..35172fa --- /dev/null +++ b/doc/core/seed/specs.md @@ -0,0 +1 @@ +- seed.py diff --git a/doc/core/settings/features.md b/doc/core/settings/features.md new file mode 100644 index 0000000..1341a82 --- /dev/null +++ b/doc/core/settings/features.md @@ -0,0 +1,6 @@ +- settings + - key-value store for shop settings (runtime-changeable, e.g. shop_name, currency) + - postgres is source of truth + - mirrored to redis on write + - emits core.settings_updated event + - distinct from config (which only reads .env infrastructure values) \ No newline at end of file diff --git a/doc/core/settings/specs.md b/doc/core/settings/specs.md new file mode 100644 index 0000000..96d6154 --- /dev/null +++ b/doc/core/settings/specs.md @@ -0,0 +1 @@ +- settings.py diff --git a/docker-compose.yml b/docker-compose.yml index e8c8d76..a20b5f9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,8 +53,8 @@ services: entrypoint: ["/bin/sh", "-c"] command: > "sleep 5 && - OLLAMA_HOST=ollama:11434 ollama pull llama3.1 && - OLLAMA_HOST=ollama:11434 ollama pull nomic-embed-text && + OLLAMA_HOST=ollama:11434 ollama pull qwen2.5:7b && + OLLAMA_HOST=ollama:11434 ollama pull bge-m3 && echo 'Models ready'" restart: "no"