Making UI progress; seeding lkml RAG

This commit is contained in:
James Ketr 2025-03-31 14:42:16 -07:00
parent d815842f0e
commit dcb4d35d07
2 changed files with 234 additions and 229 deletions

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef, useCallback } from 'react';
import FormGroup from '@mui/material/FormGroup'; import FormGroup from '@mui/material/FormGroup';
import FormControlLabel from '@mui/material/FormControlLabel'; import FormControlLabel from '@mui/material/FormControlLabel';
import Switch from '@mui/material/Switch'; import Switch from '@mui/material/Switch';
@ -62,228 +62,38 @@ type Tool = {
type SeverityType = 'error' | 'info' | 'success' | 'warning' | undefined; type SeverityType = 'error' | 'info' | 'success' | 'warning' | undefined;
interface ControlsParams { interface ControlsParams {
sessionId: string, tools: Tool[],
connectionBase: string, rags: Tool[],
setSnack: (snackMessage: string, snackSeverity?: SeverityType) => void systemPrompt: string,
onClearHistory: () => void toggleTool: (tool: Tool) => void,
toggleRag: (tool: Tool) => void,
setRags: (rags: Tool[]) => void,
setSystemPrompt: (prompt: string) => void,
reset: (types: ("rags" | "tools" | "history" | "system-prompt")[]) => Promise<void>
}; };
const Controls = ({ sessionId, connectionBase, setSnack, onClearHistory }: ControlsParams) => { const Controls = ({ tools, rags, systemPrompt, toggleTool, toggleRag, setSystemPrompt, reset }: ControlsParams) => {
const [systemPrompt, setSystemPrompt] = useState<string>("");
const [editSystemPrompt, setEditSystemPrompt] = useState<string>(systemPrompt); const [editSystemPrompt, setEditSystemPrompt] = useState<string>(systemPrompt);
const [tools, setTools] = useState<Tool[]>([]);
const [rags, setRags] = useState<Tool[]>([]);
useEffect(() => { useEffect(() => {
if (systemPrompt !== "") { setEditSystemPrompt(systemPrompt);
return; }, [systemPrompt, setEditSystemPrompt]);
}
const fetchSystemPrompt = async () => {
// Make the fetch request with proper headers
const response = await fetch(connectionBase + `/api/system-prompt/${sessionId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
const data = await response.json();
const systemPrompt = data["system-prompt"].trim();
setSystemPrompt(systemPrompt);
setEditSystemPrompt(systemPrompt);
}
fetchSystemPrompt(); const toggle = async (type: string, index: number) => {
}, [sessionId, systemPrompt, setSystemPrompt, setEditSystemPrompt, connectionBase]);
useEffect(() => {
if (tools.length) {
return;
}
const fetchTools = async () => {
try {
// Make the fetch request with proper headers
const response = await fetch(connectionBase + `/api/tools/${sessionId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw Error();
}
const tools = await response.json();
setTools(tools);
} catch (error: any) {
setSnack("Unable to fetch tools", "error");
console.error(error);
}
}
fetchTools();
}, [sessionId, tools, setTools, setSnack, connectionBase]);
useEffect(() => {
if (rags.length) {
return;
}
const fetchRags = async () => {
try {
// Make the fetch request with proper headers
const response = await fetch(connectionBase + `/api/rags/${sessionId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw Error();
}
const rags = await response.json();
setRags(rags);
} catch (error: any) {
setSnack("Unable to fetch RAGs", "error");
console.error(error);
}
}
fetchRags();
}, [sessionId, rags, setRags, setSnack, connectionBase]);
const toggleTool = async (type: string, index: number) => {
switch (type) { switch (type) {
case "rag": case "rag":
setSnack("RAG backend not yet implemented", "warning"); toggleRag(rags[index])
// rags[index].enabled = !rags[index].enabled
// setRags([...rags])
break; break;
case "tool": case "tool":
const tool = tools[index]; toggleTool(tools[index]);
tool.enabled = !tool.enabled
try {
const response = await fetch(connectionBase + `/api/tools/${sessionId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ "tool": tool?.function?.name, "enabled": tool.enabled }),
});
const tools = await response.json();
setTools([...tools])
setSnack(`${tool?.function?.name} ${tool.enabled ? "enabled" : "disabled"}`);
} catch (error) {
console.error('Fetch error:', error);
setSnack(`${tool?.function?.name} ${tool.enabled ? "enabling" : "disabling"} failed.`, "error");
tool.enabled = !tool.enabled
}
};
};
const sendSystemPrompt = async () => {
if (!editSystemPrompt.trim()) {
setEditSystemPrompt(systemPrompt)
return;
}
try {
const response = await fetch(connectionBase + `/api/system-prompt/${sessionId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ "system-prompt": editSystemPrompt.trim() }),
});
const data = await response.json();
if (data["system-prompt"] !== systemPrompt) {
setSystemPrompt(data["system-prompt"].trim());
setSnack("System prompt updated", "success");
}
} catch (error) {
console.error('Fetch error:', error);
setSnack("System prompt update failed", "error");
} }
}; };
const onResetSystemPrompt = async () => {
try {
const response = await fetch(connectionBase + `/api/system-prompt/${sessionId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ "system-prompt": "" }),
});
const data = await response.json();
const systemPrompt = data["system-prompt"].trim();
setSystemPrompt(systemPrompt);
setEditSystemPrompt(systemPrompt)
} catch (error) {
console.error('Fetch error:', error);
setSnack("Unable to fetch system prompt", "error");
}
};
const onResetToDefaults = async (event: any) => {
try {
const response = await fetch(connectionBase + `/api/reset/${sessionId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ "reset": ["rag", "tools"] }),
});
if (response.ok) {
await response.json();
setSnack("Defaults restored", "success");
} else {
throw Error(`${{ status: response.status, message: response.statusText }}`);
}
} catch (error) {
console.error('Fetch error:', error);
setSnack("Unable to restore defaults", "error");
}
};
const onClearContext = async (event: any) => {
try {
const response = await fetch(connectionBase + `/api/reset/${sessionId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ "reset": ["history"] }),
});
if (response.ok) {
await response.json();
setSnack("Chat history cleared", "success");
onClearHistory();
} else {
throw Error(`${{ status: response.status, message: response.statusText }}`);
}
} catch (error) {
console.error('Fetch error:', error);
setSnack("Unable to clear chat history", "error");
}
};
const handleKeyPress = (event: any) => { const handleKeyPress = (event: any) => {
if (event.key === 'Enter' && event.ctrlKey) { if (event.key === 'Enter' && event.ctrlKey) {
switch (event.target.id) { switch (event.target.id) {
case 'SystemPromptInput': case 'SystemPromptInput':
sendSystemPrompt(); setSystemPrompt(editSystemPrompt);
break; break;
} }
} }
@ -314,8 +124,8 @@ const Controls = ({ sessionId, connectionBase, setSnack, onClearHistory }: Contr
id="SystemPromptInput" id="SystemPromptInput"
/> />
<div style={{ display: "flex", flexDirection: "row", gap: "8px", paddingTop: "8px" }}> <div style={{ display: "flex", flexDirection: "row", gap: "8px", paddingTop: "8px" }}>
<Button variant="contained" disabled={editSystemPrompt === systemPrompt} onClick={sendSystemPrompt}>Set</Button> <Button variant="contained" disabled={editSystemPrompt === systemPrompt} onClick={() => { setSystemPrompt(editSystemPrompt); }}>Set</Button>
<Button variant="outlined" onClick={onResetSystemPrompt} color="error">Reset</Button> <Button variant="outlined" onClick={() => { reset(["system-prompt"]); }} color="error">Reset</Button>
</div> </div>
</AccordionActions> </AccordionActions>
</Accordion> </Accordion>
@ -334,7 +144,7 @@ const Controls = ({ sessionId, connectionBase, setSnack, onClearHistory }: Contr
tools.map((tool, index) => tools.map((tool, index) =>
<Box key={index}> <Box key={index}>
<Divider /> <Divider />
<FormControlLabel control={<Switch checked={tool.enabled} />} onChange={() => toggleTool("tool", index)} label={tool?.function?.name} /> <FormControlLabel control={<Switch checked={tool.enabled} />} onChange={() => toggle("tool", index)} label={tool?.function?.name} />
<Typography>{tool?.function?.description}</Typography> <Typography>{tool?.function?.description}</Typography>
</Box> </Box>
) )
@ -356,15 +166,15 @@ const Controls = ({ sessionId, connectionBase, setSnack, onClearHistory }: Contr
rags.map((rag, index) => rags.map((rag, index) =>
<Box key={index}> <Box key={index}>
<Divider /> <Divider />
<FormControlLabel control={<Switch checked={rag.enabled} />} onChange={() => toggleTool("rag", index)} label={rag?.name} /> <FormControlLabel control={<Switch checked={rag.enabled} />} onChange={() => toggle("rag", index)} label={rag?.name} />
<Typography>{rag?.description}</Typography> <Typography>{rag?.description}</Typography>
</Box> </Box>
) )
}</FormGroup> }</FormGroup>
</AccordionActions> </AccordionActions>
</Accordion> </Accordion>
<Button onClick={onClearContext}>Clear Chat History</Button> <Button onClick={() => { reset(["history"]); }}>Clear Chat History</Button>
<Button onClick={onResetToDefaults}>Reset to defaults</Button> <Button onClick={() => { reset(["rags", "tools", "system-prompt"]) }}>Reset to defaults</Button>
</div>); </div>);
} }
@ -380,6 +190,10 @@ const App = () => {
const [snackOpen, setSnackOpen] = useState(false); const [snackOpen, setSnackOpen] = useState(false);
const [snackMessage, setSnackMessage] = useState(""); const [snackMessage, setSnackMessage] = useState("");
const [snackSeverity, setSnackSeverity] = useState<SeverityType>("success"); const [snackSeverity, setSnackSeverity] = useState<SeverityType>("success");
const [tools, setTools] = useState<Tool[]>([]);
const [rags, setRags] = useState<Tool[]>([]);
const [systemPrompt, setSystemPrompt] = useState<string>("");
const [serverSystemPrompt, setServerSystemPrompt] = useState<string>("");
// Scroll to bottom of conversation when conversation updates // Scroll to bottom of conversation when conversation updates
useEffect(() => { useEffect(() => {
@ -388,6 +202,7 @@ const App = () => {
} }
}, [conversation]); }, [conversation]);
// Set the initial chat history to "loading" or the welcome message if loaded.
useEffect(() => { useEffect(() => {
if (sessionId === undefined) { if (sessionId === undefined) {
setConversation([loadingMessage]); setConversation([loadingMessage]);
@ -396,6 +211,8 @@ const App = () => {
} }
}, [sessionId, setConversation]); }, [sessionId, setConversation]);
// Extract the sessionId from the URL if present, otherwise
// request a sessionId from the server.
useEffect(() => { useEffect(() => {
const url = new URL(loc.href); const url = new URL(loc.href);
const pathParts = url.pathname.split('/').filter(Boolean); const pathParts = url.pathname.split('/').filter(Boolean);
@ -422,12 +239,196 @@ const App = () => {
}, [setSessionId, loc]); }, [setSessionId, loc]);
const setSnack = (message: string, severity: SeverityType = "success") => { // If the systemPrompt has not been set, fetch it from the server
useEffect(() => {
if (serverSystemPrompt !== "" || sessionId === undefined) {
return;
}
const fetchSystemPrompt = async () => {
// Make the fetch request with proper headers
const response = await fetch(getConnectionBase(loc) + `/api/system-prompt/${sessionId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
const data = await response.json();
const serverSystemPrompt = data["system-prompt"].trim();
console.log("System prompt initialized to:", serverSystemPrompt);
setServerSystemPrompt(serverSystemPrompt);
setSystemPrompt(serverSystemPrompt);
}
fetchSystemPrompt();
}, [sessionId, serverSystemPrompt, setServerSystemPrompt, loc]);
// Set the snack pop-up and open it
const setSnack = useCallback((message: string, severity: SeverityType = "success") => {
setSnackMessage(message); setSnackMessage(message);
setSnackSeverity(severity); setSnackSeverity(severity);
setSnackOpen(true); setSnackOpen(true);
}, []);
// If the tools have not been set, fetch them from the server
useEffect(() => {
if (tools.length || sessionId === undefined) {
return;
}
const fetchTools = async () => {
try {
// Make the fetch request with proper headers
const response = await fetch(getConnectionBase(loc) + `/api/tools/${sessionId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw Error();
}
const tools = await response.json();
setTools(tools);
} catch (error: any) {
setSnack("Unable to fetch tools", "error");
console.error(error);
}
}
fetchTools();
}, [sessionId, tools, setTools, setSnack, loc]);
// If the RAGs have not been set, fetch them from the server
useEffect(() => {
if (rags.length || sessionId === undefined) {
return;
}
const fetchRags = async () => {
try {
// Make the fetch request with proper headers
const response = await fetch(getConnectionBase(loc) + `/api/rags/${sessionId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw Error();
}
const rags = await response.json();
setRags(rags);
} catch (error: any) {
setSnack("Unable to fetch RAGs", "error");
console.error(error);
}
}
fetchRags();
}, [sessionId, rags, setRags, setSnack, loc]);
const toggleRag = async (tool: Tool) => {
setSnack("RAG is not yet implemented", "warning");
} }
const toggleTool = async (tool: Tool) => {
tool.enabled = !tool.enabled
try {
const response = await fetch(getConnectionBase(loc) + `/api/tools/${sessionId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ "tool": tool?.function?.name, "enabled": tool.enabled }),
});
const tools = await response.json();
setTools([...tools])
setSnack(`${tool?.function?.name} ${tool.enabled ? "enabled" : "disabled"}`);
} catch (error) {
console.error('Fetch error:', error);
setSnack(`${tool?.function?.name} ${tool.enabled ? "enabling" : "disabling"} failed.`, "error");
tool.enabled = !tool.enabled
}
};
useEffect(() => {
if (systemPrompt === serverSystemPrompt || !systemPrompt.trim() || sessionId === undefined) {
return;
}
const sendSystemPrompt = async (prompt: string) => {
try {
const response = await fetch(getConnectionBase(loc) + `/api/system-prompt/${sessionId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ "system-prompt": prompt }),
});
const data = await response.json();
const newPrompt = data["system-prompt"];
if (newPrompt !== serverSystemPrompt) {
setServerSystemPrompt(newPrompt);
setSystemPrompt(newPrompt)
setSnack("System prompt updated", "success");
}
} catch (error) {
console.error('Fetch error:', error);
setSnack("System prompt update failed", "error");
}
};
sendSystemPrompt(systemPrompt);
}, [systemPrompt, setServerSystemPrompt, serverSystemPrompt, loc, sessionId, setSnack]);
const reset = async (types: ("rags" | "tools" | "history" | "system-prompt")[]) => {
try {
const response = await fetch(getConnectionBase(loc) + `/api/reset/${sessionId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ "reset": types }),
});
if (response.ok) {
const data = await response.json();
if (data.error) {
throw Error()
}
for (const [key, value] of Object.entries(data)) {
switch (key) {
case "rags":
setRags(value as Tool[]);
break;
case "tools":
setTools(value as Tool[]);
break;
case "system-prompt":
setServerSystemPrompt((value as any)["system-prompt"].trim());
setSystemPrompt((value as any)["system-prompt"].trim());
break;
case "history":
setConversation([welcomeMessage]);
break;
}
}
setSnack("Update successful.", "success");
} else {
throw Error(`${{ status: response.status, message: response.statusText }}`);
}
} catch (error) {
console.error('Fetch error:', error);
setSnack("Unable to restore defaults", "error");
}
};
const handleDrawerClose = () => { const handleDrawerClose = () => {
setIsClosing(true); setIsClosing(true);
setMobileOpen(false); setMobileOpen(false);
@ -443,13 +444,9 @@ const App = () => {
} }
}; };
const onClearHistory = () => {
setConversation([welcomeMessage]);
};
const drawer = ( const drawer = (
<> <>
{sessionId !== undefined && <Controls onClearHistory={onClearHistory} setSnack={setSnack} sessionId={sessionId} connectionBase={getConnectionBase(loc)} />} {sessionId !== undefined && <Controls {...{ tools, rags, reset, systemPrompt, toggleTool, toggleRag, setRags, setSystemPrompt }} />}
</> </>
); );
@ -479,6 +476,10 @@ const App = () => {
type MessageList = Message[]; type MessageList = Message[];
const onNew = async () => {
reset(["rags", "tools", "history", "system-prompt"]);
}
const sendQuery = async () => { const sendQuery = async () => {
if (!query.trim()) return; if (!query.trim()) return;
@ -743,10 +744,11 @@ const App = () => {
/> />
<AccordionActions> <AccordionActions>
<Button variant="contained" onClick={sendQuery}>Send</Button> <Button variant="contained" onClick={sendQuery}>Send</Button>
<Button variant="outlined" onClick={onNew}>New</Button>
</AccordionActions> </AccordionActions>
</Box> </Box>
</Box> </Box>
<Snackbar open={snackOpen} autoHideDuration={snackSeverity === "success" ? 1500 : 6000} onClose={handleSnackClose}> <Snackbar open={snackOpen} autoHideDuration={(snackSeverity === "success" || snackSeverity === "info") ? 1500 : 6000} onClose={handleSnackClose}>
<Alert <Alert
onClose={handleSnackClose} onClose={handleSnackClose}
severity={snackSeverity} severity={snackSeverity}

View File

@ -87,7 +87,7 @@ When you receive a response from summarize_site, you must:
1. Review the entire content returned by the second LLM 1. Review the entire content returned by the second LLM
2. Provide the URL used to obtain the information. 2. Provide the URL used to obtain the information.
3. Incorporate the information into your response as appropriate 3. Incorporate the information into your response as appropriate
""" """.strip()
tool_log = [] tool_log = []
command_log = [] command_log = []
@ -376,25 +376,28 @@ class WebServer:
context = self.upsert_context(context_id) context = self.upsert_context(context_id)
data = await request.json() data = await request.json()
try: try:
response = {}
for reset in data["reset"]: for reset in data["reset"]:
match reset: match reset:
case "system-prompt": case "system-prompt":
context["system"] = [{"role": "system", "content": system_message}] context["system"] = [{"role": "system", "content": system_message}]
return JSONResponse(context["system"]) response["system-prompt"] = { "system-prompt": system_message }
case "rag": case "rag":
context["rag"] = rags.copy() context["rag"] = rags.copy()
return JSONResponse(context["rag"]) response["rags"] = context["rag"]
case "tools": case "tools":
context["tools"] = default_tools(tools) context["tools"] = default_tools(tools)
return JSONResponse(context["tools"]) response["tools"] = context["tools"]
case "history": case "history":
context["history"] = [] context["history"] = []
return JSONResponse(context["history"]) response["history"] = context["history"]
if not response:
return JSONResponse({ "error": "Usage: { reset: rag|tools|history|system-prompt}"}), 405 return JSONResponse({ "error": "Usage: { reset: rag|tools|history|system-prompt}"})
else:
return JSONResponse(response);
except: except:
return JSONResponse({ "error": "Usage: { reset: rag|tools|history|system-prompt}"}), 405 return JSONResponse({ "error": "Usage: { reset: rag|tools|history|system-prompt}"})
@self.app.put('/api/system-prompt/{context_id}') @self.app.put('/api/system-prompt/{context_id}')
@ -406,7 +409,7 @@ class WebServer:
data = await request.json() data = await request.json()
system_prompt = data["system-prompt"].strip() system_prompt = data["system-prompt"].strip()
if not system_prompt: if not system_prompt:
return JSONResponse({ "status": "error", "message": "System prompt can not be empty." }), 405 return JSONResponse({ "status": "error", "message": "System prompt can not be empty." })
context["system"] = [{"role": "system", "content": system_prompt}] context["system"] = [{"role": "system", "content": system_prompt}]
return JSONResponse({ "system-prompt": system_prompt }) return JSONResponse({ "system-prompt": system_prompt })