diff --git a/lkml/init.sh b/lkml/init.sh new file mode 100644 index 0000000..5ecca3d --- /dev/null +++ b/lkml/init.sh @@ -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 diff --git a/src/ketr-chat/src/App.css b/src/ketr-chat/src/App.css index ec4fc42..d35b652 100644 --- a/src/ketr-chat/src/App.css +++ b/src/ketr-chat/src/App.css @@ -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 { diff --git a/src/ketr-chat/src/App.tsx b/src/ketr-chat/src/App.tsx index aa7b6b9..d8a3994 100644 --- a/src/ketr-chat/src/App.tsx +++ b/src/ketr-chat/src/App.tsx @@ -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(""); const [editSystemPrompt, setEditSystemPrompt] = useState(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(undefined); const [loc,] = useState(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("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 && } + + ); + 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 ( - + const handleSnackClose = ( + event: React.SyntheticEvent | Event, + reason?: SnackbarCloseReason, + ) => { + setSnackMessage(""); -
-
+ if (reason === 'clickaway') { + return; + } + + setSnackOpen(false); + }; + + return ( + + + theme.zIndex.drawer + 1, + }} + > + + + + + + Ketr-Chat + + + + + {/* The implementation can be swapped with js to avoid SEO duplication of links. */} + + + {drawer} + + + + + + + {conversation.map((message, index) => { - const formattedContent = message.content - .split("\n") - .map((line) => line.replace(/^[^\s:]+:\s*/, '')) - .join("\n"); + const formattedContent = message.content.trim(); return (
@@ -491,9 +607,9 @@ const App = () => { data-testid="loader" />
-
+ -
+ { -
-
- {sessionId !== undefined && } - -
+ + + + + {snackMessage} + + + ); }; diff --git a/src/server.py b/src/server.py index 0167dbe..139598f 100644 --- a/src/server.py +++ b/src/server.py @@ -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}')