diff --git a/backend/api/v1/chat.py b/backend/api/v1/chat.py index 0fe2c06..3a128b1 100644 --- a/backend/api/v1/chat.py +++ b/backend/api/v1/chat.py @@ -14,19 +14,6 @@ from config import settings router = APIRouter() -@router.get("/models", response_model=List[str]) -async def list_models(): - try: - async with httpx.AsyncClient(timeout=settings.REQUEST_TIMEOUT) as client: - resp = await client.get(settings.LM_STUDIO_MODELS) - resp.raise_for_status() - data = resp.json() - return [m["id"] for m in data.get("data", [])] - except Exception: - logger.exception("Error fetching models") - raise HTTPException(status_code=500, detail="Failed to fetch models") - - @router.post("/chat", response_model=ChatResponse) async def chat_endpoint(payload: ChatRequest): try: diff --git a/backend/api/v1/models.py b/backend/api/v1/models.py new file mode 100644 index 0000000..7344b6e --- /dev/null +++ b/backend/api/v1/models.py @@ -0,0 +1,69 @@ +# api/v1/models_info.py +import httpx +import json +from fastapi import APIRouter +from config import settings +from models.llm_model import LmModelMeta +from services import redis_service +from typing import List +from utils.logging import logger +from config import settings + +router = APIRouter() + +@router.get("/models", response_model=List[str]) +async def list_models(): + try: + async with httpx.AsyncClient(timeout=settings.REQUEST_TIMEOUT) as client: + resp = await client.get(settings.LM_STUDIO_MODELS) + resp.raise_for_status() + data = resp.json() + return [m["id"] for m in data.get("data", [])] + except Exception: + logger.exception("Error fetching models") + raise HTTPException(status_code=500, detail="Failed to fetch models") + +@router.get("/default-model", response_model=str) +async def get_default_model(): + return settings.MODEL_NAME + +@router.get("/models-info", response_model=List[LmModelMeta]) +async def get_model_info(): + raw = redis_service.r.get("modelInfoList") + if not raw: + return [] + try: + data = json.loads(raw) + return [LmModelMeta(**item) for item in data] + except Exception: + return [] + +@router.post("/models-info/update", response_model=List[LmModelMeta]) +async def update_model_info(): + import httpx + + try: + async with httpx.AsyncClient(timeout=settings.REQUEST_TIMEOUT) as client: + resp = await client.get(settings.LM_STUDIO_MODELS) + resp.raise_for_status() + model_list = [m["id"] for m in resp.json().get("data", [])] + except Exception: + raise HTTPException(status_code=500, detail="Failed to fetch models") + + enriched = [] + for name in model_list: + enriched.append(LmModelMeta( + name=name, + description=f"Modello {name.split('/')[-1]}", + year=2024, # puoi migliorare con heuristics + features=[ + "Quantizzazione INT4" if "int4" in name.lower() else "Precisione standard", + f"{name.split('-')[0].upper()} origin", + f"{name.split('-')[1]} variant" if "-" in name else "Generico" + ] + )) + + redis_service.r.set("modelInfoList", json.dumps([m.dict() for m in enriched]), ex=86400) + return enriched + + diff --git a/backend/main.py b/backend/main.py index 4d03048..51c3207 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,7 +1,7 @@ # main.py (new project entrypoint) from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from api.v1 import chat, sessions # import the routers +from api.v1 import chat, sessions, models # import the routersS app = FastAPI() @@ -15,3 +15,4 @@ app.add_middleware( # Mount the router under /v1 app.include_router(chat.router, prefix="/v1") app.include_router(sessions.router, prefix="/v1") +app.include_router(models.router, prefix="/v1") diff --git a/backend/models/llm_model.py b/backend/models/llm_model.py new file mode 100644 index 0000000..cff8cd0 --- /dev/null +++ b/backend/models/llm_model.py @@ -0,0 +1,11 @@ +# models/llm_model.py +from pydantic import BaseModel +from typing import List, Optional + +class LmModelMeta(BaseModel): + name: str + description: Optional[str] = None + year: Optional[int] = 0 + features: Optional[List[str]] = [] + + diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 785c1ee..7da389a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -19,6 +19,14 @@ export default function App() { const sessionId = getSessionId(); const [showEditor, setShowEditor] = useState(false); const [editorMode, setEditorMode] = useState("create"); + const [defaultModelName, setDefaultModelName] = useState(""); + + useEffect(() => { + fetch("/v1/default-model") + .then(res => res.text()) + .then(setDefaultModelName) + .catch(err => console.error("Errore nel recupero modello di default", err)); + }, []); useEffect(() => { const saved = localStorage.getItem("preferredTheme"); @@ -124,6 +132,7 @@ export default function App() { sessionId={sessionId} sessionModelName={sessionModelName} sessionName={sessionName} + defaultModelName={defaultModelName} /> {showEditor && ( diff --git a/frontend/src/ChatHeader.jsx b/frontend/src/ChatHeader.jsx index c3fe0b5..c785bd5 100644 --- a/frontend/src/ChatHeader.jsx +++ b/frontend/src/ChatHeader.jsx @@ -8,8 +8,10 @@ export default function ChatHeader({ onFreshStart, onCreateSession, onManageSession, + onShowModels, sessionId, - sessionName + sessionName, + defaultModelName }) { const hasSession = Boolean(sessionId); @@ -70,7 +72,12 @@ export default function ChatHeader({ {/* Center column: titolo solo se c'è sessione */}
-

🤖 EgalWare's LLM ChatBot

+

+ 🤖 EgalWare's LLM ChatBot + +

{/* Right column: toggle tema solo se c'è sessione */} diff --git a/frontend/src/ChatLayout.jsx b/frontend/src/ChatLayout.jsx index 8e17463..5795c82 100644 --- a/frontend/src/ChatLayout.jsx +++ b/frontend/src/ChatLayout.jsx @@ -5,6 +5,7 @@ import ChatWindow from "./ChatWindow"; import ChatInput from "./ChatInput"; import SessionTable from "./SessionTable"; import NoSessionBox from "./NoSessionBox"; +import ModelOverview from "./ModelOverview"; export default function ChatLayout({ theme, @@ -21,9 +22,11 @@ export default function ChatLayout({ userId, sessionId, sessionModelName, - sessionName + sessionName, + defaultModelName }) { const [showSessionsPanel, setShowSessionsPanel] = useState(false); + const [showModelPage, setShowModelPage] = useState(false); function getScrollbarWidth() { return window.innerWidth - document.documentElement.clientWidth; @@ -47,6 +50,14 @@ export default function ChatLayout({ document.body.classList.remove("sessions-open"); }; + const openModelsDetail = () => { + setShowModelPage(true); + }; + + const closeModelsDetail = () => { + setShowModelPage(true); + }; + return ( <>
@@ -59,14 +70,20 @@ export default function ChatLayout({ onCreateSession={onCreateSession} onEditSession={onEditSession} onManageSession={openSessionManager} + onShowModels={openModelsDetail} sessionId={sessionId} sessionName={sessionName} + defaultModelName={defaultModelName} /> - {/* AREA SCROLLABILE */} -
- -
+ {showModelPage ? ( + setShowModelPage(false)} /> + ) : ( + /* AREA SCROLLABILE */ +
+ +
+ )} {/* INPUT SEMPRE IN BASSO */} {sessionId ? ( diff --git a/frontend/src/ModelOverview.jsx b/frontend/src/ModelOverview.jsx new file mode 100644 index 0000000..bab35ef --- /dev/null +++ b/frontend/src/ModelOverview.jsx @@ -0,0 +1,106 @@ +// src/ModelOverview.jsx + +import React, { useEffect, useState } from "react"; + +export default function ModelOverview({ onBackToChat }) { + const [defaultModel, setDefaultModel] = useState(""); + const [modelInfo, setModelInfo] = useState([]); + const [loading, setLoading] = useState(false); + + const fetchModelData = async () => { + setLoading(true); + try { + const [defaultRes, infoRes] = await Promise.all([ + fetch("/v1/default-model"), + fetch("/v1/models-info") + ]); + const defaultText = await defaultRes.text(); + const infoJson = await infoRes.json(); + setDefaultModel(defaultText); + setModelInfo(infoJson); + } catch (err) { + console.error("Errore nel caricamento modelli", err); + } finally { + setLoading(false); + } + }; + + const updateModelData = async () => { + setLoading(true); + try { + const res = await fetch("/v1/models-info/update", { method: "POST" }); + if (res.ok) { + const updated = await res.json(); + setModelInfo(updated); + } + } catch (err) { + console.error("Errore nell'aggiornamento modelli", err); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchModelData(); + }, []); + + return ( +
+
+

📌 Modello di default

+

+ {defaultModel || "Non disponibile"} +

+
+ +
+ + +
+ +
+ + + + + + + + + + + {loading && ( + + + + )} + {!loading && modelInfo.length === 0 && ( + + + + )} + {!loading && modelInfo.map((model) => ( + + + + + + + ))} + +
NomeDescrizioneAnnoCaratteristiche
Caricamento…
Nessun modello disponibile
{model.name}{model.description || "—"}{model.year || "—"} +
    + {model.features?.map((f, i) => ( +
  • {f}
  • + )) ||
  • } +
+
+
+
+ ); +} + diff --git a/frontend/src/SessionTable.jsx b/frontend/src/SessionTable.jsx index 6ceecc8..696a555 100644 --- a/frontend/src/SessionTable.jsx +++ b/frontend/src/SessionTable.jsx @@ -119,7 +119,7 @@ export default function SessionTable({ userId, onSelectSession, onClosePanel }) Session/Model - Created/Mess + Created/Mess @@ -160,9 +160,9 @@ export default function SessionTable({ userId, onSelectSession, onClosePanel }) : "default"}
- -
{new Date(s.created_at).toLocaleString()}
-
+ +
{new Date(s.created_at).toLocaleString()}
+
# mess: {s.message_count}