feat(playground): add chat history clearing

- add a toolbar action that is enabled only when saved playground messages exist.
- confirm destructive clears before removing browser-stored conversation state.
- add localized strings for the action, dialog, and completion toast.
This commit is contained in:
QuentinHsu
2026-05-31 14:23:50 +08:00
parent 9b633a4131
commit 4372abd787
9 changed files with 124 additions and 42 deletions
@@ -16,9 +16,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { GlobeIcon, PaperclipIcon } from 'lucide-react'
import { GlobeIcon, PaperclipIcon, Trash2Icon } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { ConfirmDialog } from '@/components/confirm-dialog'
import {
DropdownMenu,
DropdownMenuContent,
@@ -37,10 +39,17 @@ import {
type PlaygroundInputToolsProps = {
disabled?: boolean
hasMessages?: boolean
onClearMessages?: () => void
}
export function PlaygroundInputTools({ disabled }: PlaygroundInputToolsProps) {
export function PlaygroundInputTools({
disabled,
hasMessages = false,
onClearMessages,
}: PlaygroundInputToolsProps) {
const { t } = useTranslation()
const [clearConfirmOpen, setClearConfirmOpen] = useState(false)
const handleFileAction = (action: string) => {
const notice = getAttachmentActionNotice(action)
@@ -54,45 +63,76 @@ export function PlaygroundInputTools({ disabled }: PlaygroundInputToolsProps) {
toast.info(t(notice.title))
}
return (
<PromptInputTools>
<DropdownMenu>
<DropdownMenuTrigger
render={
<PromptInputButton
className='border font-medium'
disabled={disabled}
variant='outline'
/>
}
>
<PaperclipIcon size={16} />
<span className='hidden sm:inline'>{t('Attach')}</span>
<span className='sr-only sm:hidden'>{t('Attach')}</span>
</DropdownMenuTrigger>
<DropdownMenuContent align='start'>
{ATTACHMENT_ACTIONS.map(({ action, icon: Icon, label }) => (
<DropdownMenuItem
key={action}
onClick={() => handleFileAction(action)}
>
<Icon className='mr-2' size={16} />
{t(label)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
const handleClearMessages = () => {
onClearMessages?.()
setClearConfirmOpen(false)
toast.success(t('Conversation cleared'))
}
<PromptInputButton
className='border font-medium'
disabled={disabled}
onClick={handleSearchAction}
variant='outline'
>
<GlobeIcon size={16} />
<span className='hidden sm:inline'>{t('Search')}</span>
<span className='sr-only sm:hidden'>{t('Search')}</span>
</PromptInputButton>
</PromptInputTools>
return (
<>
<PromptInputTools>
<DropdownMenu>
<DropdownMenuTrigger
render={
<PromptInputButton
className='border font-medium'
disabled={disabled}
variant='outline'
/>
}
>
<PaperclipIcon size={16} />
<span className='hidden sm:inline'>{t('Attach')}</span>
<span className='sr-only sm:hidden'>{t('Attach')}</span>
</DropdownMenuTrigger>
<DropdownMenuContent align='start'>
{ATTACHMENT_ACTIONS.map(({ action, icon: Icon, label }) => (
<DropdownMenuItem
key={action}
onClick={() => handleFileAction(action)}
>
<Icon className='mr-2' size={16} />
{t(label)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<PromptInputButton
className='border font-medium'
disabled={disabled}
onClick={handleSearchAction}
variant='outline'
>
<GlobeIcon size={16} />
<span className='hidden sm:inline'>{t('Search')}</span>
<span className='sr-only sm:hidden'>{t('Search')}</span>
</PromptInputButton>
<PromptInputButton
className='border font-medium text-muted-foreground hover:text-destructive'
disabled={disabled || !hasMessages || !onClearMessages}
onClick={() => setClearConfirmOpen(true)}
variant='outline'
>
<Trash2Icon size={16} />
<span className='hidden sm:inline'>{t('Clear chat history')}</span>
<span className='sr-only sm:hidden'>{t('Clear chat history')}</span>
</PromptInputButton>
</PromptInputTools>
<ConfirmDialog
destructive
desc={t(
'All playground messages saved in this browser will be removed. This cannot be undone.'
)}
confirmText={t('Clear')}
handleConfirm={handleClearMessages}
open={clearConfirmOpen}
onOpenChange={setClearConfirmOpen}
title={t('Clear chat history?')}
/>
</>
)
}
@@ -42,6 +42,8 @@ interface PlaygroundInputProps {
groups: GroupOption[]
groupValue: string
onGroupChange: (value: string) => void
hasMessages?: boolean
onClearMessages?: () => void
}
export function PlaygroundInput({
@@ -56,6 +58,8 @@ export function PlaygroundInput({
groups,
groupValue,
onGroupChange,
hasMessages = false,
onClearMessages,
}: PlaygroundInputProps) {
const { t } = useTranslation()
const [text, setText] = useState('')
@@ -96,7 +100,13 @@ export function PlaygroundInput({
onModelChange={onModelChange}
onStop={onStop}
text={text}
tools={<PlaygroundInputTools disabled={disabled} />}
tools={
<PlaygroundInputTools
disabled={disabled}
hasMessages={hasMessages}
onClearMessages={onClearMessages}
/>
}
/>
</PromptInputFooter>
</PromptInput>
+8
View File
@@ -36,6 +36,7 @@ export function Playground() {
setModels,
setGroups,
updateConfig,
clearMessages,
} = usePlaygroundState()
const { sendChat, stopGeneration, isGenerating } = useChatHandler({
@@ -58,6 +59,11 @@ export function Playground() {
sendChat,
})
const handleClearMessages = () => {
handleEditOpenChange(false)
clearMessages()
}
const { isLoadingModels } = usePlaygroundOptions({
currentGroup: config.group,
currentModel: config.model,
@@ -95,9 +101,11 @@ export function Playground() {
modelValue={config.model}
models={models}
onGroupChange={(value) => updateConfig('group', value)}
onClearMessages={handleClearMessages}
onModelChange={(value) => updateConfig('model', value)}
onStop={stopGeneration}
onSubmit={handleSendMessage}
hasMessages={messages.length > 0}
/>
</div>
</div>
+4
View File
@@ -253,6 +253,7 @@
"All conditions must match before this tier is used.": "All conditions must match before this tier is used.",
"All edits are overwrite operations. Leave fields empty to keep current values unchanged.": "All edits are overwrite operations. Leave fields empty to keep current values unchanged.",
"All files exceed the maximum size.": "All files exceed the maximum size.",
"All playground messages saved in this browser will be removed. This cannot be undone.": "All playground messages saved in this browser will be removed. This cannot be undone.",
"All Groups": "All Groups",
"All Models": "All Models",
"All models in use are properly configured.": "All models in use are properly configured.",
@@ -726,6 +727,8 @@
"Clear All Cache": "Clear All Cache",
"Clear all filters": "Clear all filters",
"Clear cache for this rule": "Clear cache for this rule",
"Clear chat history": "Clear chat history",
"Clear chat history?": "Clear chat history?",
"Clear filters": "Clear filters",
"Clear Mapping": "Clear Mapping",
"Clear mode flags in prompts": "Clear mode flags in prompts",
@@ -918,6 +921,7 @@
"Continue with Telegram": "Continue with Telegram",
"Continue with WeChat": "Continue with WeChat",
"Contract review, compliance, summarisation": "Contract review, compliance, summarisation",
"Conversation cleared": "Conversation cleared",
"Control which models are exposed and which groups may use them.": "Control which models are exposed and which groups may use them.",
"Controls how much the model thinks before answering": "Controls how much the model thinks before answering",
"Controls whether user verification (biometrics/PIN) is required during Passkey flows.": "Controls whether user verification (biometrics/PIN) is required during Passkey flows.",
+4
View File
@@ -253,6 +253,7 @@
"All conditions must match before this tier is used.": "Toutes les conditions doivent correspondre avant que ce palier soit utilisé.",
"All edits are overwrite operations. Leave fields empty to keep current values unchanged.": "Toutes les modifications sont des opérations d'écrasement. Laissez les champs vides pour conserver les valeurs actuelles inchangées.",
"All files exceed the maximum size.": "Tous les fichiers dépassent la taille maximale.",
"All playground messages saved in this browser will be removed. This cannot be undone.": "Tous les messages du Playground enregistrés dans ce navigateur seront supprimés. Cette action est irréversible.",
"All Groups": "Tous les groupes",
"All Models": "Tous les modèles",
"All models in use are properly configured.": "Tous les modèles utilisés sont correctement configurés.",
@@ -726,6 +727,8 @@
"Clear All Cache": "Vider tout le cache",
"Clear all filters": "Effacer tous les filtres",
"Clear cache for this rule": "Vider le cache de cette règle",
"Clear chat history": "Effacer l'historique du chat",
"Clear chat history?": "Effacer l'historique du chat ?",
"Clear filters": "Effacer les filtres",
"Clear Mapping": "Effacer le mappage",
"Clear mode flags in prompts": "Effacer les indicateurs de mode dans les prompts",
@@ -918,6 +921,7 @@
"Continue with Telegram": "Continuer avec Telegram",
"Continue with WeChat": "Continuer avec WeChat",
"Contract review, compliance, summarisation": "Revue de contrats, conformité, résumé",
"Conversation cleared": "Conversation effacée",
"Control which models are exposed and which groups may use them.": "Contrôlez les modèles exposés et les groupes autorisés à les utiliser.",
"Controls how much the model thinks before answering": "Contrôle la quantité de raisonnement avant la réponse",
"Controls whether user verification (biometrics/PIN) is required during Passkey flows.": "Contrôle si la vérification de l'utilisateur (biométrie/PIN) est requise lors des flux de Passkey.",
+4
View File
@@ -253,6 +253,7 @@
"All conditions must match before this tier is used.": "この段階を使用するには、すべての条件に一致する必要があります。",
"All edits are overwrite operations. Leave fields empty to keep current values unchanged.": "すべての編集は上書き操作です。現在の値を変更しないままにするには、フィールドを空のままにしてください。",
"All files exceed the maximum size.": "すべてのファイルが最大サイズを超えています。",
"All playground messages saved in this browser will be removed. This cannot be undone.": "このブラウザに保存されたすべての Playground メッセージが削除されます。この操作は元に戻せません。",
"All Groups": "すべてのグループ",
"All Models": "すべてのモデル",
"All models in use are properly configured.": "使用中のすべてのモデルが適切に構成されています。",
@@ -726,6 +727,8 @@
"Clear All Cache": "全キャッシュをクリア",
"Clear all filters": "すべてのフィルターをクリア",
"Clear cache for this rule": "このルールのキャッシュをクリア",
"Clear chat history": "チャット履歴を消去",
"Clear chat history?": "チャット履歴を消去しますか?",
"Clear filters": "フィルターをクリア",
"Clear Mapping": "マッピングをクリア",
"Clear mode flags in prompts": "プロンプト内のモードフラグをクリア",
@@ -918,6 +921,7 @@
"Continue with Telegram": "Telegram で続行",
"Continue with WeChat": "WeChat で続行",
"Contract review, compliance, summarisation": "契約レビュー・コンプライアンス・要約",
"Conversation cleared": "会話を消去しました",
"Control which models are exposed and which groups may use them.": "公開するモデルと、それらを利用できるグループを制御します。",
"Controls how much the model thinks before answering": "モデルが回答前に考える深さを制御します",
"Controls whether user verification (biometrics/PIN) is required during Passkey flows.": "Passkeyフロー中にユーザー認証(生体認証/PIN)が必要かどうかを制御します。",
+4
View File
@@ -253,6 +253,7 @@
"All conditions must match before this tier is used.": "Все условия должны совпасть, прежде чем будет использован этот уровень.",
"All edits are overwrite operations. Leave fields empty to keep current values unchanged.": "Все изменения являются операциями перезаписи. Оставьте поля пустыми, чтобы сохранить текущие значения без изменений.",
"All files exceed the maximum size.": "Все файлы превышают максимальный размер.",
"All playground messages saved in this browser will be removed. This cannot be undone.": "Все сообщения Playground, сохраненные в этом браузере, будут удалены. Это действие нельзя отменить.",
"All Groups": "Все группы",
"All Models": "Все модели",
"All models in use are properly configured.": "Все используемые модели настроены правильно.",
@@ -726,6 +727,8 @@
"Clear All Cache": "Очистить весь кэш",
"Clear all filters": "Очистить все фильтры",
"Clear cache for this rule": "Очистить кэш этого правила",
"Clear chat history": "Очистить историю чата",
"Clear chat history?": "Очистить историю чата?",
"Clear filters": "Очистить фильтры",
"Clear Mapping": "Очистить сопоставление",
"Clear mode flags in prompts": "Очистить флаги режимов в промптах",
@@ -918,6 +921,7 @@
"Continue with Telegram": "Продолжить с Telegram",
"Continue with WeChat": "Продолжить с WeChat",
"Contract review, compliance, summarisation": "Анализ контрактов, комплаенс, резюме",
"Conversation cleared": "Диалог очищен",
"Control which models are exposed and which groups may use them.": "Управляйте тем, какие модели доступны и какие группы могут их использовать.",
"Controls how much the model thinks before answering": "Регулирует глубину размышлений модели перед ответом",
"Controls whether user verification (biometrics/PIN) is required during Passkey flows.": "Определяет, требуется ли проверка пользователя (биометрия/PIN) во время процессов Passkey.",
+4
View File
@@ -253,6 +253,7 @@
"All conditions must match before this tier is used.": "Tất cả điều kiện phải khớp trước khi tầng này được sử dụng.",
"All edits are overwrite operations. Leave fields empty to keep current values unchanged.": "Tất cả các chỉnh sửa đều là thao tác ghi đè. Để trống các trường để giữ nguyên giá trị hiện tại.",
"All files exceed the maximum size.": "Tất cả các tệp vượt quá kích thước tối đa.",
"All playground messages saved in this browser will be removed. This cannot be undone.": "Tất cả tin nhắn Playground đã lưu trong trình duyệt này sẽ bị xóa. Không thể hoàn tác hành động này.",
"All Groups": "Tất cả các nhóm",
"All Models": "Tất cả các mẫu",
"All models in use are properly configured.": "Tất cả các mô hình đang được sử dụng đều được cấu hình đúng cách.",
@@ -726,6 +727,8 @@
"Clear All Cache": "Xóa toàn bộ bộ nhớ đệm",
"Clear all filters": "Xóa tất cả bộ lọc",
"Clear cache for this rule": "Xóa bộ nhớ đệm của quy tắc này",
"Clear chat history": "Xóa lịch sử trò chuyện",
"Clear chat history?": "Xóa lịch sử trò chuyện?",
"Clear filters": "Clear filter",
"Clear Mapping": "Xóa Ánh xạ",
"Clear mode flags in prompts": "Xóa các cờ chế độ trong lời nhắc",
@@ -918,6 +921,7 @@
"Continue with Telegram": "Tiếp tục với Telegram",
"Continue with WeChat": "Tiếp tục với WeChat",
"Contract review, compliance, summarisation": "Rà soát hợp đồng, tuân thủ, tóm tắt",
"Conversation cleared": "Đã xóa cuộc trò chuyện",
"Control which models are exposed and which groups may use them.": "Kiểm soát mô hình được hiển thị và nhóm nào có thể sử dụng chúng.",
"Controls how much the model thinks before answering": "Điều chỉnh mức suy luận trước khi trả lời",
"Controls whether user verification (biometrics/PIN) is required during Passkey flows.": "Kiểm soát xem liệu có yêu cầu xác minh người dùng (sinh trắc học/mã PIN) trong các luồng Passkey hay không.",
+4
View File
@@ -253,6 +253,7 @@
"All conditions must match before this tier is used.": "所有条件都匹配后才会使用此阶梯。",
"All edits are overwrite operations. Leave fields empty to keep current values unchanged.": "所有编辑都是覆盖操作。留空字段将保持当前值不变。",
"All files exceed the maximum size.": "所有文件都超过最大尺寸。",
"All playground messages saved in this browser will be removed. This cannot be undone.": "保存在此浏览器中的所有游乐场消息都将被移除。此操作无法撤销。",
"All Groups": "所有分组",
"All Models": "所有模型",
"All models in use are properly configured.": "所有正在使用的模型都已正确配置。",
@@ -726,6 +727,8 @@
"Clear All Cache": "清空全部缓存",
"Clear all filters": "清除所有筛选",
"Clear cache for this rule": "清空该规则缓存",
"Clear chat history": "清空聊天历史",
"Clear chat history?": "清空聊天历史?",
"Clear filters": "清除筛选器",
"Clear Mapping": "清除映射",
"Clear mode flags in prompts": "在提示中清除模式标志",
@@ -918,6 +921,7 @@
"Continue with Telegram": "使用 Telegram 继续",
"Continue with WeChat": "使用 微信 继续",
"Contract review, compliance, summarisation": "合同审阅、合规与摘要",
"Conversation cleared": "对话已清空",
"Control which models are exposed and which groups may use them.": "控制对外暴露的模型,以及哪些分组可以使用它们。",
"Controls how much the model thinks before answering": "控制模型回答前的推理深度",
"Controls whether user verification (biometrics/PIN) is required during Passkey flows.": "控制在通行密钥流程中是否需要用户验证(生物识别/PIN)。",