feat: per-user assistant name and system prompt customization
Each user can rename the AI assistant and edit their own system prompt in Settings.
This commit is contained in:
@@ -7,9 +7,10 @@ import { AssistantPanel } from "./assistant-panel";
|
||||
type Props = {
|
||||
configured: boolean;
|
||||
userId: string;
|
||||
assistantName: string;
|
||||
};
|
||||
|
||||
export function AssistantBubble({ configured, userId }: Props) {
|
||||
export function AssistantBubble({ configured, userId, assistantName }: Props) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
@@ -18,31 +19,36 @@ export function AssistantBubble({ configured, userId }: Props) {
|
||||
<div
|
||||
className="assistant-bubble-panel"
|
||||
role="dialog"
|
||||
aria-label="Assistant"
|
||||
aria-label={assistantName}
|
||||
aria-modal="false"
|
||||
>
|
||||
<div className="assistant-bubble-header">
|
||||
<div>
|
||||
<div className="serif text-[15px] font-medium tracking-tight">Assistant</div>
|
||||
<div className="serif text-[15px] font-medium tracking-tight">{assistantName}</div>
|
||||
<div className="muted text-[11px]">Household helper</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="assistant-bubble-close"
|
||||
aria-label="Close assistant"
|
||||
aria-label={`Close ${assistantName}`}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
<AssistantPanel key={userId} configured={configured} userId={userId} />
|
||||
<AssistantPanel
|
||||
key={userId}
|
||||
configured={configured}
|
||||
userId={userId}
|
||||
assistantName={assistantName}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="assistant-bubble-trigger"
|
||||
aria-label={open ? "Close assistant" : "Open assistant"}
|
||||
aria-label={open ? `Close ${assistantName}` : `Open ${assistantName}`}
|
||||
aria-expanded={open}
|
||||
onClick={() => setOpen((current) => !current)}
|
||||
>
|
||||
|
||||
@@ -15,9 +15,10 @@ import {
|
||||
type Props = {
|
||||
configured: boolean;
|
||||
userId: string;
|
||||
assistantName: string;
|
||||
};
|
||||
|
||||
export function AssistantPanel({ configured, userId }: Props) {
|
||||
export function AssistantPanel({ configured, userId, assistantName }: Props) {
|
||||
const [messages, setMessages] = useState<AssistantChatMessage[]>(() => loadAssistantChat(userId));
|
||||
const [input, setInput] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -144,7 +145,7 @@ export function AssistantPanel({ configured, userId }: Props) {
|
||||
style={message.role === "assistant" ? { borderColor: "var(--hair)" } : undefined}
|
||||
>
|
||||
<div className="eyebrow mb-0.5 text-[10px]">
|
||||
{message.role === "user" ? "You" : "Assistant"}
|
||||
{message.role === "user" ? "You" : assistantName}
|
||||
</div>
|
||||
{message.content}
|
||||
</div>
|
||||
@@ -157,7 +158,7 @@ export function AssistantPanel({ configured, userId }: Props) {
|
||||
aria-live="polite"
|
||||
aria-busy="true"
|
||||
>
|
||||
<div className="eyebrow mb-1 text-[10px]">Assistant</div>
|
||||
<div className="eyebrow mb-1 text-[10px]">{assistantName}</div>
|
||||
<div className="flex items-center gap-2 text-[12.5px] text-muted-foreground">
|
||||
<Loader2 className="size-3.5 shrink-0 animate-spin" />
|
||||
<span>{activityLabel ?? "Working…"}</span>
|
||||
@@ -180,9 +181,9 @@ export function AssistantPanel({ configured, userId }: Props) {
|
||||
<Input
|
||||
value={input}
|
||||
onChange={(event) => setInput(event.target.value)}
|
||||
placeholder="Ask the assistant…"
|
||||
placeholder={`Ask ${assistantName}…`}
|
||||
disabled={isPending}
|
||||
aria-label="Assistant message"
|
||||
aria-label={`Message for ${assistantName}`}
|
||||
className="h-9"
|
||||
/>
|
||||
<Button type="submit" size="sm" disabled={isPending || !input.trim()} aria-label="Send">
|
||||
|
||||
@@ -26,6 +26,7 @@ export type AgentProgressHandler = (event: AgentProgressEvent) => void;
|
||||
export async function runAgentChat(options: {
|
||||
messages: ClientChatMessage[];
|
||||
request: Request;
|
||||
systemPrompt?: string;
|
||||
llm?: LlmClient;
|
||||
executeTool?: ToolExecutor;
|
||||
onProgress?: AgentProgressHandler;
|
||||
@@ -33,9 +34,10 @@ export async function runAgentChat(options: {
|
||||
const llm = options.llm ?? createLlmClient();
|
||||
const executeTool = options.executeTool ?? createApiToolExecutor(options.request);
|
||||
const onProgress = options.onProgress;
|
||||
const systemPrompt = options.systemPrompt ?? AGENT_SYSTEM_PROMPT;
|
||||
|
||||
const transcript: ChatMessage[] = [
|
||||
{ role: "system", content: AGENT_SYSTEM_PROMPT },
|
||||
{ role: "system", content: systemPrompt },
|
||||
...options.messages.map(
|
||||
(message): ChatMessage => ({
|
||||
role: message.role,
|
||||
|
||||
Reference in New Issue
Block a user