perf(playground): add starter empty state

- show starter prompts in the empty playground chat area
- wire empty-state prompt selection into the existing send flow
- add localized copy for the new empty state
This commit is contained in:
QuentinHsu
2026-05-30 18:32:11 +08:00
parent efc9c5844b
commit 4f03641ac7
9 changed files with 150 additions and 47 deletions
@@ -29,6 +29,7 @@ import {
} from '../lib'
import type { Message as MessageType } from '../types'
import { MessageActions } from './message-actions'
import { PlaygroundEmptyState } from './playground-empty-state'
import { PlaygroundMessageContent } from './playground-message-content'
import { PlaygroundMessageEditor } from './playground-message-editor'
@@ -38,6 +39,7 @@ interface PlaygroundChatProps {
onRegenerateMessage?: (message: MessageType) => void
onEditMessage?: (message: MessageType) => void
onDeleteMessage?: (message: MessageType) => void
onSelectPrompt?: (prompt: string) => void
isGenerating?: boolean
editingKey?: string | null
onSaveEdit?: (newContent: string) => void
@@ -51,6 +53,7 @@ export function PlaygroundChat({
onRegenerateMessage,
onEditMessage,
onDeleteMessage,
onSelectPrompt,
isGenerating = false,
editingKey,
onSaveEdit,
@@ -74,54 +77,58 @@ export function PlaygroundChat({
{/* Remove outer padding; apply padding to inner centered container to align with input */}
<ConversationContent className='p-0'>
<div className='mx-auto w-full max-w-4xl px-4 py-4'>
{messages.map((message, messageIndex) => {
const { alwaysShowActions, content, isEditing } =
getChatMessageRenderState(
messages,
message,
messageIndex,
editingKey
)
{messages.length === 0 && onSelectPrompt ? (
<PlaygroundEmptyState onSelectPrompt={onSelectPrompt} />
) : (
messages.map((message, messageIndex) => {
const { alwaysShowActions, content, isEditing } =
getChatMessageRenderState(
messages,
message,
messageIndex,
editingKey
)
return (
<Message
className='group flex-row-reverse'
from={message.from}
key={message.key}
>
<div className='w-full min-w-0 flex-1 basis-full py-1'>
{isEditing ? (
<PlaygroundMessageEditor
editText={editText}
message={message}
onCancelEdit={onCancelEdit}
onEditTextChange={setEditText}
onSaveEdit={onSaveEdit}
onSaveEditAndSubmit={onSaveEditAndSubmit}
originalText={originalText}
/>
) : (
<PlaygroundMessageContent
actions={
<MessageActions
message={message}
onCopy={onCopyMessage}
onRegenerate={onRegenerateMessage}
onEdit={onEditMessage}
onDelete={onDeleteMessage}
isGenerating={isGenerating}
alwaysVisible={alwaysShowActions}
className='mt-1'
/>
}
message={message}
versionContent={content}
/>
)}
</div>
</Message>
)
})}
return (
<Message
className='group flex-row-reverse'
from={message.from}
key={message.key}
>
<div className='w-full min-w-0 flex-1 basis-full py-1'>
{isEditing ? (
<PlaygroundMessageEditor
editText={editText}
message={message}
onCancelEdit={onCancelEdit}
onEditTextChange={setEditText}
onSaveEdit={onSaveEdit}
onSaveEditAndSubmit={onSaveEditAndSubmit}
originalText={originalText}
/>
) : (
<PlaygroundMessageContent
actions={
<MessageActions
message={message}
onCopy={onCopyMessage}
onRegenerate={onRegenerateMessage}
onEdit={onEditMessage}
onDelete={onDeleteMessage}
isGenerating={isGenerating}
alwaysVisible={alwaysShowActions}
className='mt-1'
/>
}
message={message}
versionContent={content}
/>
)}
</div>
</Message>
)
})
)}
</div>
</ConversationContent>
<ConversationScrollButton />
@@ -0,0 +1,83 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import {
BarChartIcon,
CodeSquareIcon,
GraduationCapIcon,
MessageSquarePlusIcon,
NotepadTextIcon,
} from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { Button } from '@/components/ui/button'
type PlaygroundEmptyStateProps = {
onSelectPrompt: (prompt: string) => void
}
const starterPrompts = [
{ icon: BarChartIcon, text: 'Analyze data' },
{ icon: NotepadTextIcon, text: 'Summarize text' },
{ icon: CodeSquareIcon, text: 'Code' },
{ icon: GraduationCapIcon, text: 'Get advice' },
]
export function PlaygroundEmptyState({
onSelectPrompt,
}: PlaygroundEmptyStateProps) {
const { t } = useTranslation()
return (
<div className='flex min-h-[min(520px,calc(100svh-18rem))] items-center justify-center px-1 py-8 md:py-12'>
<div className='grid w-full max-w-2xl gap-5 text-center'>
<div className='mx-auto flex size-11 items-center justify-center rounded-xl border bg-muted/50 text-muted-foreground'>
<MessageSquarePlusIcon className='size-5' aria-hidden='true' />
</div>
<div className='grid gap-2'>
<h2 className='text-balance text-xl font-semibold tracking-tight md:text-2xl'>
{t('Start a playground chat')}
</h2>
<p className='mx-auto max-w-lg text-balance text-sm leading-6 text-muted-foreground'>
{t(
'Test a model with a starter prompt, or write your own request below.'
)}
</p>
</div>
<div className='grid gap-2 sm:grid-cols-2'>
{starterPrompts.map(({ icon: Icon, text }) => {
const prompt = t(text)
return (
<Button
className='h-auto min-h-11 justify-start gap-2 whitespace-normal px-3 py-2.5 text-left'
key={text}
onClick={() => onSelectPrompt(prompt)}
variant='outline'
>
<Icon className='size-4 text-muted-foreground' />
<span>{prompt}</span>
</Button>
)
})}
</div>
</div>
</div>
)
}
+1
View File
@@ -75,6 +75,7 @@ export function Playground() {
onRegenerateMessage={handleRegenerateMessage}
onEditMessage={handleEditMessage}
onDeleteMessage={handleDeleteMessage}
onSelectPrompt={handleSendMessage}
isGenerating={isGenerating}
editingKey={editingMessageKey}
onCancelEdit={handleEditOpenChange}
+2
View File
@@ -3713,6 +3713,7 @@
"Standard": "Standard",
"Standard price": "Standard price",
"Start": "Start",
"Start a playground chat": "Start a playground chat",
"Start a conversation to see messages here": "Start a conversation to see messages here",
"Start collecting payments globally without registering a company. Built for indie developers, OPC sole proprietorships, and startups. Waffo Pancake acts as your Merchant of Record, taking on the compliance burden of global payment collection — consumption tax, invoicing, subscription management, refunds, and chargebacks. Solo developers can launch fast and stay focused on product instead of compliance. Onboard in minutes — one prompt to a full integration.": "Start collecting payments globally without registering a company. Built for indie developers, OPC sole proprietorships, and startups. Waffo Pancake acts as your Merchant of Record, taking on the compliance burden of global payment collection — consumption tax, invoicing, subscription management, refunds, and chargebacks. Solo developers can launch fast and stay focused on product instead of compliance. Onboard in minutes — one prompt to a full integration.",
"Start for free with generous limits. No credit card required.": "Start for free with generous limits. No credit card required.",
@@ -3889,6 +3890,7 @@
"Test Model": "Test Model",
"Test models and prompts from the browser": "Test models and prompts from the browser",
"Test selected models": "Test selected models",
"Test a model with a starter prompt, or write your own request below.": "Test a model with a starter prompt, or write your own request below.",
"Testing all enabled channels started. Please refresh to see results.": "Testing all enabled channels started. Please refresh to see results.",
"Testing...": "Testing...",
"Text": "Text",
+2
View File
@@ -3713,6 +3713,7 @@
"Standard": "Standard",
"Standard price": "Prix standard",
"Start": "Début",
"Start a playground chat": "Démarrer une conversation dans le playground",
"Start a conversation to see messages here": "Démarrez une conversation pour voir les messages ici",
"Start collecting payments globally without registering a company. Built for indie developers, OPC sole proprietorships, and startups. Waffo Pancake acts as your Merchant of Record, taking on the compliance burden of global payment collection — consumption tax, invoicing, subscription management, refunds, and chargebacks. Solo developers can launch fast and stay focused on product instead of compliance. Onboard in minutes — one prompt to a full integration.": "Commencez à encaisser des paiements dans le monde entier sans créer de société. Conçu pour les développeurs indépendants, les entrepreneurs individuels OPC et les startups. Waffo Pancake agit comme Merchant of Record et prend en charge la conformité liée à lencaissement mondial : taxes à la consommation, facturation, gestion des abonnements, remboursements et rétrofacturations. Les développeurs solo peuvent lancer rapidement leur produit et rester concentrés sur celui-ci plutôt que sur la conformité. Intégration en quelques minutes, dune seule invite à une intégration complète.",
"Start for free with generous limits. No credit card required.": "Commencez gratuitement avec des limites généreuses. Aucune carte de crédit requise.",
@@ -3889,6 +3890,7 @@
"Test Model": "Tester le modèle",
"Test models and prompts from the browser": "Tester les modèles et les prompts depuis le navigateur",
"Test selected models": "Tester les modèles sélectionnés",
"Test a model with a starter prompt, or write your own request below.": "Testez un modèle avec un prompt de départ, ou rédigez votre propre demande ci-dessous.",
"Testing all enabled channels started. Please refresh to see results.": "Test de tous les canaux activés démarré. Veuillez actualiser pour voir les résultats.",
"Testing...": "Test en cours...",
"Text": "Texte",
+2
View File
@@ -3713,6 +3713,7 @@
"Standard": "標準",
"Standard price": "標準価格",
"Start": "開始",
"Start a playground chat": "Playground でチャットを開始",
"Start a conversation to see messages here": "会話を開始すると、ここにメッセージが表示されます",
"Start collecting payments globally without registering a company. Built for indie developers, OPC sole proprietorships, and startups. Waffo Pancake acts as your Merchant of Record, taking on the compliance burden of global payment collection — consumption tax, invoicing, subscription management, refunds, and chargebacks. Solo developers can launch fast and stay focused on product instead of compliance. Onboard in minutes — one prompt to a full integration.": "法人を設立せずに世界中で決済を受け付けられます。個人開発者、OPC 個人事業主、スタートアップ向けに設計されています。Waffo Pancake は Merchant of Record として、消費税、請求書、サブスクリプション管理、返金、チャージバックなど、グローバル決済のコンプライアンス負担を引き受けます。個人開発者はコンプライアンスではなくプロダクトに集中しながら素早くローンチできます。数分でオンボーディングし、1 つのプロンプトから完全な統合まで進められます。",
"Start for free with generous limits. No credit card required.": "豊富な無料枠で始められます。クレジットカードは不要です。",
@@ -3889,6 +3890,7 @@
"Test Model": "モデルをテスト",
"Test models and prompts from the browser": "ブラウザでモデルとプロンプトをテスト",
"Test selected models": "選択したモデルをテスト",
"Test a model with a starter prompt, or write your own request below.": "スタータープロンプトでモデルをテストするか、下に独自のリクエストを入力してください。",
"Testing all enabled channels started. Please refresh to see results.": "有効な全チャネルのテストを開始しました。結果を確認するにはページを更新してください。",
"Testing...": "テスト中...",
"Text": "テキスト",
+2
View File
@@ -3713,6 +3713,7 @@
"Standard": "Стандартный",
"Standard price": "Стандартная цена",
"Start": "Начало",
"Start a playground chat": "Начните чат в Playground",
"Start a conversation to see messages here": "Начните разговор, чтобы увидеть сообщения здесь",
"Start collecting payments globally without registering a company. Built for indie developers, OPC sole proprietorships, and startups. Waffo Pancake acts as your Merchant of Record, taking on the compliance burden of global payment collection — consumption tax, invoicing, subscription management, refunds, and chargebacks. Solo developers can launch fast and stay focused on product instead of compliance. Onboard in minutes — one prompt to a full integration.": "Начните принимать платежи по всему миру без регистрации компании. Подходит для независимых разработчиков, индивидуальных предпринимателей OPC и стартапов. Waffo Pancake выступает как Merchant of Record и берет на себя комплаенс глобального приема платежей: потребительские налоги, выставление счетов, управление подписками, возвраты и чарджбеки. Одиночные разработчики могут быстро запуститься и сосредоточиться на продукте, а не на комплаенсе. Подключение за минуты — от одного запроса до полной интеграции.",
"Start for free with generous limits. No credit card required.": "Начните бесплатно с щедрыми лимитами. Кредитная карта не требуется.",
@@ -3889,6 +3890,7 @@
"Test Model": "Проверить модель",
"Test models and prompts from the browser": "Тестируйте модели и промпты в браузере",
"Test selected models": "Проверить выбранные модели",
"Test a model with a starter prompt, or write your own request below.": "Проверьте модель с начальным промптом или напишите собственный запрос ниже.",
"Testing all enabled channels started. Please refresh to see results.": "Тестирование всех включенных каналов начато. Пожалуйста, обновите страницу, чтобы увидеть результаты.",
"Testing...": "Тестирование...",
"Text": "Текст",
+2
View File
@@ -3713,6 +3713,7 @@
"Standard": "Tiêu chuẩn",
"Standard price": "Giá tiêu chuẩn",
"Start": "Bắt đầu",
"Start a playground chat": "Bắt đầu cuộc trò chuyện trong playground",
"Start a conversation to see messages here": "Bắt đầu một cuộc trò chuyện để xem tin nhắn tại đây",
"Start collecting payments globally without registering a company. Built for indie developers, OPC sole proprietorships, and startups. Waffo Pancake acts as your Merchant of Record, taking on the compliance burden of global payment collection — consumption tax, invoicing, subscription management, refunds, and chargebacks. Solo developers can launch fast and stay focused on product instead of compliance. Onboard in minutes — one prompt to a full integration.": "Bắt đầu thu thanh toán toàn cầu mà không cần đăng ký công ty. Dành cho lập trình viên độc lập, chủ sở hữu OPC và startup. Waffo Pancake đóng vai trò Merchant of Record, chịu trách nhiệm tuân thủ cho việc thu thanh toán toàn cầu — thuế tiêu dùng, hóa đơn, quản lý đăng ký, hoàn tiền và tranh chấp thanh toán. Lập trình viên cá nhân có thể ra mắt nhanh và tập trung vào sản phẩm thay vì tuân thủ. Onboard trong vài phút — từ một prompt đến tích hợp hoàn chỉnh.",
"Start for free with generous limits. No credit card required.": "Bắt đầu miễn phí với giới hạn hào phóng. Không cần thẻ tín dụng.",
@@ -3889,6 +3890,7 @@
"Test Model": "Kiểm tra Mô hình",
"Test models and prompts from the browser": "Kiểm thử mô hình và prompt trong trình duyệt",
"Test selected models": "Kiểm tra các mô hình đã chọn",
"Test a model with a starter prompt, or write your own request below.": "Kiểm thử mô hình bằng prompt gợi ý, hoặc viết yêu cầu của riêng bạn bên dưới.",
"Testing all enabled channels started. Please refresh to see results.": "Bắt đầu kiểm tra tất cả các kênh đã kích hoạt. Vui lòng làm mới để xem kết quả.",
"Testing...": "Đang kiểm tra...",
"Text": "Văn bản",
+2
View File
@@ -3713,6 +3713,7 @@
"Standard": "标准",
"Standard price": "标准价格",
"Start": "开始",
"Start a playground chat": "开始一场游乐场对话",
"Start a conversation to see messages here": "开始对话以在此处查看消息",
"Start collecting payments globally without registering a company. Built for indie developers, OPC sole proprietorships, and startups. Waffo Pancake acts as your Merchant of Record, taking on the compliance burden of global payment collection — consumption tax, invoicing, subscription management, refunds, and chargebacks. Solo developers can launch fast and stay focused on product instead of compliance. Onboard in minutes — one prompt to a full integration.": "无需注册公司即可开始全球收款。面向独立开发者、OPC 个体经营者和初创团队构建。Waffo Pancake 作为你的登记商户(Merchant of Record),承担全球收款相关的合规负担,包括消费税、开票、订阅管理、退款和拒付。个人开发者可以快速上线,专注产品而不是合规事务。几分钟即可完成入驻,从一个提示词到完整集成。",
"Start for free with generous limits. No credit card required.": "免费开始使用,额度充足,无需绑定信用卡。",
@@ -3889,6 +3890,7 @@
"Test Model": "测试模型",
"Test models and prompts from the browser": "在浏览器中测试模型和提示词",
"Test selected models": "测试所选模型",
"Test a model with a starter prompt, or write your own request below.": "使用入门提示词测试模型,或在下方编写自己的请求。",
"Testing all enabled channels started. Please refresh to see results.": "测试所有启用的通道已开始。请刷新以查看结果。",
"Testing...": "测试中...",
"Text": "文本",