server : (webui) revamp Settings dialog, add Pyodide interpreter (#11759)
* redo Settings modal UI * add python code interpreter * fix auto scroll * build * fix overflow for long output lines * bring back sticky copy button * adapt layout on mobile view * fix multiple lines output and color scheme * handle python exception * better state management * add webworker * add headers * format code * speed up by loading pyodide on page load * (small tweak) add small animation to make it feels like claude
This commit is contained in:
parent
e6e6583199
commit
55ac8c7791
18 changed files with 873 additions and 272 deletions
|
@ -1,5 +1,11 @@
|
|||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { APIMessage, Conversation, Message, PendingMessage } from './types';
|
||||
import {
|
||||
APIMessage,
|
||||
CanvasData,
|
||||
Conversation,
|
||||
Message,
|
||||
PendingMessage,
|
||||
} from './types';
|
||||
import StorageUtils from './storage';
|
||||
import {
|
||||
filterThoughtFromMsgs,
|
||||
|
@ -10,6 +16,7 @@ import { BASE_URL, CONFIG_DEFAULT, isDev } from '../Config';
|
|||
import { matchPath, useLocation } from 'react-router';
|
||||
|
||||
interface AppContextValue {
|
||||
// conversations and messages
|
||||
viewingConversation: Conversation | null;
|
||||
pendingMessages: Record<Conversation['id'], PendingMessage>;
|
||||
isGenerating: (convId: string) => boolean;
|
||||
|
@ -26,8 +33,15 @@ interface AppContextValue {
|
|||
onChunk?: CallbackGeneratedChunk
|
||||
) => Promise<void>;
|
||||
|
||||
// canvas
|
||||
canvasData: CanvasData | null;
|
||||
setCanvasData: (data: CanvasData | null) => void;
|
||||
|
||||
// config
|
||||
config: typeof CONFIG_DEFAULT;
|
||||
saveConfig: (config: typeof CONFIG_DEFAULT) => void;
|
||||
showSettings: boolean;
|
||||
setShowSettings: (show: boolean) => void;
|
||||
}
|
||||
|
||||
// for now, this callback is only used for scrolling to the bottom of the chat
|
||||
|
@ -54,8 +68,13 @@ export const AppContextProvider = ({
|
|||
Record<Conversation['id'], AbortController>
|
||||
>({});
|
||||
const [config, setConfig] = useState(StorageUtils.getConfig());
|
||||
const [canvasData, setCanvasData] = useState<CanvasData | null>(null);
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
|
||||
// handle change when the convId from URL is changed
|
||||
useEffect(() => {
|
||||
// also reset the canvas data
|
||||
setCanvasData(null);
|
||||
const handleConversationChange = (changedConvId: string) => {
|
||||
if (changedConvId !== convId) return;
|
||||
setViewingConversation(StorageUtils.getOneConversation(convId));
|
||||
|
@ -292,8 +311,12 @@ export const AppContextProvider = ({
|
|||
sendMessage,
|
||||
stopGenerating,
|
||||
replaceMessageAndGenerate,
|
||||
canvasData,
|
||||
setCanvasData,
|
||||
config,
|
||||
saveConfig,
|
||||
showSettings,
|
||||
setShowSettings,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
38
examples/server/webui/src/utils/common.tsx
Normal file
38
examples/server/webui/src/utils/common.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
export const XCloseButton: React.ElementType<
|
||||
React.ClassAttributes<HTMLButtonElement> &
|
||||
React.HTMLAttributes<HTMLButtonElement>
|
||||
> = ({ className, ...props }) => (
|
||||
<button className={`btn btn-square btn-sm ${className ?? ''}`} {...props}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
|
||||
export const OpenInNewTab = ({
|
||||
href,
|
||||
children,
|
||||
}: {
|
||||
href: string;
|
||||
children: string;
|
||||
}) => (
|
||||
<a
|
||||
className="underline"
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
|
@ -85,3 +85,6 @@ export function classNames(classes: Record<string, boolean>): string {
|
|||
.map(([key, _]) => key)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
export const delay = (ms: number) =>
|
||||
new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
|
|
@ -23,3 +23,14 @@ export interface Conversation {
|
|||
export type PendingMessage = Omit<Message, 'content'> & {
|
||||
content: string | null;
|
||||
};
|
||||
|
||||
export enum CanvasType {
|
||||
PY_INTERPRETER,
|
||||
}
|
||||
|
||||
export interface CanvasPyInterpreter {
|
||||
type: CanvasType.PY_INTERPRETER;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export type CanvasData = CanvasPyInterpreter;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue