Instructions per chat (system prompt addition; UI dialog and Telegram /prompt)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
434aa84d40
commit
ff8ff585a2
14 files changed
+156
-4
No files matched your search
@@ -13,6 +13,7 @@ import { useReasoningMode } from "../prefs";
|
||||
import { Composer } from "./Composer";
|
||||
import { ShareDialog } from "./ShareDialog";
|
||||
import { RenameDialog } from "./RenameDialog";
|
||||
import { InstructionsDialog } from "./InstructionsDialog";
|
||||
import { RegenerateButton } from "./Regenerate";
|
||||
import { WorktreeChip } from "./WorktreeChip";
|
||||
import { QueueList } from "./QueueList";
|
||||
@@ -151,6 +152,7 @@ export function ChatView({ sessionId, onMenu, onClosePane }: { sessionId: string
|
||||
const isAdmin = useStore((s) => s.me?.role === "admin");
|
||||
const tabs = useChatTabs(sessionId, session?.cwd ?? "");
|
||||
const [sharing, setSharing] = useState(false);
|
||||
const [editingInstructions, setEditingInstructions] = useState(false);
|
||||
const models = useModels();
|
||||
const [loadError, setLoadError] = useState<string>();
|
||||
const [text, setText] = useState(() => takePrefill(sessionId) ?? "");
|
||||
@@ -307,6 +309,11 @@ export function ChatView({ sessionId, onMenu, onClosePane }: { sessionId: string
|
||||
<ShellsChip sessionId={sessionId} busy={busy} />
|
||||
{isAdmin && <BudgetChip />}
|
||||
{session.permission_mode === "bypass" && <span className="chip warn">{t("permissionMode.bypass")}</span>}
|
||||
{!!session.instructions && (
|
||||
<button className="chip" style={{ border: "none" }} onClick={() => !readOnly && setEditingInstructions(true)} title={session.instructions}>
|
||||
<Icon name="edit" /> {t("instructions.chip")}
|
||||
</button>
|
||||
)}
|
||||
{readOnly && <span className="chip">{t("chat.readOnly")}</span>}
|
||||
{!readOnly && (
|
||||
<>
|
||||
@@ -335,6 +342,16 @@ export function ChatView({ sessionId, onMenu, onClosePane }: { sessionId: string
|
||||
<Icon name="more" />
|
||||
</summary>
|
||||
<div className="menu-pop-body">
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
(e.currentTarget.closest("details") as HTMLDetailsElement).open = false;
|
||||
setEditingInstructions(true);
|
||||
}}
|
||||
>
|
||||
<Icon name="edit" /> {t("instructions.title")}
|
||||
</a>
|
||||
<a href={`/api/sessions/${sessionId}/export?format=md`} download>
|
||||
<Icon name="download" /> {t("chat.exportMd")}
|
||||
</a>
|
||||
@@ -527,6 +544,7 @@ export function ChatView({ sessionId, onMenu, onClosePane }: { sessionId: string
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{editingInstructions && <InstructionsDialog sessionId={sessionId} value={session.instructions ?? ""} onClose={() => setEditingInstructions(false)} />}
|
||||
{editingTitle && <RenameDialog sessionId={sessionId} title={session.title} onClose={() => setEditingTitle(false)} />}
|
||||
{sharing && <ShareDialog sessionId={sessionId} onClose={() => setSharing(false)} />}
|
||||
{rewinding && <RewindDialog sessionId={sessionId} messageId={rewinding.id} onDone={rewindDone} onClose={() => setRewinding(undefined)} />}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useState } from "react";
|
||||
import { patch } from "../api";
|
||||
import { useT } from "../i18n";
|
||||
|
||||
/** Instructions for one chat: added to the system prompt of every turn. */
|
||||
export function InstructionsDialog({ sessionId, value, onClose }: { sessionId: string; value: string; onClose: () => void }) {
|
||||
const t = useT();
|
||||
const [text, setText] = useState(value);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const save = async (next: string) => {
|
||||
setBusy(true);
|
||||
try {
|
||||
await patch(`/sessions/${sessionId}`, { instructions: next });
|
||||
onClose();
|
||||
} catch (e: any) {
|
||||
setError(e.message);
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="modal-backdrop" onClick={onClose}>
|
||||
<div className="modal instructions-modal" role="dialog" aria-label={t("instructions.title")} onClick={(e) => e.stopPropagation()}>
|
||||
<h3>{t("instructions.title")}</h3>
|
||||
<div className="hint" style={{ marginBottom: 8 }}>
|
||||
{t("instructions.hint")}
|
||||
</div>
|
||||
<textarea
|
||||
autoFocus
|
||||
rows={10}
|
||||
style={{ width: "100%" }}
|
||||
value={text}
|
||||
placeholder={t("instructions.placeholder")}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
onKeyDown={(e) => (e.ctrlKey || e.metaKey) && e.key === "Enter" && !busy && void save(text)}
|
||||
/>
|
||||
{error && <div className="notice err">{error}</div>}
|
||||
<div className="row" style={{ marginTop: 10, justifyContent: "flex-end", gap: 6 }}>
|
||||
{value && (
|
||||
<button className="danger" onClick={() => void save("")} disabled={busy}>
|
||||
{t("instructions.clear")}
|
||||
</button>
|
||||
)}
|
||||
<span className="spacer" />
|
||||
<button onClick={onClose}>{t("common.cancel")}</button>
|
||||
<button className="primary" onClick={() => void save(text)} disabled={busy || text.trim() === value.trim()}>
|
||||
{t("common.save")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user