Making UI progress; seeding lkml RAG

This commit is contained in:
James Ketr 2025-03-31 11:45:54 -07:00
parent b25498f6a0
commit 1c56814a92
4 changed files with 161 additions and 33 deletions

14
lkml/init.sh Normal file
View File

@ -0,0 +1,14 @@
#!/bin/bash
fail() {
echo "FAIL: ${*}" >&2
exit 1
}
for i in {8..16}; do
if [[ -d "${i}" ]]; then
echo "Skipping lkml/git/$i -- already exists"
else
cmd="git clone https://erol.kernel.org/lkml/git/$i"
${cmd} || fail "cmd"
fi
done

View File

@ -10,22 +10,13 @@ div {
flex-direction: column;
}
.Container {
display: flex;
flex-direction: row;
overflow: auto;
margin: 0 auto;
padding: 1rem;
border: 1px solid green;
max-width: 80%;
height: 100vh;
}
.ChatBox {
display: flex;
flex-direction: column;
flex-grow: 1;
border: 1px solid red;
max-width: 800px;
margin: 0 auto;
}
.Controls {

View File

@ -2,9 +2,8 @@ import React, { useState, useEffect, useRef, useCallback } from 'react';
import FormGroup from '@mui/material/FormGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import Switch from '@mui/material/Switch';
import Card from '@mui/material/Card';
import CardHeader from '@mui/material/CardHeader';
import CardContent from '@mui/material/CardContent';
import Snackbar, { SnackbarCloseReason } from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
import TextField from '@mui/material/TextField';
import Accordion from '@mui/material/Accordion';
import AccordionActions from '@mui/material/AccordionActions';
@ -13,7 +12,13 @@ import AccordionDetails from '@mui/material/AccordionDetails';
import Typography from '@mui/material/Typography';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import Button from '@mui/material/Button';
import Container from '@mui/material/Container';
import AppBar from '@mui/material/AppBar';
import Drawer from '@mui/material/Drawer';
import Toolbar from '@mui/material/Toolbar';
import MenuIcon from '@mui/icons-material/Menu';
import IconButton from '@mui/material/IconButton';
import Box from '@mui/material/Box';
import CssBaseline from '@mui/material/CssBaseline';
import PropagateLoader from "react-spinners/PropagateLoader";
import Markdown from 'react-markdown';
@ -45,12 +50,15 @@ type Tool = {
enabled: boolean
};
type SeverityType = 'error' | 'info' | 'success' | 'warning' | undefined;
interface ControlsParams {
sessionId: string,
connectionBase: string
connectionBase: string,
setSnack: (snackMessage: string, snackSeverity?: SeverityType) => void
};
const Controls = ({ sessionId, connectionBase }: ControlsParams) => {
const Controls = ({ sessionId, connectionBase, setSnack }: ControlsParams) => {
const [systemPrompt, setSystemPrompt] = useState<string>("");
const [editSystemPrompt, setEditSystemPrompt] = useState<string>(systemPrompt);
@ -68,7 +76,6 @@ const Controls = ({ sessionId, connectionBase }: ControlsParams) => {
},
});
const data = await response.json();
console.log(data);
setSystemPrompt(data["system-prompt"]);
setEditSystemPrompt(data["system-prompt"]);
}
@ -119,9 +126,11 @@ const Controls = ({ sessionId, connectionBase }: ControlsParams) => {
console.log(data);
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");
}
};
@ -142,6 +151,7 @@ const Controls = ({ sessionId, connectionBase }: ControlsParams) => {
setEditSystemPrompt(systemPrompt)
} catch (error) {
console.error('Fetch error:', error);
setSnack("Unable to fetch system prompt", "error");
}
};
@ -227,6 +237,19 @@ const App = () => {
const [processing, setProcessing] = useState(false);
const [sessionId, setSessionId] = useState<string | undefined>(undefined);
const [loc,] = useState<Location>(window.location)
const [mobileOpen, setMobileOpen] = useState(false);
const [isClosing, setIsClosing] = useState(false);
const [snackOpen, setSnackOpen] = useState(false);
const [snackMessage, setSnackMessage] = useState("");
const [snackSeverity, setSnackSeverity] = useState<SeverityType>("success");
useEffect(() => {
if (snackMessage === "") {
setSnackOpen(false);
} else {
setSnackOpen(true);
}
}, [snackMessage, setSnackOpen]);
// Scroll to bottom of conversation when conversation updates
useEffect(() => {
@ -269,6 +292,32 @@ const App = () => {
}, [setSessionId, loc]);
const setSnack = (message: string, severity: SeverityType = "success") => {
setSnackMessage(message);
setSnackSeverity(severity);
}
const handleDrawerClose = () => {
setIsClosing(true);
setMobileOpen(false);
};
const handleDrawerTransitionEnd = () => {
setIsClosing(false);
};
const handleDrawerToggle = () => {
if (!isClosing) {
setMobileOpen(!mobileOpen);
}
};
const drawer = (
<>
{sessionId !== undefined && <Controls setSnack={setSnack} sessionId={sessionId} connectionBase={getConnectionBase(loc)} />}
</>
);
const handleKeyPress = (event: any) => {
if (event.key === 'Enter') {
switch (event.target.id) {
@ -298,6 +347,8 @@ const App = () => {
const sendQuery = async () => {
if (!query.trim()) return;
setSnack("Query sent", "info");
const userMessage = [{ role: 'user', content: query }];
// Add user message to conversation
@ -431,6 +482,7 @@ const App = () => {
setProcessing(false);
} catch (error) {
console.error('Fetch error:', error);
setSnack("Unable to process query", "error");
setConversation(prev => [
...prev.filter(msg => !msg.isProcessing),
{ role: 'assistant', type: 'error', content: `Error: ${error}` }
@ -439,16 +491,80 @@ const App = () => {
}
};
return (
<Container className="Container">
const handleSnackClose = (
event: React.SyntheticEvent | Event,
reason?: SnackbarCloseReason,
) => {
setSnackMessage("");
<div className="ChatBox">
<div className="Conversation" ref={conversationRef}>
if (reason === 'clickaway') {
return;
}
setSnackOpen(false);
};
return (
<Box sx={{ display: 'flex', height: 1 }}>
<CssBaseline />
<AppBar
position="fixed"
sx={{
zIndex: (theme) => theme.zIndex.drawer + 1,
}}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
sx={{ mr: 2 }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
Ketr-Chat
</Typography>
</Toolbar>
</AppBar>
<Box
component="nav"
aria-label="mailbox folders"
>
{/* The implementation can be swapped with js to avoid SEO duplication of links. */}
<Drawer
container={window.document.body}
variant="temporary"
open={mobileOpen}
onTransitionEnd={handleDrawerTransitionEnd}
onClose={handleDrawerClose}
sx={{
display: 'block',
'& .MuiDrawer-paper': { boxSizing: 'border-box' },
}}
slotProps={{
root: {
keepMounted: true, // Better open performance on mobile.
},
}}
>
<Toolbar />
{drawer}
</Drawer>
</Box>
<Box
component="main"
sx={{ flexGrow: 1, p: 3, height: '100vh' }}
className="ChatBox">
<Toolbar />
<Box className="Conversation"
sx={{ flexGrow: 1, p: 3 }}
ref={conversationRef}>
{conversation.map((message, index) => {
const formattedContent = message.content
.split("\n")
.map((line) => line.replace(/^[^\s:]+:\s*/, ''))
.join("\n");
const formattedContent = message.content.trim();
return (
<div key={index} className={message.role === 'user' ? 'user-message' : 'assistant-message'}>
@ -491,9 +607,9 @@ const App = () => {
data-testid="loader"
/>
</div>
</div>
</Box>
<div className="Query" style={{ display: "flex", flexDirection: "row" }}>
<Box className="Query" style={{ display: "flex", flexDirection: "row" }}>
<TextField
variant="outlined"
disabled={processing}
@ -509,11 +625,19 @@ const App = () => {
<AccordionActions>
<Button variant="contained" onClick={sendQuery}>Send</Button>
</AccordionActions>
</div>
</div>
{sessionId !== undefined && <Controls sessionId={sessionId} connectionBase={getConnectionBase(loc)} />}
</Container>
</Box>
</Box>
<Snackbar open={snackOpen} autoHideDuration={snackSeverity === "success" ? 1500 : 6000} onClose={handleSnackClose}>
<Alert
onClose={handleSnackClose}
severity={snackSeverity}
variant="filled"
sx={{ width: '100%' }}
>
{snackMessage}
</Alert>
</Snackbar>
</Box >
);
};

View File

@ -373,7 +373,6 @@ class WebServer:
async def get_system_prompt(context_id: str):
context = self.upsert_context(context_id)
system_prompt = context["system"][0]["content"];
logging.info(f"returning system prompt as '{system_prompt}'")
return JSONResponse({ "system-prompt": system_prompt })
@self.app.post('/api/chat/{context_id}')