Fix streamlit model, fix model selection display
This commit is contained in:
+17
-17
@@ -13,34 +13,41 @@ 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:
|
||||
# Creazione sessione se non esiste
|
||||
session_id = payload.session_id
|
||||
if not session_id:
|
||||
meta = redis_service.create_session(payload.user_id, payload.message)
|
||||
session_id = meta["session_id"]
|
||||
|
||||
# Salva messaggio utente
|
||||
redis_service.save_chat(payload.user_id, session_id, {"role": "user", "content": payload.message})
|
||||
|
||||
# Prepara la history ottimizzata
|
||||
history_to_send = await prepare_history(payload.user_id, session_id)
|
||||
|
||||
# Chiamata a LM Studio
|
||||
model_to_use = payload.model_name or settings.MODEL_NAME
|
||||
|
||||
async with httpx.AsyncClient(timeout=settings.REQUEST_TIMEOUT) as client:
|
||||
resp = await client.post(
|
||||
settings.LM_STUDIO_URL,
|
||||
json={"model": settings.MODEL_NAME, "messages": history_to_send},
|
||||
json={"model": model_to_use, "messages": history_to_send},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
|
||||
reply = data["choices"][0]["message"]["content"]
|
||||
|
||||
# Salva risposta assistant
|
||||
redis_service.save_chat(payload.user_id, session_id, {"role": "assistant", "content": reply})
|
||||
|
||||
return ChatResponse(response=reply, session_id=session_id)
|
||||
@@ -52,20 +59,14 @@ async def chat_endpoint(payload: ChatRequest):
|
||||
|
||||
@router.post("/chat-stream")
|
||||
async def chat_stream_endpoint(payload: ChatRequest):
|
||||
"""
|
||||
Streams model output token-by-token usando SSE,
|
||||
con windowing + summarization + condensed history.
|
||||
"""
|
||||
session_id = payload.session_id
|
||||
if not session_id:
|
||||
meta = redis_service.create_session(payload.user_id, payload.message)
|
||||
session_id = meta["session_id"]
|
||||
|
||||
# Salva messaggio utente
|
||||
redis_service.save_chat(payload.user_id, session_id, {"role": "user", "content": payload.message})
|
||||
|
||||
# Prepara la history ottimizzata
|
||||
history_to_send = await prepare_history(payload.user_id, session_id)
|
||||
model_to_use = payload.model_name or settings.MODEL_NAME
|
||||
|
||||
async def event_generator():
|
||||
assistant_text = ""
|
||||
@@ -75,7 +76,7 @@ async def chat_stream_endpoint(payload: ChatRequest):
|
||||
"POST",
|
||||
settings.LM_STUDIO_URL,
|
||||
json={
|
||||
"model": settings.MODEL_NAME,
|
||||
"model": model_to_use,
|
||||
"messages": history_to_send,
|
||||
"stream": True
|
||||
}
|
||||
@@ -139,4 +140,3 @@ async def delete_history(
|
||||
redis_service.clear_chat(user_id, session_id)
|
||||
return {"status": "cleared"}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# api/v1/sessions.py
|
||||
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
from fastapi import Body, Query, Path, WebSocket, WebSocketDisconnect
|
||||
from services import redis_service
|
||||
from fastapi import APIRouter
|
||||
@@ -17,11 +17,9 @@ router = APIRouter()
|
||||
async def sessions_ws(websocket: WebSocket, user_id: str = Query(...)):
|
||||
await websocket.accept()
|
||||
try:
|
||||
# Invia subito la lista completa
|
||||
sessions = redis_service.get_sessions(user_id)
|
||||
await websocket.send_json({"type": "full_list", "sessions": sessions})
|
||||
|
||||
# Sottoscrizione al canale Redis
|
||||
pubsub = redis_service.r.pubsub()
|
||||
channel = f"sessions:{user_id}"
|
||||
await pubsub.subscribe(channel)
|
||||
@@ -59,10 +57,10 @@ async def get_session_meta_endpoint(
|
||||
@router.post("/sessions", response_model=dict)
|
||||
async def create_session_endpoint(
|
||||
user_id: str = Query(..., description="User ID"),
|
||||
first_message: str = Body("", embed=True)
|
||||
first_message: str = Body("", embed=True),
|
||||
model_name: Optional[str] = Body(None, embed=True) # <-- Accept model_name
|
||||
):
|
||||
meta = redis_service.create_session(user_id, first_message)
|
||||
# Notifica WS
|
||||
meta = redis_service.create_session(user_id, first_message, model_name=model_name)
|
||||
redis_service.r.publish(
|
||||
f"sessions:{user_id}",
|
||||
json.dumps({"type": "created", "session": meta})
|
||||
@@ -73,9 +71,15 @@ async def create_session_endpoint(
|
||||
async def update_session_endpoint(
|
||||
user_id: str = Query(..., description="User ID"),
|
||||
session_id: str = Path(..., description="Session ID"),
|
||||
session_name: str = Body(..., embed=True)
|
||||
session_name: Optional[str] = Body(None, embed=True),
|
||||
model_name: Optional[str] = Body(None, embed=True) # <-- Allow model update
|
||||
):
|
||||
updated = redis_service.update_session_meta(user_id, session_id, session_name=session_name) or {}
|
||||
updated = redis_service.update_session_meta(
|
||||
user_id, session_id,
|
||||
session_name=session_name,
|
||||
model_name=model_name
|
||||
) or {}
|
||||
|
||||
if updated:
|
||||
redis_service.r.publish(
|
||||
f"sessions:{user_id}",
|
||||
@@ -95,4 +99,3 @@ async def delete_session_endpoint(
|
||||
)
|
||||
return {"status": "deleted"}
|
||||
|
||||
|
||||
|
||||
+5
-2
@@ -1,3 +1,4 @@
|
||||
# config.py
|
||||
import os
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
@@ -6,9 +7,11 @@ class Settings(BaseSettings):
|
||||
REDIS_PORT: int = int(os.getenv("REDIS_PORT", 6379))
|
||||
REDIS_DB: int = int(os.getenv("REDIS_DB", 0))
|
||||
LM_STUDIO_URL: str = os.getenv("LM_STUDIO_URL", "http://10.74.83.100:1234/v1/chat/completions")
|
||||
#MODEL_NAME: str = os.getenv("MODEL_NAME", "qwen/qwen3-4b-thinking-2507")
|
||||
LM_STUDIO_MODELS: str = os.getenv("LM_STUDIO_URL", "http://10.74.83.100:1234/v1/models")
|
||||
MODEL_NAME: str = os.getenv("MODEL_NAME", "qwen/qwen3-4b-2507")
|
||||
REQUEST_TIMEOUT: float = float(os.getenv("REQUEST_TIMEOUT", 30.0))
|
||||
#MODEL_NAME: str = os.getenv("MODEL_NAME", "qwen/qwen3-4b-thinking-2507")
|
||||
#MODEL_NAME: str = os.getenv("MODEL_NAME", "openai/gpt-oss-20b")
|
||||
REQUEST_TIMEOUT: float = float(os.getenv("REQUEST_TIMEOUT", 60.0))
|
||||
|
||||
settings = Settings()
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ class ChatRequest(BaseModel):
|
||||
user_id: str # identifier for the user (can be same as session if desired)
|
||||
session_id: Optional[str] = None # new: multi-session handling
|
||||
message: str # user input text
|
||||
model_name: Optional[str] = None # <-- Add this
|
||||
|
||||
class ChatResponse(BaseModel):
|
||||
response: str # assistant's reply
|
||||
|
||||
@@ -9,4 +9,5 @@ class SessionMeta(BaseModel):
|
||||
session_name: str
|
||||
message_count: int = 0
|
||||
history_size_bytes: int = 0
|
||||
model_name: Optional[str] = None # <-- Added field
|
||||
|
||||
|
||||
@@ -76,7 +76,8 @@ def create_session(user_id: str, first_message: str) -> dict:
|
||||
"created_at": created_at,
|
||||
"session_name": session_name,
|
||||
"message_count": 0,
|
||||
"history_size_bytes": 0
|
||||
"history_size_bytes": 0,
|
||||
"model_name": model_name # <-- Add this line
|
||||
}
|
||||
meta_key = f"chatSession:{user_id}:{session_id}"
|
||||
index_key = f"chatSessionsIndex:{user_id}"
|
||||
|
||||
@@ -10,7 +10,7 @@ import "katex/dist/katex.min.css"; // <-- IMPORTANTE
|
||||
export default function App() {
|
||||
const { messages, loading, sendMessage, stopGenerating, setMessages } = useChatStream();
|
||||
const [themeName, setThemeName] = useState("light");
|
||||
const [sessionName, setSessionName] = useState("");
|
||||
const [sessionName, setSessionName, sessionModelName ] = useState("");
|
||||
const theme = themes[themeName];
|
||||
const userId = getUserId();
|
||||
const sessionId = getSessionId();
|
||||
@@ -103,7 +103,8 @@ export default function App() {
|
||||
onSelectSession={handleSelectSession}
|
||||
userId={userId}
|
||||
sessionId={sessionId}
|
||||
sessionName={sessionName} // <-- aggiunto
|
||||
sessionModelName={sessionModelName}
|
||||
sessionName={sessionName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
// src/AssistantMessage.jsx
|
||||
import React, { useState, useEffect } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkMath from "remark-math";
|
||||
import rehypeKatex from "rehype-katex";
|
||||
|
||||
// Prism.js per syntax highlight
|
||||
import Prism from "prismjs";
|
||||
import "prismjs/themes/prism.css";
|
||||
|
||||
// Linguaggi base
|
||||
import "prismjs/components/prism-sql";
|
||||
import "prismjs/components/prism-javascript";
|
||||
import "prismjs/components/prism-css";
|
||||
import "prismjs/components/prism-json";
|
||||
import "prismjs/components/prism-markdown";
|
||||
import "prismjs/components/prism-csharp";
|
||||
import "prismjs/components/prism-lua";
|
||||
import "prismjs/components/prism-c";
|
||||
import "prismjs/components/prism-cpp";
|
||||
import "prismjs/components/prism-python";
|
||||
import "prismjs/components/prism-basic";
|
||||
import "prismjs/components/prism-javascript";
|
||||
|
||||
// Aggiungo alias "vb" che punta a "vbnet"
|
||||
Prism.languages.vb = Prism.languages.vbnet;
|
||||
|
||||
export default function AssistantMessage({
|
||||
content,
|
||||
theme,
|
||||
timestamp,
|
||||
startedAt,
|
||||
endedAt,
|
||||
isFinal
|
||||
}) {
|
||||
const [showThink, setShowThink] = useState(false);
|
||||
const [fadeOut, setFadeOut] = useState(false);
|
||||
|
||||
const ts = timestamp ?? endedAt ?? startedAt;
|
||||
|
||||
const thinkMatch = content?.match(/<think>([\s\S]*?)<\/think>/i);
|
||||
const thinkContent = thinkMatch ? thinkMatch[1].trim() : null;
|
||||
|
||||
const isComplete = isFinal || Boolean(timestamp || endedAt);
|
||||
|
||||
const visibleContent = isComplete
|
||||
? content?.replace(/<think>[\s\S]*?<\/think>/i, "").trim()
|
||||
: content;
|
||||
|
||||
useEffect(() => {
|
||||
if (thinkContent && !isComplete) {
|
||||
setShowThink(true);
|
||||
setFadeOut(false);
|
||||
}
|
||||
if (thinkContent && isComplete) {
|
||||
setFadeOut(true);
|
||||
const timer = setTimeout(() => setShowThink(false), 600);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [thinkContent, isComplete]);
|
||||
|
||||
return (
|
||||
<div className="mb-2 text-start">
|
||||
<div
|
||||
className={`d-inline-block p-2 rounded ${theme.assistantBg}`}
|
||||
style={{ maxWidth: "75%" }}
|
||||
>
|
||||
{showThink && (
|
||||
<div
|
||||
className={`think-block${fadeOut ? " fade-out" : ""}`}
|
||||
style={{
|
||||
fontStyle: "italic",
|
||||
opacity: 0.7,
|
||||
marginBottom: "0.5rem",
|
||||
whiteSpace: "pre-wrap"
|
||||
}}
|
||||
>
|
||||
🤔 {thinkContent}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeKatex]}
|
||||
components={{
|
||||
table: (props) => (
|
||||
<table className="table table-sm table-bordered" {...props} />
|
||||
),
|
||||
th: (props) => <th className="bg-light" {...props} />,
|
||||
code: CodeWithCopy
|
||||
}}
|
||||
>
|
||||
{visibleContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
{ts != null && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.75rem",
|
||||
color: "#666",
|
||||
marginTop: "0.2rem",
|
||||
marginLeft: "0.25rem"
|
||||
}}
|
||||
>
|
||||
{formatDateTime(ts)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeWithCopy({ inline, className = "", children, ...props }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const codeText = String(children).replace(/\n$/, "");
|
||||
const isFencedBlock = !inline && /^language-/.test(className);
|
||||
|
||||
// Evidenziazione con Prism
|
||||
useEffect(() => {
|
||||
if (isFencedBlock) {
|
||||
Prism.highlightAll();
|
||||
}
|
||||
}, [codeText, isFencedBlock]);
|
||||
|
||||
if (!isFencedBlock) {
|
||||
return (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(codeText);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500);
|
||||
} catch (err) {
|
||||
console.error("Copy failed", err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ position: "relative" }}>
|
||||
<pre className={className} {...props} style={{ paddingRight: "2rem" }}>
|
||||
<code className={className}>{codeText}</code>
|
||||
</pre>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "0.25rem",
|
||||
right: "0.25rem",
|
||||
border: "none",
|
||||
background: "transparent",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
className="btn btn-copy shadow"
|
||||
title="Copy to clipboard"
|
||||
>
|
||||
📋
|
||||
</button>
|
||||
{copied && (
|
||||
<span
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "0.25rem",
|
||||
right: "2rem",
|
||||
fontSize: "0.8rem",
|
||||
color: "green"
|
||||
}}
|
||||
>
|
||||
Copied!
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function formatDateTime(dateTime) {
|
||||
const date = dateTime instanceof Date ? dateTime : new Date(dateTime);
|
||||
if (Number.isNaN(date.getTime())) return String(dateTime);
|
||||
return date.toLocaleString(undefined, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+70
-42
@@ -1,13 +1,16 @@
|
||||
// src/ChatInput.jsx
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
const MAX_EXECUTION_TIME_MS_DEFAULT = 2 * 60 * 1000; // 2 minuti
|
||||
const MAX_EXECUTION_TIME_MS_DEFAULT = 2 * 60 * 1000;
|
||||
const EXTRA_TIME_MS = 60 * 1000;
|
||||
const CHAR_LIMIT_FOR_TEXTAREA = 80;
|
||||
const LINE_LIMIT_FOR_TEXTAREA = 1;
|
||||
|
||||
export default function ChatInput({ onSend, onStop, loading }) {
|
||||
export default function ChatInput({ onSend, onStop, loading, sessionModelName = "" }) {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [modelList, setModelList] = useState([]);
|
||||
const [selectedModel, setSelectedModel] = useState(sessionModelName || "");
|
||||
const [timeLeft, setTimeLeft] = useState(null);
|
||||
const [elapsedTime, setElapsedTime] = useState(0);
|
||||
const [maxExecutionTime, setMaxExecutionTime] = useState(MAX_EXECUTION_TIME_MS_DEFAULT);
|
||||
@@ -21,7 +24,21 @@ export default function ChatInput({ onSend, onStop, loading }) {
|
||||
inputValue.length > CHAR_LIMIT_FOR_TEXTAREA ||
|
||||
inputValue.split("\n").length > LINE_LIMIT_FOR_TEXTAREA;
|
||||
|
||||
// Focus automatico
|
||||
// Fetch models and set default from session
|
||||
useEffect(() => {
|
||||
axios.get("/v1/models")
|
||||
.then(res => {
|
||||
const sortedModels = res.data.sort((a, b) => a.localeCompare(b));
|
||||
setModelList(sortedModels);
|
||||
if (sessionModelName && sortedModels.includes(sessionModelName)) {
|
||||
setSelectedModel(sessionModelName);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("❌ Failed to fetch models:", err.message);
|
||||
});
|
||||
}, [sessionModelName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
@@ -30,7 +47,6 @@ export default function ChatInput({ onSend, onStop, loading }) {
|
||||
}
|
||||
}, [isTextarea, loading]);
|
||||
|
||||
// Auto‑resize
|
||||
useEffect(() => {
|
||||
if (isTextarea && inputRef.current) {
|
||||
inputRef.current.style.height = "auto";
|
||||
@@ -38,7 +54,6 @@ export default function ChatInput({ onSend, onStop, loading }) {
|
||||
}
|
||||
}, [inputValue, isTextarea]);
|
||||
|
||||
// Gestione timer e countdown
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
setTimeLeft(Math.floor(maxExecutionTime / 1000));
|
||||
@@ -77,7 +92,7 @@ export default function ChatInput({ onSend, onStop, loading }) {
|
||||
|
||||
const handleSend = () => {
|
||||
if (inputValue.trim()) {
|
||||
onSend(inputValue);
|
||||
onSend(inputValue, selectedModel);
|
||||
setInputValue("");
|
||||
}
|
||||
};
|
||||
@@ -113,36 +128,55 @@ export default function ChatInput({ onSend, onStop, loading }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chat-input-container p-2 border-top d-flex align-items-center">
|
||||
{loading ? (
|
||||
<div className="flex-grow-1 me-2 p-2 bg-light rounded small text-muted">
|
||||
💬 Il modello sta processando… (tempo trascorso: {elapsedTime}s)
|
||||
</div>
|
||||
) : isTextarea ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
className="form-control me-2"
|
||||
placeholder="Scrivi un messaggio..."
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
rows={3}
|
||||
style={{ resize: "none", overflow: "hidden" }}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
className="form-control me-2"
|
||||
placeholder="Scrivi un messaggio..."
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
)}
|
||||
<div className="chat-input-container p-2 border-top">
|
||||
<div className="input-group">
|
||||
<select
|
||||
className="form-select"
|
||||
value={selectedModel}
|
||||
onChange={(e) => setSelectedModel(e.target.value)}
|
||||
style={{ maxWidth: "20%" }}
|
||||
>
|
||||
<option value="">Seleziona modello</option>
|
||||
{modelList.map((model) => (
|
||||
<option key={model} value={model}>
|
||||
{model}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
{loading ? (
|
||||
<div className="d-flex align-items-center">
|
||||
{isTextarea ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
className="form-control"
|
||||
placeholder="Scrivi un messaggio..."
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
rows={3}
|
||||
style={{ resize: "none", overflow: "hidden" }}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Scrivi un messaggio..."
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
)}
|
||||
|
||||
<button className="btn btn-primary" onClick={handleSend}>
|
||||
➤ Invia
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{loading && (
|
||||
<div className="mt-2 d-flex align-items-center">
|
||||
<div className="me-2 small text-muted">
|
||||
💬 Il modello sta processando… ({elapsedTime}s)
|
||||
</div>
|
||||
<button className="btn btn-danger btn-sm me-2" onClick={onStop}>
|
||||
⏹ Stop
|
||||
</button>
|
||||
@@ -150,17 +184,11 @@ export default function ChatInput({ onSend, onStop, loading }) {
|
||||
+1 min
|
||||
</button>
|
||||
{timeLeft !== null && (
|
||||
<span
|
||||
className={`small fw-bold ${timeLeft <= 10 ? "text-danger" : "text-muted"}`}
|
||||
>
|
||||
<span className={`small fw-bold ${timeLeft <= 10 ? "text-danger" : "text-muted"}`}>
|
||||
{timeLeft}s
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<button className="btn btn-primary btn-sm" onClick={handleSend}>
|
||||
➤ Invia
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ export default function ChatLayout({
|
||||
onSelectSession,
|
||||
userId,
|
||||
sessionId,
|
||||
sessionModelName,
|
||||
sessionName
|
||||
}) {
|
||||
const [showSessionsPanel, setShowSessionsPanel] = useState(false);
|
||||
@@ -70,9 +71,10 @@ export default function ChatLayout({
|
||||
{/* INPUT SEMPRE IN BASSO */}
|
||||
{sessionId ? (
|
||||
<ChatInput
|
||||
onSend={onSend}
|
||||
onSend={(message, modelName) => onSend(message, modelName)}
|
||||
onStop={onStop}
|
||||
loading={loading}
|
||||
sessionModelName={sessionModelName || ""}
|
||||
/>
|
||||
) : (
|
||||
<NoSessionBox onCreateSession={onCreateSession} />
|
||||
|
||||
Generated
+280
@@ -5,6 +5,7 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"axios": "^1.11.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react-syntax-highlighter": "^15.6.6"
|
||||
}
|
||||
@@ -33,6 +34,36 @@
|
||||
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/character-entities": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
|
||||
@@ -63,6 +94,18 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/comma-separated-tokens": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
|
||||
@@ -73,6 +116,74 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fault": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
|
||||
@@ -86,6 +197,42 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/format": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
|
||||
@@ -94,6 +241,103 @@
|
||||
"node": ">=0.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-parse-selector": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
|
||||
@@ -194,6 +438,36 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-entities": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
|
||||
@@ -233,6 +507,12 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"axios": "^1.11.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react-syntax-highlighter": "^15.6.6"
|
||||
}
|
||||
|
||||
+12
-25
@@ -2,7 +2,7 @@
|
||||
#### Streamlit Streaming using LM Studio as OpenAI Standin
|
||||
#### run with `streamlit run app.py`
|
||||
|
||||
# !pip install pypdf langchain langchain-core langchain-openai
|
||||
# !pip install pypdf langchain langchain_openai
|
||||
|
||||
import streamlit as st
|
||||
from langchain_core.messages import AIMessage, HumanMessage
|
||||
@@ -12,9 +12,10 @@ from langchain_core.prompts import ChatPromptTemplate
|
||||
|
||||
# app config
|
||||
st.set_page_config(page_title="Egalware Chatbot", page_icon="🤖")
|
||||
st.title("Egalware's Live Chatbot")
|
||||
st.title("Egalware's Chatbot")
|
||||
|
||||
def get_response(user_query, chat_history):
|
||||
|
||||
template = """
|
||||
You are a helpful assistant. Answer the following questions considering the history of the conversation:
|
||||
|
||||
@@ -22,18 +23,14 @@ def get_response(user_query, chat_history):
|
||||
|
||||
User question: {user_question}
|
||||
"""
|
||||
|
||||
prompt = ChatPromptTemplate.from_template(template)
|
||||
|
||||
# Using LM Studio Local Inference Server
|
||||
llm = ChatOpenAI(
|
||||
base_url="http://10.74.83.100:1234/v1",
|
||||
api_key="lm-studio",
|
||||
model="qwen/qwen3-4b-2507"
|
||||
)
|
||||
llm = ChatOpenAI(base_url="http://10.74.83.100:1234/v1",api_key="lm-studio", model="qwen/qwen3-4b-2507")
|
||||
|
||||
chain = prompt | llm | StrOutputParser()
|
||||
|
||||
# Return a generator for streaming
|
||||
|
||||
return chain.stream({
|
||||
"chat_history": chat_history,
|
||||
"user_question": user_query,
|
||||
@@ -42,12 +39,11 @@ def get_response(user_query, chat_history):
|
||||
# session state
|
||||
if "chat_history" not in st.session_state:
|
||||
st.session_state.chat_history = [
|
||||
AIMessage(content="Hello, I am EgalWare's Live & Stateless ChatBot. "
|
||||
"How can I help you? (puoi fare domande in italiano, "
|
||||
"ma in inglese funziona meglio...)"),
|
||||
AIMessage(content="Hello, I am EgalWare's current ChatBot. How can I help you? (puoi fare domande in italiano, ma in inglese funziona meglio...)"),
|
||||
]
|
||||
|
||||
# conversation history display
|
||||
|
||||
# conversation
|
||||
for message in st.session_state.chat_history:
|
||||
if isinstance(message, AIMessage):
|
||||
with st.chat_message("AI"):
|
||||
@@ -58,22 +54,13 @@ for message in st.session_state.chat_history:
|
||||
|
||||
# user input
|
||||
user_query = st.chat_input("Type your message here...")
|
||||
if user_query:
|
||||
# store human message
|
||||
if user_query is not None and user_query != "":
|
||||
st.session_state.chat_history.append(HumanMessage(content=user_query))
|
||||
|
||||
with st.chat_message("Human"):
|
||||
st.markdown(user_query)
|
||||
|
||||
# stream AI response and capture it
|
||||
with st.chat_message("AI"):
|
||||
chunks = []
|
||||
for chunk in get_response(user_query, st.session_state.chat_history):
|
||||
st.write(chunk)
|
||||
chunks.append(chunk)
|
||||
full_response = "".join(chunks)
|
||||
|
||||
# store AI message with actual text
|
||||
st.session_state.chat_history.append(AIMessage(content=full_response))
|
||||
|
||||
response = st.write_stream(get_response(user_query, st.session_state.chat_history))
|
||||
|
||||
st.session_state.chat_history.append(AIMessage(content=response))
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
####
|
||||
#### Streamlit Streaming using LM Studio as OpenAI Standin
|
||||
#### run with `streamlit run app.py`
|
||||
|
||||
# !pip install pypdf langchain langchain-core langchain-openai
|
||||
|
||||
import streamlit as st
|
||||
from langchain_core.messages import AIMessage, HumanMessage
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
|
||||
# app config
|
||||
st.set_page_config(page_title="Egalware Chatbot", page_icon="🤖")
|
||||
st.title("Egalware's Live Chatbot")
|
||||
|
||||
def get_response(user_query, chat_history):
|
||||
template = """
|
||||
You are a helpful assistant. Answer the following questions considering the history of the conversation:
|
||||
|
||||
Chat history: {chat_history}
|
||||
|
||||
User question: {user_question}
|
||||
"""
|
||||
prompt = ChatPromptTemplate.from_template(template)
|
||||
|
||||
# Using LM Studio Local Inference Server
|
||||
llm = ChatOpenAI(
|
||||
base_url="http://10.74.83.100:1234/v1",
|
||||
api_key="lm-studio",
|
||||
model="qwen/qwen3-4b-2507"
|
||||
)
|
||||
|
||||
chain = prompt | llm | StrOutputParser()
|
||||
|
||||
# Return a generator for streaming
|
||||
return chain.stream({
|
||||
"chat_history": chat_history,
|
||||
"user_question": user_query,
|
||||
})
|
||||
|
||||
# session state
|
||||
if "chat_history" not in st.session_state:
|
||||
st.session_state.chat_history = [
|
||||
AIMessage(content="Hello, I am EgalWare's Live & Stateless ChatBot. "
|
||||
"How can I help you? (puoi fare domande in italiano, "
|
||||
"ma in inglese funziona meglio...)"),
|
||||
]
|
||||
|
||||
# conversation history display
|
||||
for message in st.session_state.chat_history:
|
||||
if isinstance(message, AIMessage):
|
||||
with st.chat_message("AI"):
|
||||
st.write(message.content)
|
||||
elif isinstance(message, HumanMessage):
|
||||
with st.chat_message("Human"):
|
||||
st.write(message.content)
|
||||
|
||||
# user input
|
||||
user_query = st.chat_input("Type your message here...")
|
||||
if user_query:
|
||||
# store human message
|
||||
st.session_state.chat_history.append(HumanMessage(content=user_query))
|
||||
|
||||
with st.chat_message("Human"):
|
||||
st.markdown(user_query)
|
||||
|
||||
# stream AI response and capture it
|
||||
with st.chat_message("AI"):
|
||||
chunks = []
|
||||
for chunk in get_response(user_query, st.session_state.chat_history):
|
||||
st.write(chunk)
|
||||
chunks.append(chunk)
|
||||
full_response = "".join(chunks)
|
||||
|
||||
# store AI message with actual text
|
||||
st.session_state.chat_history.append(AIMessage(content=full_response))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user