aggiunta lista modelli
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+2
-1
@@ -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")
|
||||
|
||||
@@ -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]] = []
|
||||
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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 */}
|
||||
<div className="col-5 text-center">
|
||||
<h4 className="mb-0">🤖 EgalWare's LLM ChatBot</h4>
|
||||
<h4 className="mb-0" title={`Default model: ${defaultModelName}`}>
|
||||
🤖 EgalWare's LLM ChatBot
|
||||
<button className="ms-2 btn btn-sm btn-outline-info" onClick={onShowModels}>
|
||||
📊 Modelli
|
||||
</button>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
{/* Right column: toggle tema solo se c'è sessione */}
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className={`chat-layout sessions-wrapper ${theme.bodyBg}`}>
|
||||
@@ -59,14 +70,20 @@ export default function ChatLayout({
|
||||
onCreateSession={onCreateSession}
|
||||
onEditSession={onEditSession}
|
||||
onManageSession={openSessionManager}
|
||||
onShowModels={openModelsDetail}
|
||||
sessionId={sessionId}
|
||||
sessionName={sessionName}
|
||||
defaultModelName={defaultModelName}
|
||||
/>
|
||||
|
||||
{/* AREA SCROLLABILE */}
|
||||
<div className="flex-grow-1 overflow-auto" style={{ minHeight: 0 }}>
|
||||
<ChatWindow messages={messages} loading={loading} theme={theme} />
|
||||
</div>
|
||||
{showModelPage ? (
|
||||
<ModelOverview onBackToChat={() => setShowModelPage(false)} />
|
||||
) : (
|
||||
/* AREA SCROLLABILE */
|
||||
<div className="flex-grow-1 overflow-auto" style={{ minHeight: 0 }}>
|
||||
<ChatWindow messages={messages} loading={loading} theme={theme} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* INPUT SEMPRE IN BASSO */}
|
||||
{sessionId ? (
|
||||
|
||||
@@ -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 (
|
||||
<div className="container py-4">
|
||||
<div className="bg-light p-4 rounded shadow-sm mb-4">
|
||||
<h3 className="mb-0">📌 Modello di default</h3>
|
||||
<p className="lead text-muted mb-0">
|
||||
{defaultModel || "Non disponibile"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="d-flex justify-content-between mb-3">
|
||||
<button className="btn btn-outline-primary" onClick={onBackToChat}>
|
||||
⬅ Torna alla chat
|
||||
</button>
|
||||
<button className="btn btn-outline-success" onClick={updateModelData}>
|
||||
🔄 Aggiorna dati modelli
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="table-responsive">
|
||||
<table className="table table-bordered table-hover align-middle">
|
||||
<thead className="table-light">
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>Descrizione</th>
|
||||
<th>Anno</th>
|
||||
<th>Caratteristiche</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading && (
|
||||
<tr>
|
||||
<td colSpan="4" className="text-center">Caricamento…</td>
|
||||
</tr>
|
||||
)}
|
||||
{!loading && modelInfo.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan="4" className="text-center">Nessun modello disponibile</td>
|
||||
</tr>
|
||||
)}
|
||||
{!loading && modelInfo.map((model) => (
|
||||
<tr key={model.name}>
|
||||
<td>{model.name}</td>
|
||||
<td>{model.description || "—"}</td>
|
||||
<td>{model.year || "—"}</td>
|
||||
<td>
|
||||
<ul className="mb-0 small">
|
||||
{model.features?.map((f, i) => (
|
||||
<li key={i}>{f}</li>
|
||||
)) || <li>—</li>}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ export default function SessionTable({ userId, onSelectSession, onClosePanel })
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Session/Model</th>
|
||||
<th class="text-end">Created/Mess</th>
|
||||
<th className="text-end">Created/Mess</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -160,9 +160,9 @@ export default function SessionTable({ userId, onSelectSession, onClosePanel })
|
||||
: "default"}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="small">{new Date(s.created_at).toLocaleString()}</div>
|
||||
<div class="small">
|
||||
<td className="text-end">
|
||||
<div className="small">{new Date(s.created_at).toLocaleString()}</div>
|
||||
<div className="small">
|
||||
# mess: {s.message_count}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user