feat(settings): add quick messages management with drag-and-drop sorting
Adds a new "Quick Messages" settings page below Experts for managing reusable title/content snippets, backed by SQLite via SeaORM and exposed through both Tauri commands and the Axum web router. The list supports drag-to-reorder using the same motion/react Reorder pattern as the agent list, with translations provided across all 10 supported locales.
This commit is contained in:
5
src/app/settings/quick-messages/page.tsx
Normal file
5
src/app/settings/quick-messages/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { QuickMessagesSettings } from "@/components/settings/quick-messages-settings"
|
||||
|
||||
export default function SettingsQuickMessagesPage() {
|
||||
return <QuickMessagesSettings />
|
||||
}
|
||||
583
src/components/settings/quick-messages-settings.tsx
Normal file
583
src/components/settings/quick-messages-settings.tsx
Normal file
@@ -0,0 +1,583 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type PointerEvent,
|
||||
type ReactNode,
|
||||
} from "react"
|
||||
import { GripVertical, Loader2, Plus, Save, Trash2 } from "lucide-react"
|
||||
import { Reorder, useDragControls } from "motion/react"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/components/ui/resizable"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
quickMessagesCreate,
|
||||
quickMessagesDelete,
|
||||
quickMessagesList,
|
||||
quickMessagesReorder,
|
||||
quickMessagesUpdate,
|
||||
} from "@/lib/api"
|
||||
import type { QuickMessage } from "@/lib/types"
|
||||
|
||||
const LEFT_MIN_WIDTH = 280
|
||||
const RIGHT_MIN_WIDTH = 420
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
return Math.max(min, Math.min(max, value))
|
||||
}
|
||||
|
||||
function toPercent(pixels: number, totalPixels: number): number {
|
||||
if (totalPixels <= 0) return 0
|
||||
return (pixels / totalPixels) * 100
|
||||
}
|
||||
|
||||
interface QuickMessageReorderItemProps {
|
||||
message: QuickMessage
|
||||
selected: boolean
|
||||
reordering: boolean
|
||||
onSelect: (id: number) => void
|
||||
onDragStart: () => void
|
||||
onDragEnd: () => void
|
||||
children: (
|
||||
startDrag: (event: PointerEvent<HTMLButtonElement>) => void
|
||||
) => ReactNode
|
||||
}
|
||||
|
||||
function QuickMessageReorderItem({
|
||||
message,
|
||||
selected,
|
||||
reordering,
|
||||
onSelect,
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
children,
|
||||
}: QuickMessageReorderItemProps) {
|
||||
const dragControls = useDragControls()
|
||||
|
||||
const startDrag = useCallback(
|
||||
(event: PointerEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
dragControls.start(event)
|
||||
},
|
||||
[dragControls]
|
||||
)
|
||||
|
||||
return (
|
||||
<Reorder.Item
|
||||
as="section"
|
||||
value={message}
|
||||
data-quick-message-id={message.id}
|
||||
drag={reordering ? false : "y"}
|
||||
dragListener={false}
|
||||
dragControls={dragControls}
|
||||
dragMomentum={false}
|
||||
layout="position"
|
||||
className={cn(
|
||||
"rounded-lg border bg-card p-2.5 transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40",
|
||||
selected && "border-primary/60 bg-primary/5"
|
||||
)}
|
||||
tabIndex={0}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}
|
||||
onClick={() => onSelect(message.id)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.target !== event.currentTarget) return
|
||||
if (event.key !== "Enter" && event.key !== " ") return
|
||||
event.preventDefault()
|
||||
onSelect(message.id)
|
||||
}}
|
||||
>
|
||||
{children(startDrag)}
|
||||
</Reorder.Item>
|
||||
)
|
||||
}
|
||||
|
||||
export function QuickMessagesSettings() {
|
||||
const t = useTranslations("QuickMessagesSettings")
|
||||
|
||||
const [messages, setMessages] = useState<QuickMessage[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [loadError, setLoadError] = useState<string | null>(null)
|
||||
const [selectedId, setSelectedId] = useState<number | null>(null)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
|
||||
const [draftTitle, setDraftTitle] = useState("")
|
||||
const [draftContent, setDraftContent] = useState("")
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [creating, setCreating] = useState(false)
|
||||
const [deleteTargetId, setDeleteTargetId] = useState<number | null>(null)
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
|
||||
const [reordering, setReordering] = useState(false)
|
||||
const pendingOrderRef = useRef<number[] | null>(null)
|
||||
|
||||
const panelContainerRef = useRef<HTMLDivElement | null>(null)
|
||||
const [panelContainerWidth, setPanelContainerWidth] = useState(0)
|
||||
const titleInputRef = useRef<HTMLInputElement | null>(null)
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setLoading(true)
|
||||
setLoadError(null)
|
||||
try {
|
||||
const list = await quickMessagesList()
|
||||
setMessages(list)
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
setLoadError(message)
|
||||
setMessages([])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
refresh().catch((err) => {
|
||||
console.error("[QuickMessagesSettings] initial refresh failed:", err)
|
||||
})
|
||||
}, [refresh])
|
||||
|
||||
useEffect(() => {
|
||||
const container = panelContainerRef.current
|
||||
if (!container) return
|
||||
const updateWidth = (next: number) => {
|
||||
setPanelContainerWidth((prev) =>
|
||||
Math.abs(prev - next) < 1 ? prev : next
|
||||
)
|
||||
}
|
||||
updateWidth(container.getBoundingClientRect().width)
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
updateWidth(
|
||||
entries[0]?.contentRect.width ?? container.getBoundingClientRect().width
|
||||
)
|
||||
})
|
||||
observer.observe(container)
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const selectedMessage = useMemo(
|
||||
() => messages.find((m) => m.id === selectedId) ?? null,
|
||||
[messages, selectedId]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedMessage) {
|
||||
setDraftTitle(selectedMessage.title)
|
||||
setDraftContent(selectedMessage.content)
|
||||
} else {
|
||||
setDraftTitle("")
|
||||
setDraftContent("")
|
||||
}
|
||||
}, [selectedMessage])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedId === null && messages.length > 0) {
|
||||
setSelectedId(messages[0].id)
|
||||
}
|
||||
}, [selectedId, messages])
|
||||
|
||||
const filteredMessages = useMemo(() => {
|
||||
const q = searchQuery.trim().toLowerCase()
|
||||
if (!q) return messages
|
||||
return messages.filter(
|
||||
(m) =>
|
||||
m.title.toLowerCase().includes(q) || m.content.toLowerCase().includes(q)
|
||||
)
|
||||
}, [messages, searchQuery])
|
||||
|
||||
const isDirty = useMemo(() => {
|
||||
if (!selectedMessage) return false
|
||||
return (
|
||||
draftTitle !== selectedMessage.title ||
|
||||
draftContent !== selectedMessage.content
|
||||
)
|
||||
}, [selectedMessage, draftTitle, draftContent])
|
||||
|
||||
const persistReorder = useCallback(
|
||||
async (ids: number[]) => {
|
||||
if (ids.length === 0) return
|
||||
setReordering(true)
|
||||
try {
|
||||
await quickMessagesReorder(ids)
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
toast.error(t("toasts.saveOrderFailed"), { description: message })
|
||||
await refresh()
|
||||
} finally {
|
||||
setReordering(false)
|
||||
}
|
||||
},
|
||||
[refresh, t]
|
||||
)
|
||||
|
||||
const handleReorder = useCallback((next: QuickMessage[]) => {
|
||||
const reordered = next.map((m, index) => ({ ...m, sort_order: index }))
|
||||
setMessages(reordered)
|
||||
pendingOrderRef.current = reordered.map((m) => m.id)
|
||||
}, [])
|
||||
|
||||
const handleCreate = useCallback(async () => {
|
||||
setCreating(true)
|
||||
try {
|
||||
const created = await quickMessagesCreate({ title: "", content: "" })
|
||||
setMessages((prev) => [...prev, created])
|
||||
setSelectedId(created.id)
|
||||
toast.success(t("toasts.created"))
|
||||
requestAnimationFrame(() => {
|
||||
titleInputRef.current?.focus()
|
||||
})
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
toast.error(t("toasts.createFailed"), { description: message })
|
||||
} finally {
|
||||
setCreating(false)
|
||||
}
|
||||
}, [t])
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!selectedMessage) return
|
||||
setSaving(true)
|
||||
try {
|
||||
const updated = await quickMessagesUpdate({
|
||||
id: selectedMessage.id,
|
||||
title: draftTitle,
|
||||
content: draftContent,
|
||||
})
|
||||
setMessages((prev) =>
|
||||
prev.map((m) => (m.id === updated.id ? updated : m))
|
||||
)
|
||||
toast.success(t("toasts.saved"))
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
toast.error(t("toasts.saveFailed"), { description: message })
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}, [selectedMessage, draftTitle, draftContent, t])
|
||||
|
||||
const handleDelete = useCallback(async () => {
|
||||
if (deleteTargetId === null) return
|
||||
const target = deleteTargetId
|
||||
setDeleting(true)
|
||||
try {
|
||||
await quickMessagesDelete(target)
|
||||
setMessages((prev) => {
|
||||
const next = prev.filter((m) => m.id !== target)
|
||||
if (selectedId === target) {
|
||||
setSelectedId(next[0]?.id ?? null)
|
||||
}
|
||||
return next
|
||||
})
|
||||
toast.success(t("toasts.deleted"))
|
||||
setDeleteTargetId(null)
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
toast.error(t("toasts.deleteFailed"), { description: message })
|
||||
} finally {
|
||||
setDeleting(false)
|
||||
}
|
||||
}, [deleteTargetId, selectedId, t])
|
||||
|
||||
const safeContainerWidth =
|
||||
panelContainerWidth > 0 ? panelContainerWidth : 1200
|
||||
const leftMinSize = clamp(
|
||||
toPercent(LEFT_MIN_WIDTH, safeContainerWidth),
|
||||
5,
|
||||
95
|
||||
)
|
||||
const rightMinSize = clamp(
|
||||
toPercent(RIGHT_MIN_WIDTH, safeContainerWidth),
|
||||
5,
|
||||
95
|
||||
)
|
||||
const leftMaxSize = Math.max(leftMinSize, 100 - rightMinSize)
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="h-full flex items-center justify-center text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||
{t("loading")}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const deleteTargetMessage =
|
||||
deleteTargetId !== null
|
||||
? (messages.find((m) => m.id === deleteTargetId) ?? null)
|
||||
: null
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col p-3 md:p-4">
|
||||
<div className="flex items-center justify-between gap-3 pb-4">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold">{t("title")}</h2>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{t("description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loadError && (
|
||||
<div className="mb-3 rounded-md border border-red-500/30 bg-red-500/5 px-3 py-2 text-xs text-red-400">
|
||||
{loadError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={panelContainerRef} className="flex-1 min-h-0 min-w-0">
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
className="h-full min-h-0 min-w-0"
|
||||
>
|
||||
<ResizablePanel
|
||||
defaultSize={34}
|
||||
minSize={leftMinSize}
|
||||
maxSize={leftMaxSize}
|
||||
>
|
||||
<div className="min-h-0 h-full min-w-0 rounded-lg border bg-card flex flex-col overflow-hidden lg:rounded-r-none">
|
||||
<div className="border-b p-3 space-y-2.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
value={searchQuery}
|
||||
onChange={(event) => setSearchQuery(event.target.value)}
|
||||
placeholder={t("searchPlaceholder")}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
handleCreate().catch((err) => {
|
||||
console.error(
|
||||
"[QuickMessagesSettings] create failed:",
|
||||
err
|
||||
)
|
||||
})
|
||||
}}
|
||||
disabled={creating}
|
||||
>
|
||||
{creating ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
) : (
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{t("actions.new")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{filteredMessages.length === 0 ? (
|
||||
<div className="flex-1 flex items-center justify-center text-xs text-muted-foreground px-4 text-center">
|
||||
{messages.length === 0
|
||||
? t("emptyList")
|
||||
: t("searchPlaceholder")}
|
||||
</div>
|
||||
) : (
|
||||
<Reorder.Group
|
||||
as="div"
|
||||
axis="y"
|
||||
values={messages}
|
||||
onReorder={handleReorder}
|
||||
className="flex-1 min-h-0 overflow-y-auto space-y-2 p-2"
|
||||
>
|
||||
{filteredMessages.map((m) => (
|
||||
<QuickMessageReorderItem
|
||||
key={m.id}
|
||||
message={m}
|
||||
selected={selectedId === m.id}
|
||||
reordering={reordering}
|
||||
onSelect={(id) => setSelectedId(id)}
|
||||
onDragStart={() => {
|
||||
/* no-op: list re-render handles dragging state */
|
||||
}}
|
||||
onDragEnd={() => {
|
||||
const order = pendingOrderRef.current
|
||||
pendingOrderRef.current = null
|
||||
if (order && !reordering) {
|
||||
persistReorder(order).catch((err) => {
|
||||
console.error(
|
||||
"[QuickMessagesSettings] reorder failed:",
|
||||
err
|
||||
)
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
{(startDrag) => (
|
||||
<div className="flex items-center gap-2 overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
className="text-muted-foreground cursor-grab active:cursor-grabbing rounded p-0.5 hover:bg-muted"
|
||||
title={t("actions.dragSort")}
|
||||
aria-label={t("actions.dragSortMessage", {
|
||||
name: m.title || t("untitled"),
|
||||
})}
|
||||
onPointerDown={startDrag}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
disabled={reordering}
|
||||
>
|
||||
<GripVertical className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium truncate">
|
||||
{m.title || (
|
||||
<span className="italic text-muted-foreground">
|
||||
{t("untitled")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{m.content && (
|
||||
<div className="text-[11px] text-muted-foreground truncate mt-0.5">
|
||||
{m.content}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</QuickMessageReorderItem>
|
||||
))}
|
||||
</Reorder.Group>
|
||||
)}
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
|
||||
<ResizableHandle withHandle />
|
||||
|
||||
<ResizablePanel defaultSize={66} minSize={rightMinSize}>
|
||||
<div className="h-full flex-1 min-h-0 min-w-0 rounded-lg border bg-card overflow-hidden lg:rounded-l-none lg:border-l-0">
|
||||
{selectedMessage ? (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="quick-message-title" className="text-xs">
|
||||
{t("fields.title")}
|
||||
</Label>
|
||||
<Input
|
||||
id="quick-message-title"
|
||||
ref={titleInputRef}
|
||||
value={draftTitle}
|
||||
onChange={(event) => setDraftTitle(event.target.value)}
|
||||
placeholder={t("fields.titlePlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label
|
||||
htmlFor="quick-message-content"
|
||||
className="text-xs"
|
||||
>
|
||||
{t("fields.content")}
|
||||
</Label>
|
||||
<Textarea
|
||||
id="quick-message-content"
|
||||
value={draftContent}
|
||||
onChange={(event) =>
|
||||
setDraftContent(event.target.value)
|
||||
}
|
||||
placeholder={t("fields.contentPlaceholder")}
|
||||
className="min-h-[260px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t px-4 py-3 flex items-center justify-between gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setDeleteTargetId(selectedMessage.id)}
|
||||
className="text-red-500 hover:text-red-500"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
{t("actions.delete")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
handleSave().catch((err) => {
|
||||
console.error(
|
||||
"[QuickMessagesSettings] save failed:",
|
||||
err
|
||||
)
|
||||
})
|
||||
}}
|
||||
disabled={saving || !isDirty}
|
||||
>
|
||||
{saving ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
) : (
|
||||
<Save className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-full flex items-center justify-center text-xs text-muted-foreground">
|
||||
{t("emptySelection")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
|
||||
<AlertDialog
|
||||
open={deleteTargetId !== null}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setDeleteTargetId(null)
|
||||
}}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t("confirmDelete.title")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t("confirmDelete.message", {
|
||||
name: deleteTargetMessage?.title || t("untitled"),
|
||||
})}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={deleting}>
|
||||
{t("confirmDelete.cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
handleDelete().catch((err) => {
|
||||
console.error("[QuickMessagesSettings] delete failed:", err)
|
||||
})
|
||||
}}
|
||||
disabled={deleting}
|
||||
>
|
||||
{deleting ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
) : null}
|
||||
{t("confirmDelete.confirm")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
Globe,
|
||||
Keyboard,
|
||||
Menu,
|
||||
MessageSquareText,
|
||||
SendHorizontal,
|
||||
Palette,
|
||||
PlugZap,
|
||||
@@ -41,6 +42,7 @@ interface SettingsNavItem {
|
||||
| "mcp"
|
||||
| "skills"
|
||||
| "experts"
|
||||
| "quick_messages"
|
||||
| "shortcuts"
|
||||
| "version_control"
|
||||
| "chat_channels"
|
||||
@@ -70,6 +72,11 @@ const SETTINGS_NAV_ITEMS: SettingsNavItem[] = [
|
||||
labelKey: "experts",
|
||||
icon: Sparkles,
|
||||
},
|
||||
{
|
||||
href: "/settings/quick-messages",
|
||||
labelKey: "quick_messages",
|
||||
icon: MessageSquareText,
|
||||
},
|
||||
{
|
||||
href: "/settings/agents",
|
||||
labelKey: "agents",
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"chat_channels": "قنوات المحادثة",
|
||||
"web_service": "خدمة الويب",
|
||||
"model_providers": "مزودو النماذج",
|
||||
"experts": "الخبراء"
|
||||
"experts": "الخبراء",
|
||||
"quick_messages": "رسائل سريعة"
|
||||
}
|
||||
},
|
||||
"AppearanceSettings": {
|
||||
@@ -2108,5 +2109,43 @@
|
||||
"disableFailed": "فشل تعطيل الخبير",
|
||||
"openFolderFailed": "فشل فتح المجلد"
|
||||
}
|
||||
},
|
||||
"QuickMessagesSettings": {
|
||||
"title": "رسائل سريعة",
|
||||
"description": "إدارة مقتطفات الرسائل القابلة لإعادة الاستخدام. اسحب لإعادة الترتيب.",
|
||||
"loading": "جارٍ تحميل الرسائل السريعة…",
|
||||
"emptyList": "لا توجد رسائل سريعة بعد. انقر على \"جديد\" لإنشاء واحدة.",
|
||||
"emptySelection": "اختر رسالة سريعة للتعديل.",
|
||||
"searchPlaceholder": "ابحث حسب العنوان أو المحتوى",
|
||||
"untitled": "بدون عنوان",
|
||||
"actions": {
|
||||
"new": "جديد",
|
||||
"save": "حفظ",
|
||||
"delete": "حذف",
|
||||
"dragSort": "اسحب لإعادة الترتيب",
|
||||
"dragSortMessage": "اسحب لإعادة ترتيب الرسالة السريعة: {name}"
|
||||
},
|
||||
"fields": {
|
||||
"title": "العنوان",
|
||||
"titlePlaceholder": "أعطِ هذه الرسالة عنوانًا قصيرًا",
|
||||
"content": "المحتوى",
|
||||
"contentPlaceholder": "اكتب محتوى الرسالة هنا"
|
||||
},
|
||||
"confirmDelete": {
|
||||
"title": "حذف الرسالة السريعة؟",
|
||||
"message": "سيؤدي ذلك إلى حذف \"{name}\" نهائيًا. هل أنت متأكد؟",
|
||||
"cancel": "إلغاء",
|
||||
"confirm": "حذف"
|
||||
},
|
||||
"toasts": {
|
||||
"loadFailed": "فشل تحميل الرسائل السريعة",
|
||||
"createFailed": "فشل إنشاء الرسالة السريعة",
|
||||
"saveFailed": "فشل حفظ الرسالة السريعة",
|
||||
"deleteFailed": "فشل حذف الرسالة السريعة",
|
||||
"saveOrderFailed": "فشل حفظ الترتيب",
|
||||
"created": "تم إنشاء الرسالة السريعة",
|
||||
"saved": "تم حفظ الرسالة السريعة",
|
||||
"deleted": "تم حذف الرسالة السريعة"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"chat_channels": "Chat-Kanäle",
|
||||
"web_service": "Webdienst",
|
||||
"model_providers": "Modellanbieter",
|
||||
"experts": "Experten"
|
||||
"experts": "Experten",
|
||||
"quick_messages": "Schnellnachrichten"
|
||||
}
|
||||
},
|
||||
"AppearanceSettings": {
|
||||
@@ -2108,5 +2109,43 @@
|
||||
"disableFailed": "Deaktivieren des Experten fehlgeschlagen",
|
||||
"openFolderFailed": "Ordner konnte nicht geöffnet werden"
|
||||
}
|
||||
},
|
||||
"QuickMessagesSettings": {
|
||||
"title": "Schnellnachrichten",
|
||||
"description": "Verwalte wiederverwendbare Nachrichtenbausteine. Ziehe zum Neuordnen.",
|
||||
"loading": "Schnellnachrichten werden geladen…",
|
||||
"emptyList": "Noch keine Schnellnachrichten. Klicke auf \"Neu\", um eine zu erstellen.",
|
||||
"emptySelection": "Wähle eine Schnellnachricht zum Bearbeiten aus.",
|
||||
"searchPlaceholder": "Nach Titel oder Inhalt suchen",
|
||||
"untitled": "Ohne Titel",
|
||||
"actions": {
|
||||
"new": "Neu",
|
||||
"save": "Speichern",
|
||||
"delete": "Löschen",
|
||||
"dragSort": "Zum Neuordnen ziehen",
|
||||
"dragSortMessage": "Schnellnachricht neu ordnen: {name}"
|
||||
},
|
||||
"fields": {
|
||||
"title": "Titel",
|
||||
"titlePlaceholder": "Gib dieser Nachricht einen kurzen Titel",
|
||||
"content": "Inhalt",
|
||||
"contentPlaceholder": "Gib hier den Nachrichteninhalt ein"
|
||||
},
|
||||
"confirmDelete": {
|
||||
"title": "Schnellnachricht löschen?",
|
||||
"message": "Dadurch wird \"{name}\" dauerhaft gelöscht. Bist du sicher?",
|
||||
"cancel": "Abbrechen",
|
||||
"confirm": "Löschen"
|
||||
},
|
||||
"toasts": {
|
||||
"loadFailed": "Laden der Schnellnachrichten fehlgeschlagen",
|
||||
"createFailed": "Erstellen der Schnellnachricht fehlgeschlagen",
|
||||
"saveFailed": "Speichern der Schnellnachricht fehlgeschlagen",
|
||||
"deleteFailed": "Löschen der Schnellnachricht fehlgeschlagen",
|
||||
"saveOrderFailed": "Reihenfolge konnte nicht gespeichert werden",
|
||||
"created": "Schnellnachricht erstellt",
|
||||
"saved": "Schnellnachricht gespeichert",
|
||||
"deleted": "Schnellnachricht gelöscht"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"chat_channels": "Chat Channels",
|
||||
"web_service": "Web Service",
|
||||
"model_providers": "Model Providers",
|
||||
"experts": "Experts"
|
||||
"experts": "Experts",
|
||||
"quick_messages": "Quick Messages"
|
||||
}
|
||||
},
|
||||
"AppearanceSettings": {
|
||||
@@ -2108,5 +2109,43 @@
|
||||
"disableFailed": "Failed to disable expert",
|
||||
"openFolderFailed": "Failed to open folder"
|
||||
}
|
||||
},
|
||||
"QuickMessagesSettings": {
|
||||
"title": "Quick Messages",
|
||||
"description": "Manage reusable message snippets. Drag to reorder.",
|
||||
"loading": "Loading quick messages…",
|
||||
"emptyList": "No quick messages yet. Click \"New\" to create one.",
|
||||
"emptySelection": "Select a quick message to edit.",
|
||||
"searchPlaceholder": "Search by title or content",
|
||||
"untitled": "Untitled",
|
||||
"actions": {
|
||||
"new": "New",
|
||||
"save": "Save",
|
||||
"delete": "Delete",
|
||||
"dragSort": "Drag to reorder",
|
||||
"dragSortMessage": "Drag to reorder quick message: {name}"
|
||||
},
|
||||
"fields": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "Give this message a short title",
|
||||
"content": "Content",
|
||||
"contentPlaceholder": "Write the message content here"
|
||||
},
|
||||
"confirmDelete": {
|
||||
"title": "Delete quick message?",
|
||||
"message": "This will permanently delete \"{name}\". Are you sure?",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Delete"
|
||||
},
|
||||
"toasts": {
|
||||
"loadFailed": "Failed to load quick messages",
|
||||
"createFailed": "Failed to create quick message",
|
||||
"saveFailed": "Failed to save quick message",
|
||||
"deleteFailed": "Failed to delete quick message",
|
||||
"saveOrderFailed": "Failed to save order",
|
||||
"created": "Quick message created",
|
||||
"saved": "Quick message saved",
|
||||
"deleted": "Quick message deleted"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"chat_channels": "Canales de chat",
|
||||
"web_service": "Servicio Web",
|
||||
"model_providers": "Proveedores de Modelos",
|
||||
"experts": "Expertos"
|
||||
"experts": "Expertos",
|
||||
"quick_messages": "Mensajes rápidos"
|
||||
}
|
||||
},
|
||||
"AppearanceSettings": {
|
||||
@@ -2108,5 +2109,43 @@
|
||||
"disableFailed": "Error al deshabilitar el experto",
|
||||
"openFolderFailed": "Error al abrir la carpeta"
|
||||
}
|
||||
},
|
||||
"QuickMessagesSettings": {
|
||||
"title": "Mensajes rápidos",
|
||||
"description": "Gestiona fragmentos de mensajes reutilizables. Arrastra para reordenar.",
|
||||
"loading": "Cargando mensajes rápidos…",
|
||||
"emptyList": "Aún no hay mensajes rápidos. Haz clic en \"Nuevo\" para crear uno.",
|
||||
"emptySelection": "Selecciona un mensaje rápido para editar.",
|
||||
"searchPlaceholder": "Buscar por título o contenido",
|
||||
"untitled": "Sin título",
|
||||
"actions": {
|
||||
"new": "Nuevo",
|
||||
"save": "Guardar",
|
||||
"delete": "Eliminar",
|
||||
"dragSort": "Arrastra para reordenar",
|
||||
"dragSortMessage": "Arrastra para reordenar mensaje rápido: {name}"
|
||||
},
|
||||
"fields": {
|
||||
"title": "Título",
|
||||
"titlePlaceholder": "Asigna un título corto a este mensaje",
|
||||
"content": "Contenido",
|
||||
"contentPlaceholder": "Escribe aquí el contenido del mensaje"
|
||||
},
|
||||
"confirmDelete": {
|
||||
"title": "¿Eliminar mensaje rápido?",
|
||||
"message": "Esto eliminará permanentemente \"{name}\". ¿Estás seguro?",
|
||||
"cancel": "Cancelar",
|
||||
"confirm": "Eliminar"
|
||||
},
|
||||
"toasts": {
|
||||
"loadFailed": "Error al cargar mensajes rápidos",
|
||||
"createFailed": "Error al crear el mensaje rápido",
|
||||
"saveFailed": "Error al guardar el mensaje rápido",
|
||||
"deleteFailed": "Error al eliminar el mensaje rápido",
|
||||
"saveOrderFailed": "Error al guardar el orden",
|
||||
"created": "Mensaje rápido creado",
|
||||
"saved": "Mensaje rápido guardado",
|
||||
"deleted": "Mensaje rápido eliminado"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"chat_channels": "Canaux de chat",
|
||||
"web_service": "Service Web",
|
||||
"model_providers": "Fournisseurs de Modèles",
|
||||
"experts": "Experts"
|
||||
"experts": "Experts",
|
||||
"quick_messages": "Messages rapides"
|
||||
}
|
||||
},
|
||||
"AppearanceSettings": {
|
||||
@@ -2108,5 +2109,43 @@
|
||||
"disableFailed": "Échec de la désactivation de l'expert",
|
||||
"openFolderFailed": "Échec de l'ouverture du dossier"
|
||||
}
|
||||
},
|
||||
"QuickMessagesSettings": {
|
||||
"title": "Messages rapides",
|
||||
"description": "Gérez des extraits de messages réutilisables. Glissez pour réorganiser.",
|
||||
"loading": "Chargement des messages rapides…",
|
||||
"emptyList": "Aucun message rapide. Cliquez sur « Nouveau » pour en créer un.",
|
||||
"emptySelection": "Sélectionnez un message rapide à modifier.",
|
||||
"searchPlaceholder": "Rechercher par titre ou contenu",
|
||||
"untitled": "Sans titre",
|
||||
"actions": {
|
||||
"new": "Nouveau",
|
||||
"save": "Enregistrer",
|
||||
"delete": "Supprimer",
|
||||
"dragSort": "Glisser pour réorganiser",
|
||||
"dragSortMessage": "Réorganiser le message rapide : {name}"
|
||||
},
|
||||
"fields": {
|
||||
"title": "Titre",
|
||||
"titlePlaceholder": "Donnez un titre court à ce message",
|
||||
"content": "Contenu",
|
||||
"contentPlaceholder": "Saisissez ici le contenu du message"
|
||||
},
|
||||
"confirmDelete": {
|
||||
"title": "Supprimer le message rapide ?",
|
||||
"message": "Cela supprimera définitivement « {name} ». Êtes-vous sûr ?",
|
||||
"cancel": "Annuler",
|
||||
"confirm": "Supprimer"
|
||||
},
|
||||
"toasts": {
|
||||
"loadFailed": "Échec du chargement des messages rapides",
|
||||
"createFailed": "Échec de la création du message rapide",
|
||||
"saveFailed": "Échec de l'enregistrement du message rapide",
|
||||
"deleteFailed": "Échec de la suppression du message rapide",
|
||||
"saveOrderFailed": "Échec de l'enregistrement de l'ordre",
|
||||
"created": "Message rapide créé",
|
||||
"saved": "Message rapide enregistré",
|
||||
"deleted": "Message rapide supprimé"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"chat_channels": "チャットチャンネル",
|
||||
"web_service": "Webサービス",
|
||||
"model_providers": "モデルプロバイダー",
|
||||
"experts": "エキスパート"
|
||||
"experts": "エキスパート",
|
||||
"quick_messages": "クイックメッセージ"
|
||||
}
|
||||
},
|
||||
"AppearanceSettings": {
|
||||
@@ -2108,5 +2109,43 @@
|
||||
"disableFailed": "エキスパートの無効化に失敗しました",
|
||||
"openFolderFailed": "フォルダを開けませんでした"
|
||||
}
|
||||
},
|
||||
"QuickMessagesSettings": {
|
||||
"title": "クイックメッセージ",
|
||||
"description": "再利用可能なメッセージスニペットを管理します。ドラッグして並べ替えできます。",
|
||||
"loading": "クイックメッセージを読み込み中…",
|
||||
"emptyList": "クイックメッセージはまだありません。「新規」をクリックして作成してください。",
|
||||
"emptySelection": "編集するクイックメッセージを選択してください。",
|
||||
"searchPlaceholder": "タイトルまたは内容で検索",
|
||||
"untitled": "無題",
|
||||
"actions": {
|
||||
"new": "新規",
|
||||
"save": "保存",
|
||||
"delete": "削除",
|
||||
"dragSort": "ドラッグして並べ替え",
|
||||
"dragSortMessage": "クイックメッセージを並べ替え: {name}"
|
||||
},
|
||||
"fields": {
|
||||
"title": "タイトル",
|
||||
"titlePlaceholder": "このメッセージに短いタイトルを付けてください",
|
||||
"content": "内容",
|
||||
"contentPlaceholder": "ここにメッセージ内容を入力してください"
|
||||
},
|
||||
"confirmDelete": {
|
||||
"title": "クイックメッセージを削除しますか?",
|
||||
"message": "「{name}」を完全に削除します。よろしいですか?",
|
||||
"cancel": "キャンセル",
|
||||
"confirm": "削除"
|
||||
},
|
||||
"toasts": {
|
||||
"loadFailed": "クイックメッセージの読み込みに失敗しました",
|
||||
"createFailed": "クイックメッセージの作成に失敗しました",
|
||||
"saveFailed": "クイックメッセージの保存に失敗しました",
|
||||
"deleteFailed": "クイックメッセージの削除に失敗しました",
|
||||
"saveOrderFailed": "順序の保存に失敗しました",
|
||||
"created": "クイックメッセージを作成しました",
|
||||
"saved": "クイックメッセージを保存しました",
|
||||
"deleted": "クイックメッセージを削除しました"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"chat_channels": "채팅 채널",
|
||||
"web_service": "웹 서비스",
|
||||
"model_providers": "모델 제공업체",
|
||||
"experts": "전문가"
|
||||
"experts": "전문가",
|
||||
"quick_messages": "빠른 메시지"
|
||||
}
|
||||
},
|
||||
"AppearanceSettings": {
|
||||
@@ -2108,5 +2109,43 @@
|
||||
"disableFailed": "전문가 비활성화에 실패했습니다",
|
||||
"openFolderFailed": "폴더를 열지 못했습니다"
|
||||
}
|
||||
},
|
||||
"QuickMessagesSettings": {
|
||||
"title": "빠른 메시지",
|
||||
"description": "재사용 가능한 메시지 스니펫을 관리합니다. 드래그하여 순서를 변경하세요.",
|
||||
"loading": "빠른 메시지 불러오는 중…",
|
||||
"emptyList": "빠른 메시지가 없습니다. \"새로 만들기\"를 클릭하여 추가하세요.",
|
||||
"emptySelection": "편집할 빠른 메시지를 선택하세요.",
|
||||
"searchPlaceholder": "제목 또는 내용으로 검색",
|
||||
"untitled": "제목 없음",
|
||||
"actions": {
|
||||
"new": "새로 만들기",
|
||||
"save": "저장",
|
||||
"delete": "삭제",
|
||||
"dragSort": "드래그하여 순서 변경",
|
||||
"dragSortMessage": "빠른 메시지 순서 변경: {name}"
|
||||
},
|
||||
"fields": {
|
||||
"title": "제목",
|
||||
"titlePlaceholder": "이 메시지에 짧은 제목을 지정하세요",
|
||||
"content": "내용",
|
||||
"contentPlaceholder": "여기에 메시지 내용을 입력하세요"
|
||||
},
|
||||
"confirmDelete": {
|
||||
"title": "빠른 메시지를 삭제하시겠습니까?",
|
||||
"message": "\"{name}\" 이(가) 영구적으로 삭제됩니다. 계속하시겠습니까?",
|
||||
"cancel": "취소",
|
||||
"confirm": "삭제"
|
||||
},
|
||||
"toasts": {
|
||||
"loadFailed": "빠른 메시지를 불러오지 못했습니다",
|
||||
"createFailed": "빠른 메시지를 만들지 못했습니다",
|
||||
"saveFailed": "빠른 메시지를 저장하지 못했습니다",
|
||||
"deleteFailed": "빠른 메시지를 삭제하지 못했습니다",
|
||||
"saveOrderFailed": "순서를 저장하지 못했습니다",
|
||||
"created": "빠른 메시지가 생성되었습니다",
|
||||
"saved": "빠른 메시지가 저장되었습니다",
|
||||
"deleted": "빠른 메시지가 삭제되었습니다"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"chat_channels": "Canais de chat",
|
||||
"web_service": "Serviço Web",
|
||||
"model_providers": "Provedores de Modelos",
|
||||
"experts": "Especialistas"
|
||||
"experts": "Especialistas",
|
||||
"quick_messages": "Mensagens rápidas"
|
||||
}
|
||||
},
|
||||
"AppearanceSettings": {
|
||||
@@ -2108,5 +2109,43 @@
|
||||
"disableFailed": "Falha ao desativar o especialista",
|
||||
"openFolderFailed": "Falha ao abrir a pasta"
|
||||
}
|
||||
},
|
||||
"QuickMessagesSettings": {
|
||||
"title": "Mensagens rápidas",
|
||||
"description": "Gerencie trechos de mensagens reutilizáveis. Arraste para reordenar.",
|
||||
"loading": "Carregando mensagens rápidas…",
|
||||
"emptyList": "Ainda não há mensagens rápidas. Clique em \"Nova\" para criar uma.",
|
||||
"emptySelection": "Selecione uma mensagem rápida para editar.",
|
||||
"searchPlaceholder": "Pesquisar por título ou conteúdo",
|
||||
"untitled": "Sem título",
|
||||
"actions": {
|
||||
"new": "Nova",
|
||||
"save": "Salvar",
|
||||
"delete": "Excluir",
|
||||
"dragSort": "Arraste para reordenar",
|
||||
"dragSortMessage": "Arrastar para reordenar mensagem rápida: {name}"
|
||||
},
|
||||
"fields": {
|
||||
"title": "Título",
|
||||
"titlePlaceholder": "Dê um título curto a esta mensagem",
|
||||
"content": "Conteúdo",
|
||||
"contentPlaceholder": "Digite aqui o conteúdo da mensagem"
|
||||
},
|
||||
"confirmDelete": {
|
||||
"title": "Excluir mensagem rápida?",
|
||||
"message": "Isso excluirá permanentemente \"{name}\". Tem certeza?",
|
||||
"cancel": "Cancelar",
|
||||
"confirm": "Excluir"
|
||||
},
|
||||
"toasts": {
|
||||
"loadFailed": "Falha ao carregar mensagens rápidas",
|
||||
"createFailed": "Falha ao criar mensagem rápida",
|
||||
"saveFailed": "Falha ao salvar mensagem rápida",
|
||||
"deleteFailed": "Falha ao excluir mensagem rápida",
|
||||
"saveOrderFailed": "Falha ao salvar a ordem",
|
||||
"created": "Mensagem rápida criada",
|
||||
"saved": "Mensagem rápida salva",
|
||||
"deleted": "Mensagem rápida excluída"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"chat_channels": "消息渠道",
|
||||
"web_service": "Web 服务",
|
||||
"model_providers": "模型供应商",
|
||||
"experts": "专家"
|
||||
"experts": "专家",
|
||||
"quick_messages": "快捷消息"
|
||||
}
|
||||
},
|
||||
"AppearanceSettings": {
|
||||
@@ -2108,5 +2109,43 @@
|
||||
"disableFailed": "禁用专家失败",
|
||||
"openFolderFailed": "打开目录失败"
|
||||
}
|
||||
},
|
||||
"QuickMessagesSettings": {
|
||||
"title": "快捷消息",
|
||||
"description": "管理可复用的消息片段。拖拽以调整顺序。",
|
||||
"loading": "加载快捷消息…",
|
||||
"emptyList": "暂无快捷消息。点击“新建”创建一条。",
|
||||
"emptySelection": "选择一条快捷消息进行编辑。",
|
||||
"searchPlaceholder": "按标题或内容搜索",
|
||||
"untitled": "未命名",
|
||||
"actions": {
|
||||
"new": "新建",
|
||||
"save": "保存",
|
||||
"delete": "删除",
|
||||
"dragSort": "拖拽排序",
|
||||
"dragSortMessage": "拖拽排序快捷消息:{name}"
|
||||
},
|
||||
"fields": {
|
||||
"title": "标题",
|
||||
"titlePlaceholder": "为此消息取一个简短的标题",
|
||||
"content": "内容",
|
||||
"contentPlaceholder": "在此输入消息内容"
|
||||
},
|
||||
"confirmDelete": {
|
||||
"title": "删除快捷消息?",
|
||||
"message": "将永久删除“{name}”。确定吗?",
|
||||
"cancel": "取消",
|
||||
"confirm": "删除"
|
||||
},
|
||||
"toasts": {
|
||||
"loadFailed": "加载快捷消息失败",
|
||||
"createFailed": "创建快捷消息失败",
|
||||
"saveFailed": "保存快捷消息失败",
|
||||
"deleteFailed": "删除快捷消息失败",
|
||||
"saveOrderFailed": "保存顺序失败",
|
||||
"created": "已创建快捷消息",
|
||||
"saved": "已保存快捷消息",
|
||||
"deleted": "已删除快捷消息"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"chat_channels": "訊息頻道",
|
||||
"web_service": "Web 服務",
|
||||
"model_providers": "模型供應商",
|
||||
"experts": "專家"
|
||||
"experts": "專家",
|
||||
"quick_messages": "快捷訊息"
|
||||
}
|
||||
},
|
||||
"AppearanceSettings": {
|
||||
@@ -2108,5 +2109,43 @@
|
||||
"disableFailed": "停用專家失敗",
|
||||
"openFolderFailed": "開啟目錄失敗"
|
||||
}
|
||||
},
|
||||
"QuickMessagesSettings": {
|
||||
"title": "快捷訊息",
|
||||
"description": "管理可重用的訊息片段。拖曳以調整順序。",
|
||||
"loading": "載入快捷訊息…",
|
||||
"emptyList": "尚無快捷訊息。點擊「新增」建立一條。",
|
||||
"emptySelection": "選擇一條快捷訊息進行編輯。",
|
||||
"searchPlaceholder": "依標題或內容搜尋",
|
||||
"untitled": "未命名",
|
||||
"actions": {
|
||||
"new": "新增",
|
||||
"save": "儲存",
|
||||
"delete": "刪除",
|
||||
"dragSort": "拖曳排序",
|
||||
"dragSortMessage": "拖曳排序快捷訊息:{name}"
|
||||
},
|
||||
"fields": {
|
||||
"title": "標題",
|
||||
"titlePlaceholder": "為此訊息取一個簡短的標題",
|
||||
"content": "內容",
|
||||
"contentPlaceholder": "在此輸入訊息內容"
|
||||
},
|
||||
"confirmDelete": {
|
||||
"title": "刪除快捷訊息?",
|
||||
"message": "將永久刪除「{name}」。確定嗎?",
|
||||
"cancel": "取消",
|
||||
"confirm": "刪除"
|
||||
},
|
||||
"toasts": {
|
||||
"loadFailed": "載入快捷訊息失敗",
|
||||
"createFailed": "建立快捷訊息失敗",
|
||||
"saveFailed": "儲存快捷訊息失敗",
|
||||
"deleteFailed": "刪除快捷訊息失敗",
|
||||
"saveOrderFailed": "儲存順序失敗",
|
||||
"created": "已建立快捷訊息",
|
||||
"saved": "已儲存快捷訊息",
|
||||
"deleted": "已刪除快捷訊息"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ import type {
|
||||
ChatChannelMessageLog,
|
||||
ModelProviderInfo,
|
||||
PluginCheckSummary,
|
||||
QuickMessage,
|
||||
} from "./types"
|
||||
|
||||
export async function listConversations(params?: {
|
||||
@@ -1258,6 +1259,42 @@ export async function bootstrapFolderCommandsFromPackageJson(
|
||||
})
|
||||
}
|
||||
|
||||
// Quick message management
|
||||
|
||||
export async function quickMessagesList(): Promise<QuickMessage[]> {
|
||||
return getTransport().call("quick_messages_list")
|
||||
}
|
||||
|
||||
export async function quickMessagesCreate(params: {
|
||||
title: string
|
||||
content: string
|
||||
}): Promise<QuickMessage> {
|
||||
return getTransport().call("quick_messages_create", {
|
||||
title: params.title,
|
||||
content: params.content,
|
||||
})
|
||||
}
|
||||
|
||||
export async function quickMessagesUpdate(params: {
|
||||
id: number
|
||||
title?: string
|
||||
content?: string
|
||||
}): Promise<QuickMessage> {
|
||||
return getTransport().call("quick_messages_update", {
|
||||
id: params.id,
|
||||
title: params.title ?? null,
|
||||
content: params.content ?? null,
|
||||
})
|
||||
}
|
||||
|
||||
export async function quickMessagesDelete(id: number): Promise<void> {
|
||||
return getTransport().call("quick_messages_delete", { id })
|
||||
}
|
||||
|
||||
export async function quickMessagesReorder(ids: number[]): Promise<void> {
|
||||
return getTransport().call("quick_messages_reorder", { ids })
|
||||
}
|
||||
|
||||
// Directory browser (for web/server mode)
|
||||
|
||||
export async function getHomeDirectory(): Promise<string> {
|
||||
|
||||
@@ -769,6 +769,15 @@ export interface FolderCommand {
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface QuickMessage {
|
||||
id: number
|
||||
title: string
|
||||
content: string
|
||||
sort_order: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface GitStatusEntry {
|
||||
status: string
|
||||
file: string
|
||||
|
||||
Reference in New Issue
Block a user