Add share target, prompt templates, command palette and session organisation
- PWA share target: shared text/links start a new chat or go into a
recent session (chooser dialog)
- Prompt templates (personal + global for admins) with {{placeholders}}
that are asked for on insert; managed under Settings → Templates
- Ctrl/Cmd+K command palette; Alt+N new session, Alt+Up/Down switch
sessions, Ctrl+, settings
- Sessions can be pinned, archived and filed into folders; the list can
be grouped by date or by project, groups are collapsible
- Fix: the "What's new" dialog was never shown to signed-in users
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b5de531d2e
commit
428e2df40a
25 files changed
+1106
-33
No files matched your search
@@ -0,0 +1,79 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { confirmDialog, toast } from "../../dialogs";
|
||||
import { useT } from "../../i18n";
|
||||
import { loadTemplates, placeholders, saveTemplates, type Template, type TemplateLists } from "../../templates";
|
||||
import { Icon } from "../Icon";
|
||||
|
||||
function List({ scope, initial }: { scope: "personal" | "global"; initial: Template[] }) {
|
||||
const t = useT();
|
||||
const [list, setList] = useState<Template[]>(initial);
|
||||
const [dirty, setDirty] = useState(false);
|
||||
const set = (i: number, patch: Partial<Template>) => {
|
||||
setList((l) => l.map((x, j) => (j === i ? { ...x, ...patch } : x)));
|
||||
setDirty(true);
|
||||
};
|
||||
const save = () =>
|
||||
saveTemplates(scope, list.filter((x) => x.name.trim() || x.text.trim()))
|
||||
.then((l) => (setList(l), setDirty(false), toast(t("settings.saved"), "success")))
|
||||
.catch((e) => toast(e.message));
|
||||
const remove = async (i: number) => {
|
||||
if (!(await confirmDialog({ message: t("templates.deleteConfirm", { name: list[i].name || "?" }), danger: true, confirmLabel: t("common.delete") }))) return;
|
||||
setList((l) => l.filter((_, j) => j !== i));
|
||||
setDirty(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="panel">
|
||||
<h3>
|
||||
<Icon name={scope === "global" ? "users" : "user"} /> {t(scope === "global" ? "templates.globalTitle" : "templates.personalTitle")}
|
||||
</h3>
|
||||
<div className="hint" style={{ marginBottom: 10 }}>
|
||||
{t(scope === "global" ? "templates.globalHint" : "templates.personalHint")}
|
||||
</div>
|
||||
{list.map((x, i) => (
|
||||
<div key={x.id || `new${i}`} className="tpl-edit">
|
||||
<div className="row">
|
||||
<input className="grow" value={x.name} placeholder={t("templates.name")} onChange={(e) => set(i, { name: e.target.value })} />
|
||||
<button className="ghost small" onClick={() => remove(i)} aria-label={t("common.delete")}>
|
||||
<Icon name="trash" />
|
||||
</button>
|
||||
</div>
|
||||
<textarea rows={4} value={x.text} placeholder={t("templates.textPlaceholder")} onChange={(e) => set(i, { text: e.target.value })} />
|
||||
{placeholders(x.text).length > 0 && (
|
||||
<div className="tpl-ph">
|
||||
{placeholders(x.text).map((p) => (
|
||||
<span key={p.name} className="chip">
|
||||
{p.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div className="row" style={{ marginTop: 8 }}>
|
||||
<button onClick={() => (setList([...list, { id: "", name: "", text: "" }]), setDirty(true))}>
|
||||
<Icon name="plus" /> {t("templates.add")}
|
||||
</button>
|
||||
<span className="grow" />
|
||||
<button className="primary" disabled={!dirty} onClick={save}>
|
||||
{t("common.save")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Prompt templates: personal ones and (admins) the ones every user sees. */
|
||||
export function Templates() {
|
||||
const [lists, setLists] = useState<TemplateLists>();
|
||||
useEffect(() => {
|
||||
loadTemplates().then(setLists).catch((e) => toast(e.message));
|
||||
}, []);
|
||||
if (!lists) return null;
|
||||
return (
|
||||
<>
|
||||
<List scope="personal" initial={lists.personal} />
|
||||
{lists.can_edit_global && <List scope="global" initial={lists.global} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user