optimize: session recoginze file
This commit is contained in:
+20
-1
@@ -1,10 +1,11 @@
|
|||||||
from typing import List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from app.api import upload, llm, skills, users
|
from app.api import upload, llm, skills, users
|
||||||
from app.connectors.postgres import postgres_connector
|
from app.connectors.postgres import postgres_connector
|
||||||
@@ -78,6 +79,10 @@ class SessionAliasUpdateRequest(BaseModel):
|
|||||||
archived: Optional[bool] = None
|
archived: Optional[bool] = None
|
||||||
|
|
||||||
|
|
||||||
|
class SessionFileContextUpdateRequest(BaseModel):
|
||||||
|
active_data_file: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
|
|
||||||
def _build_sql_chart_text(nl2sql_result: NL2SQLResponse) -> str:
|
def _build_sql_chart_text(nl2sql_result: NL2SQLResponse) -> str:
|
||||||
chart = nl2sql_result.chart
|
chart = nl2sql_result.chart
|
||||||
can_visualize = bool(chart and chart.can_visualize and chart.chart_spec)
|
can_visualize = bool(chart and chart.can_visualize and chart.chart_spec)
|
||||||
@@ -245,6 +250,20 @@ def update_session(session_id: str, payload: SessionAliasUpdateRequest):
|
|||||||
)
|
)
|
||||||
return {"status": "success", **updated}
|
return {"status": "success", **updated}
|
||||||
|
|
||||||
|
|
||||||
|
@app.put("/nanobot/sessions/{session_id}/context-file")
|
||||||
|
def update_session_context_file(session_id: str, payload: SessionFileContextUpdateRequest):
|
||||||
|
if not nanobot_service.agent:
|
||||||
|
raise HTTPException(status_code=400, detail="Nanobot not running")
|
||||||
|
session = nanobot_service.agent.sessions.get_or_create(session_id)
|
||||||
|
if payload.active_data_file is None:
|
||||||
|
session.metadata.pop("active_data_file", None)
|
||||||
|
else:
|
||||||
|
session.metadata["active_data_file"] = payload.active_data_file
|
||||||
|
session.updated_at = datetime.now()
|
||||||
|
nanobot_service.agent.sessions.save(session)
|
||||||
|
return {"status": "success", "metadata": session.metadata}
|
||||||
|
|
||||||
@app.post("/api/v1/agent/nl2sql", response_model=NL2SQLResponse)
|
@app.post("/api/v1/agent/nl2sql", response_model=NL2SQLResponse)
|
||||||
async def run_nl2sql(request: NL2SQLRequest):
|
async def run_nl2sql(request: NL2SQLRequest):
|
||||||
result = await process_nl2sql(request)
|
result = await process_nl2sql(request)
|
||||||
|
|||||||
@@ -39,8 +39,19 @@ interface ModelConfig {
|
|||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DataFileContext {
|
||||||
|
filename: string;
|
||||||
|
url: string;
|
||||||
|
columns?: string[];
|
||||||
|
summary?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface SessionData {
|
interface SessionData {
|
||||||
key: string;
|
key: string;
|
||||||
|
metadata?: {
|
||||||
|
active_data_file?: DataFileContext | null;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
messages: Array<{
|
messages: Array<{
|
||||||
role: string;
|
role: string;
|
||||||
content: string;
|
content: string;
|
||||||
@@ -67,8 +78,8 @@ export function ChatInterface() {
|
|||||||
const activeSessionKey = queryParams.get("session") || "api:default";
|
const activeSessionKey = queryParams.get("session") || "api:default";
|
||||||
|
|
||||||
// File upload state
|
// File upload state
|
||||||
const [attachedFile, setAttachedFile] = useState<{ filename: string; url: string; columns?: string[]; summary?: string } | null>(null);
|
const [attachedFile, setAttachedFile] = useState<DataFileContext | null>(null);
|
||||||
const [activeDataFile, setActiveDataFile] = useState<{ filename: string; url: string; columns?: string[]; summary?: string } | null>(null);
|
const [activeDataFile, setActiveDataFile] = useState<DataFileContext | null>(null);
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
@@ -76,6 +87,16 @@ export function ChatInterface() {
|
|||||||
fetchModels();
|
fetchModels();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const syncSessionFileContext = async (file: DataFileContext | null) => {
|
||||||
|
try {
|
||||||
|
await api.put(`/nanobot/sessions/${encodeURIComponent(activeSessionKey)}/context-file`, {
|
||||||
|
active_data_file: file,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to sync session file context", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchSessionData = async () => {
|
const fetchSessionData = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -92,9 +113,19 @@ export function ChatInterface() {
|
|||||||
} else {
|
} else {
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
}
|
}
|
||||||
|
const restoredFile = data.metadata?.active_data_file || null;
|
||||||
|
setActiveDataFile(restoredFile);
|
||||||
|
setAttachedFile(null);
|
||||||
|
if (restoredFile) {
|
||||||
|
setSelectedDataSource("upload-main");
|
||||||
|
} else if (selectedDataSource.startsWith("upload")) {
|
||||||
|
setSelectedDataSource("postgres-main");
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to fetch session messages", e);
|
console.error("Failed to fetch session messages", e);
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
|
setActiveDataFile(null);
|
||||||
|
setAttachedFile(null);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -179,6 +210,7 @@ export function ChatInterface() {
|
|||||||
setAttachedFile(uploadedFile);
|
setAttachedFile(uploadedFile);
|
||||||
setActiveDataFile(uploadedFile);
|
setActiveDataFile(uploadedFile);
|
||||||
setSelectedDataSource("upload-main");
|
setSelectedDataSource("upload-main");
|
||||||
|
await syncSessionFileContext(uploadedFile);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("File upload error:", error);
|
console.error("File upload error:", error);
|
||||||
// Could show a toast notification here
|
// Could show a toast notification here
|
||||||
@@ -477,15 +509,25 @@ export function ChatInterface() {
|
|||||||
{/* Input Area */}
|
{/* Input Area */}
|
||||||
<div className="w-full max-w-3xl relative">
|
<div className="w-full max-w-3xl relative">
|
||||||
<div className="bg-white rounded-3xl shadow-[0_8px_30px_rgb(0,0,0,0.06)] border border-zinc-100 p-4 transition-shadow hover:shadow-[0_8px_40px_rgb(0,0,0,0.08)]">
|
<div className="bg-white rounded-3xl shadow-[0_8px_30px_rgb(0,0,0,0.06)] border border-zinc-100 p-4 transition-shadow hover:shadow-[0_8px_40px_rgb(0,0,0,0.08)]">
|
||||||
{attachedFile && (
|
{activeDataFile && (
|
||||||
<div className="mx-2 mb-3 p-2.5 bg-blue-50/50 border border-blue-100/50 rounded-xl flex items-center justify-between">
|
<div className="mx-2 mb-3 p-2.5 bg-blue-50/50 border border-blue-100/50 rounded-xl flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2.5 text-sm text-blue-900">
|
<div className="flex items-center gap-2.5 text-sm text-blue-900">
|
||||||
<div className="p-1.5 bg-blue-100 rounded-md">
|
<div className="p-1.5 bg-blue-100 rounded-md">
|
||||||
<FileIcon className="h-4 w-4 text-blue-600" />
|
<FileIcon className="h-4 w-4 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<span className="font-medium truncate max-w-[300px]">{attachedFile.filename}</span>
|
<span className="font-medium truncate max-w-[300px]">{activeDataFile.filename}</span>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={() => setAttachedFile(null)} className="p-1 text-blue-400 hover:text-blue-600 hover:bg-blue-100/50 rounded-md transition-colors">
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
setAttachedFile(null);
|
||||||
|
setActiveDataFile(null);
|
||||||
|
if (selectedDataSource.startsWith("upload")) {
|
||||||
|
setSelectedDataSource("postgres-main");
|
||||||
|
}
|
||||||
|
await syncSessionFileContext(null);
|
||||||
|
}}
|
||||||
|
className="p-1 text-blue-400 hover:text-blue-600 hover:bg-blue-100/50 rounded-md transition-colors"
|
||||||
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -609,13 +651,23 @@ export function ChatInterface() {
|
|||||||
<div className="absolute bottom-6 left-0 right-0 px-4">
|
<div className="absolute bottom-6 left-0 right-0 px-4">
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<div className="bg-white rounded-2xl shadow-xl border border-zinc-200/60 p-2 flex flex-col gap-2 ring-1 ring-zinc-100">
|
<div className="bg-white rounded-2xl shadow-xl border border-zinc-200/60 p-2 flex flex-col gap-2 ring-1 ring-zinc-100">
|
||||||
{attachedFile && (
|
{activeDataFile && (
|
||||||
<div className="mx-2 mt-1 p-2 bg-blue-50/50 border border-blue-100/50 rounded-lg flex items-center justify-between">
|
<div className="mx-2 mt-1 p-2 bg-blue-50/50 border border-blue-100/50 rounded-lg flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2 text-xs text-blue-900">
|
<div className="flex items-center gap-2 text-xs text-blue-900">
|
||||||
<FileIcon className="h-3.5 w-3.5 text-blue-600" />
|
<FileIcon className="h-3.5 w-3.5 text-blue-600" />
|
||||||
<span className="font-medium truncate max-w-[200px]">{attachedFile.filename}</span>
|
<span className="font-medium truncate max-w-[200px]">{activeDataFile.filename}</span>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={() => setAttachedFile(null)} className="text-blue-400 hover:text-blue-600">
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
setAttachedFile(null);
|
||||||
|
setActiveDataFile(null);
|
||||||
|
if (selectedDataSource.startsWith("upload")) {
|
||||||
|
setSelectedDataSource("postgres-main");
|
||||||
|
}
|
||||||
|
await syncSessionFileContext(null);
|
||||||
|
}}
|
||||||
|
className="text-blue-400 hover:text-blue-600"
|
||||||
|
>
|
||||||
<X className="h-3.5 w-3.5" />
|
<X className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user