initial version

This commit is contained in:
vadimwit
2026-03-05 16:51:28 +00:00
commit 5e7954ee24
31 changed files with 4832 additions and 0 deletions
View File
+21
View File
@@ -0,0 +1,21 @@
from typing import Literal
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
ai_mode: Literal["online", "offline"] = "offline"
# Online (Claude) settings
anthropic_api_key: str = ""
claude_model: str = "claude-sonnet-4-6"
# Offline (Ollama) settings
ollama_host: str = "http://localhost:11434"
ollama_model: str = "llama3.2:3b"
cors_origins: list[str] = ["http://localhost:5173", "http://localhost:3000"]
settings = Settings()
View File
+32
View File
@@ -0,0 +1,32 @@
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from api.services.ai import chat
router = APIRouter()
class Message(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
messages: list[Message]
context: dict | None = None
class ChatResponse(BaseModel):
reply: str
@router.post("/chat", response_model=ChatResponse)
async def chat_endpoint(request: ChatRequest):
try:
reply = await chat(
messages=[m.model_dump() for m in request.messages],
context=request.context,
)
return ChatResponse(reply=reply)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
View File
+50
View File
@@ -0,0 +1,50 @@
from api.config import settings
SYSTEM_PROMPT = """You are a friendly, beginner-aware music theory assistant for WhatTheFlat — a live jam helper app.
You help musicians understand what's happening harmonically during a jam session.
Keep answers short, warm, and jargon-free. If you use a music term, briefly explain it.
Never say someone played a "wrong" note — always frame it as "try these instead".
When given context about the current key and chord, use it in your response."""
async def chat(messages: list[dict], context: dict | None = None) -> str:
system = SYSTEM_PROMPT
if context:
parts = []
if context.get("key"):
parts.append(f"Current key: {context['key']}")
if context.get("chord"):
parts.append(f"Current chord: {context['chord']}")
if parts:
system += "\n\nLive session context:\n" + "\n".join(parts)
if settings.ai_mode == "online":
return await _chat_claude(messages, system)
else:
return await _chat_ollama(messages, system)
async def _chat_claude(messages: list[dict], system: str) -> str:
import anthropic
client = anthropic.AsyncAnthropic(api_key=settings.anthropic_api_key)
response = await client.messages.create(
model=settings.claude_model,
max_tokens=512,
system=system,
messages=messages,
)
return response.content[0].text
async def _chat_ollama(messages: list[dict], system: str) -> str:
import ollama
client = ollama.AsyncClient(host=settings.ollama_host)
full_messages = [{"role": "system", "content": system}] + messages
response = await client.chat(
model=settings.ollama_model,
messages=full_messages,
)
return response.message.content