This commit is contained in:
Marek
2026-04-19 19:23:16 +02:00
23 changed files with 122 additions and 14 deletions

View File

@@ -1 +0,0 @@
{"sessionId":"e480a2be-e00b-4a51-8cfc-f525e281eccb","pid":17506,"acquiredAt":1776357371970}

View File

@@ -17,9 +17,9 @@ MEILI_KEY=shop-dev-master-key
# Ollama # Ollama
OLLAMA_URL=http://localhost:11434 OLLAMA_URL=http://localhost:11434
OLLAMA_CHAT_MODEL=llama3.1 OLLAMA_CHAT_MODEL=qwen2.5:7b
OLLAMA_EMBED_MODEL=nomic-embed-text OLLAMA_EMBED_MODEL=bge-m3
OLLAMA_EMBED_DIM=768 OLLAMA_EMBED_DIM=1024
# Mail (Mailhog in dev) # Mail (Mailhog in dev)
SMTP_HOST=localhost SMTP_HOST=localhost

View File

@@ -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. - **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`. **Zwei getrennte Vue-Apps mit separatem Build:** `shop` und `admin`.
- **Auth**: JWT (Access + Refresh), `argon2` für Passwort-Hashing - **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) - **Suche**: austauschbar per Such-Abstraktion (Standard Meilisearch)
- **DB-Migrationen**: Alembic (SQLAlchemy) - **DB-Migrationen**: Alembic (SQLAlchemy)
- **i18n**: DE + EN initial - **i18n**: DE + EN initial

View File

@@ -8,6 +8,7 @@ from core.db import SessionLocal
from apps.ai_core.ollama_client import get_llm from apps.ai_core.ollama_client import get_llm
from apps.ai_core.tools import describe_for_prompt, get_tool, validate_args from apps.ai_core.tools import describe_for_prompt, get_tool, validate_args
from apps.catalog.models import Category, Product 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. 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", "")} {"id": c.id, "slug": c.slug, "name_de": (c.name or {}).get("de", "")}
for c in db.query(Category).order_by(Category.id).all() 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: finally:
db.close() db.close()
@@ -54,7 +59,7 @@ def build_plan(user_prompt: str) -> list[dict]:
state = _shop_state_snapshot() state = _shop_state_snapshot()
user_msg = ( user_msg = (
f"TOOL CATALOG (JSON):\n{json.dumps(tools, ensure_ascii=False)}\n\n" 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" f"USER REQUEST:\n{user_prompt}\n\n"
"Reply with ONLY the JSON object described in the rules." "Reply with ONLY the JSON object described in the rules."
) )

View File

@@ -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 ------------------------------------------------- # ---- 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: def register_all() -> None:

View File

@@ -12,9 +12,9 @@ class Settings(BaseSettings):
MEILI_KEY: str = "shop-dev-master-key" MEILI_KEY: str = "shop-dev-master-key"
OLLAMA_URL: str = "http://localhost:11434" OLLAMA_URL: str = "http://localhost:11434"
OLLAMA_CHAT_MODEL: str = "llama3.1" OLLAMA_CHAT_MODEL: str = "qwen2.5:7b"
OLLAMA_EMBED_MODEL: str = "nomic-embed-text" OLLAMA_EMBED_MODEL: str = "bge-m3"
OLLAMA_EMBED_DIM: int = 768 OLLAMA_EMBED_DIM: int = 1024
SMTP_HOST: str = "localhost" SMTP_HOST: str = "localhost"
SMTP_PORT: int = 1025 SMTP_PORT: int = 1025

View File

@@ -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)")

View File

@@ -23,7 +23,7 @@ Kommunikation zwischen Apps: **Events** (lose Kopplung) + **Dependency Injection
- **Datenbank**: PostgreSQL (Write-Store) + Redis (Read-Store) - **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. - **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`. **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` - **Auth**: JWT (Access + Refresh), Passwort-Hashing via `argon2`
- **Migrationen**: Alembic (SQLAlchemy) - **Migrationen**: Alembic (SQLAlchemy)
- **Suche**: austauschbar per Such-Abstraktion (Standard Meilisearch) - **Suche**: austauschbar per Such-Abstraktion (Standard Meilisearch)

View File

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

View File

@@ -0,0 +1 @@
- authentication.py

View File

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

View File

@@ -0,0 +1 @@
- authorization.py

View File

@@ -0,0 +1,2 @@
- i18n
- internationalisation helper for DE/EN text fields

1
doc/core/i18n/specs.md Normal file
View File

@@ -0,0 +1 @@
- i18n.py

View File

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

View File

@@ -0,0 +1 @@
- middleware.py

View File

@@ -0,0 +1,5 @@
- migrations
- orchestrator (migrations.py): discover per-app migration folders (apps/<name>/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

View File

@@ -0,0 +1,2 @@
- migrations.py
- migrations/ (Alembic version store)

View File

@@ -0,0 +1,2 @@
- seed
- demo data (admin, demo customer, categories, products)

1
doc/core/seed/specs.md Normal file
View File

@@ -0,0 +1 @@
- seed.py

View File

@@ -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)

View File

@@ -0,0 +1 @@
- settings.py

View File

@@ -53,8 +53,8 @@ services:
entrypoint: ["/bin/sh", "-c"] entrypoint: ["/bin/sh", "-c"]
command: > command: >
"sleep 5 && "sleep 5 &&
OLLAMA_HOST=ollama:11434 ollama pull llama3.1 && OLLAMA_HOST=ollama:11434 ollama pull qwen2.5:7b &&
OLLAMA_HOST=ollama:11434 ollama pull nomic-embed-text && OLLAMA_HOST=ollama:11434 ollama pull bge-m3 &&
echo 'Models ready'" echo 'Models ready'"
restart: "no" restart: "no"