refactor(web): replace raw HTML with design system components
- Replace raw <label> with Label component in auth pages, profile settings, tool-ideas - Replace raw <button> with Button in CopyButton, CopyDropdown, ExecutorConfigPanel, PackageManagerSelector, ToolPlayground - Replace raw <textarea> with Textarea in ToolPlayground - Replace inline SVG with Icon component in error.tsx
This commit is contained in:
parent
144f07ea75
commit
0770d88815
10 changed files with 122 additions and 160 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Input } from '@tpmjs/ui/Input/Input';
|
||||
import { Label } from '@tpmjs/ui/Label/Label';
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
|
@ -63,16 +64,17 @@ export default function ForgotPasswordPage() {
|
|||
|
||||
<p className="text-center text-sm text-foreground-secondary">
|
||||
Didn't receive the email?{' '}
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSuccess(false);
|
||||
setEmail('');
|
||||
}}
|
||||
className="text-foreground hover:underline font-medium"
|
||||
className="p-0 h-auto text-foreground hover:underline font-medium"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</Button>
|
||||
</p>
|
||||
|
||||
<p className="text-center text-sm text-foreground-secondary">
|
||||
|
|
@ -101,9 +103,7 @@ export default function ForgotPasswordPage() {
|
|||
)}
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-foreground mb-1">
|
||||
Email
|
||||
</label>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import { Input } from '@tpmjs/ui/Input/Input';
|
||||
import { Label } from '@tpmjs/ui/Label/Label';
|
||||
import { suggestUsername } from '@tpmjs/types/user';
|
||||
import Link from 'next/link';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
|
@ -182,9 +183,7 @@ export default function SignUpPage() {
|
|||
)}
|
||||
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-foreground mb-1">
|
||||
Name
|
||||
</label>
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
type="text"
|
||||
|
|
@ -196,9 +195,7 @@ export default function SignUpPage() {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="username" className="block text-sm font-medium text-foreground mb-1">
|
||||
Username
|
||||
</label>
|
||||
<Label htmlFor="username">Username</Label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-foreground-secondary">
|
||||
@
|
||||
|
|
@ -245,9 +242,7 @@ export default function SignUpPage() {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-foreground mb-1">
|
||||
Email
|
||||
</label>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
|
|
@ -259,9 +254,7 @@ export default function SignUpPage() {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-foreground mb-1">
|
||||
Password
|
||||
</label>
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import { Input } from '@tpmjs/ui/Input/Input';
|
||||
import { Label } from '@tpmjs/ui/Label/Label';
|
||||
import Link from 'next/link';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { Suspense, useCallback, useEffect, useState } from 'react';
|
||||
|
|
@ -222,9 +223,7 @@ function ProfileSettingsContent({ isSetupMode }: { isSetupMode: boolean }): Reac
|
|||
|
||||
{/* Name field */}
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-foreground mb-1">
|
||||
Display Name
|
||||
</label>
|
||||
<Label htmlFor="name">Display Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
type="text"
|
||||
|
|
@ -240,9 +239,7 @@ function ProfileSettingsContent({ isSetupMode }: { isSetupMode: boolean }): Reac
|
|||
|
||||
{/* Username field */}
|
||||
<div>
|
||||
<label htmlFor="username" className="block text-sm font-medium text-foreground mb-1">
|
||||
Username
|
||||
</label>
|
||||
<Label htmlFor="username">Username</Label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-foreground-secondary z-10">
|
||||
@
|
||||
|
|
@ -292,9 +289,7 @@ function ProfileSettingsContent({ isSetupMode }: { isSetupMode: boolean }): Reac
|
|||
|
||||
{/* Email (read-only) */}
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-foreground mb-1">
|
||||
Email
|
||||
</label>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Container } from '@tpmjs/ui/Container/Container';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import Link from 'next/link';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
|
|
@ -25,23 +26,7 @@ export default function ErrorPage({
|
|||
{/* Error Icon */}
|
||||
<div className="mb-8">
|
||||
<div className="inline-flex items-center justify-center w-24 h-24 rounded-full bg-error/10 mb-4">
|
||||
<svg
|
||||
className="w-12 h-12 text-error"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="error-icon-title"
|
||||
>
|
||||
<title id="error-icon-title">Error warning icon</title>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
<Icon icon="alertTriangle" className="w-12 h-12 text-error" />
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-foreground mb-2">
|
||||
Something went wrong
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { Badge } from '@tpmjs/ui/Badge/Badge';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@tpmjs/ui/Card/Card';
|
||||
import { Input } from '@tpmjs/ui/Input/Input';
|
||||
import { Label } from '@tpmjs/ui/Label/Label';
|
||||
import { ProgressBar } from '@tpmjs/ui/ProgressBar/ProgressBar';
|
||||
import { Select } from '@tpmjs/ui/Select/Select';
|
||||
import { Spinner } from '@tpmjs/ui/Spinner/Spinner';
|
||||
|
|
@ -99,9 +100,9 @@ export function ToolIdeasClient() {
|
|||
}, [fetchTools]);
|
||||
|
||||
const qualityColor = (score: number) => {
|
||||
if (score >= 0.9) return 'text-green-600';
|
||||
if (score >= 0.7) return 'text-yellow-600';
|
||||
return 'text-red-600';
|
||||
if (score >= 0.9) return 'text-success';
|
||||
if (score >= 0.7) return 'text-warning';
|
||||
return 'text-error';
|
||||
};
|
||||
|
||||
const ToolCard = ({ tool }: { tool: ToolIdea }) => {
|
||||
|
|
@ -227,12 +228,7 @@ export function ToolIdeasClient() {
|
|||
{/* Filters */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3 bg-surface p-4 rounded-lg border border-border">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="tool-search"
|
||||
className="text-xs font-medium text-foreground-secondary mb-1.5 block"
|
||||
>
|
||||
Search
|
||||
</label>
|
||||
<Label htmlFor="tool-search" className="text-xs mb-1.5">Search</Label>
|
||||
<Input
|
||||
id="tool-search"
|
||||
type="text"
|
||||
|
|
@ -244,12 +240,7 @@ export function ToolIdeasClient() {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="tool-category"
|
||||
className="text-xs font-medium text-foreground-secondary mb-1.5 block"
|
||||
>
|
||||
Category
|
||||
</label>
|
||||
<Label htmlFor="tool-category" className="text-xs mb-1.5">Category</Label>
|
||||
<Select
|
||||
id="tool-category"
|
||||
value={category}
|
||||
|
|
@ -260,12 +251,7 @@ export function ToolIdeasClient() {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="tool-verb"
|
||||
className="text-xs font-medium text-foreground-secondary mb-1.5 block"
|
||||
>
|
||||
Verb
|
||||
</label>
|
||||
<Label htmlFor="tool-verb" className="text-xs mb-1.5">Verb</Label>
|
||||
<Select
|
||||
id="tool-verb"
|
||||
value={verb}
|
||||
|
|
@ -276,12 +262,7 @@ export function ToolIdeasClient() {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="tool-quality"
|
||||
className="text-xs font-medium text-foreground-secondary mb-1.5 block"
|
||||
>
|
||||
Min Quality
|
||||
</label>
|
||||
<Label htmlFor="tool-quality" className="text-xs mb-1.5">Min Quality</Label>
|
||||
<Select
|
||||
id="tool-quality"
|
||||
value={minQuality}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
|
@ -33,16 +34,19 @@ export function CopyButton({
|
|||
}, [text, successMessage]);
|
||||
|
||||
const iconSize = size === 'xs' ? 'xs' : size === 'sm' ? 'sm' : 'md';
|
||||
// Map xs to sm for Button since Button doesn't support xs size
|
||||
const buttonSize = size === 'xs' ? 'sm' : size;
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size={buttonSize}
|
||||
onClick={handleCopy}
|
||||
className={`inline-flex items-center gap-1.5 px-2 py-1 text-foreground-secondary hover:text-foreground hover:bg-surface rounded transition-colors ${className}`}
|
||||
className={`inline-flex items-center gap-1.5 text-foreground-secondary hover:text-foreground ${className}`}
|
||||
title={label || 'Copy to clipboard'}
|
||||
>
|
||||
<Icon icon={copied ? 'check' : 'copy'} size={iconSize} />
|
||||
{label && <span className="text-xs">{label}</span>}
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
|
@ -46,10 +47,11 @@ export function CopyDropdown({
|
|||
|
||||
return (
|
||||
<div className={`relative ${className}`} ref={dropdownRef}>
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="inline-flex items-center gap-1 px-2 py-1 text-xs text-foreground-secondary hover:text-foreground hover:bg-surface rounded transition-colors"
|
||||
className="inline-flex items-center gap-1 text-xs text-foreground-secondary hover:text-foreground"
|
||||
>
|
||||
<Icon icon="copy" size="xs" />
|
||||
<span>{buttonLabel}</span>
|
||||
|
|
@ -58,24 +60,26 @@ export function CopyDropdown({
|
|||
size="xs"
|
||||
className={`transition-transform ${isOpen ? 'rotate-180' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
{isOpen && (
|
||||
<div className="absolute right-0 top-full mt-1 w-56 bg-background border border-border rounded-lg shadow-lg py-1 z-50">
|
||||
{options.map((option) => (
|
||||
<button
|
||||
<Button
|
||||
key={option.label}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => handleCopy(option)}
|
||||
className="w-full px-3 py-2 text-left text-sm hover:bg-surface transition-colors"
|
||||
className="w-full px-3 py-2 h-auto text-left justify-start hover:bg-surface"
|
||||
>
|
||||
<div className="font-medium text-foreground">{option.label}</div>
|
||||
{option.description && (
|
||||
<div className="text-xs text-foreground-tertiary truncate">
|
||||
{option.description}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="font-medium text-foreground">{option.label}</div>
|
||||
{option.description && (
|
||||
<div className="text-xs text-foreground-tertiary truncate max-w-full">
|
||||
{option.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -142,67 +142,71 @@ export function ExecutorConfigPanel({
|
|||
|
||||
{/* Executor Type Selection */}
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => handleTypeChange('default')}
|
||||
disabled={disabled}
|
||||
className={`flex-1 p-3 rounded-lg border-2 text-left transition-colors ${
|
||||
className={`flex-1 p-3 h-auto rounded-lg border-2 text-left justify-start transition-colors ${
|
||||
executorType === 'default'
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-foreground-secondary'
|
||||
} ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`w-4 h-4 rounded-full border-2 ${
|
||||
executorType === 'default'
|
||||
? 'border-primary bg-primary'
|
||||
: 'border-foreground-tertiary'
|
||||
}`}
|
||||
>
|
||||
{executorType === 'default' && (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className="w-1.5 h-1.5 bg-white rounded-full" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`w-4 h-4 rounded-full border-2 ${
|
||||
executorType === 'default'
|
||||
? 'border-primary bg-primary'
|
||||
: 'border-foreground-tertiary'
|
||||
}`}
|
||||
>
|
||||
{executorType === 'default' && (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className="w-1.5 h-1.5 bg-white rounded-full" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="font-medium text-sm text-foreground">TPMJS Default</span>
|
||||
</div>
|
||||
<span className="font-medium text-sm text-foreground">TPMJS Default</span>
|
||||
<p className="text-xs text-foreground-tertiary mt-1 ml-6">
|
||||
Free, managed executor hosted by TPMJS
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs text-foreground-tertiary mt-1 ml-6">
|
||||
Free, managed executor hosted by TPMJS
|
||||
</p>
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => handleTypeChange('custom_url')}
|
||||
disabled={disabled}
|
||||
className={`flex-1 p-3 rounded-lg border-2 text-left transition-colors ${
|
||||
className={`flex-1 p-3 h-auto rounded-lg border-2 text-left justify-start transition-colors ${
|
||||
executorType === 'custom_url'
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-foreground-secondary'
|
||||
} ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`w-4 h-4 rounded-full border-2 ${
|
||||
executorType === 'custom_url'
|
||||
? 'border-primary bg-primary'
|
||||
: 'border-foreground-tertiary'
|
||||
}`}
|
||||
>
|
||||
{executorType === 'custom_url' && (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className="w-1.5 h-1.5 bg-white rounded-full" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`w-4 h-4 rounded-full border-2 ${
|
||||
executorType === 'custom_url'
|
||||
? 'border-primary bg-primary'
|
||||
: 'border-foreground-tertiary'
|
||||
}`}
|
||||
>
|
||||
{executorType === 'custom_url' && (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className="w-1.5 h-1.5 bg-white rounded-full" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="font-medium text-sm text-foreground">Custom Executor</span>
|
||||
</div>
|
||||
<span className="font-medium text-sm text-foreground">Custom Executor</span>
|
||||
<p className="text-xs text-foreground-tertiary mt-1 ml-6">
|
||||
Self-hosted executor on your infrastructure
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs text-foreground-tertiary mt-1 ml-6">
|
||||
Self-hosted executor on your infrastructure
|
||||
</p>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Custom URL Configuration */}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
export type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';
|
||||
|
|
@ -59,18 +60,19 @@ export function PackageManagerSelector({
|
|||
<span className="text-sm text-foreground-secondary mr-1">Package Manager:</span>
|
||||
<div className="inline-flex rounded-lg border border-border overflow-hidden">
|
||||
{packageManagers.map((pm) => (
|
||||
<button
|
||||
<Button
|
||||
key={pm.id}
|
||||
type="button"
|
||||
variant={selected === pm.id ? 'default' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => handleSelect(pm.id)}
|
||||
className={`px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||
className={`px-3 py-1.5 text-xs font-medium rounded-none ${
|
||||
selected === pm.id
|
||||
? 'bg-primary text-primary-foreground'
|
||||
? ''
|
||||
: 'bg-background text-foreground-secondary hover:bg-surface hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
{pm.label}
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@
|
|||
|
||||
import type { TokenBreakdown as TokenData } from '@/lib/ai-agent/tool-executor-agent';
|
||||
import type { Package, Tool } from '@tpmjs/db';
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Label } from '@tpmjs/ui/Label/Label';
|
||||
import { Spinner } from '@tpmjs/ui/Spinner/Spinner';
|
||||
import { Textarea } from '@tpmjs/ui/Textarea/Textarea';
|
||||
import { useState } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
|
@ -211,11 +214,11 @@ export function ToolPlayground({ tool }: ToolPlaygroundProps): React.ReactElemen
|
|||
<div className="border-b border-border bg-muted/10">
|
||||
<div className="flex space-x-1 px-6">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
<Button
|
||||
key={tab.id}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`px-4 py-3 text-sm font-medium transition-colors relative ${
|
||||
className={`px-4 py-3 rounded-none text-sm font-medium transition-colors relative ${
|
||||
activeTab === tab.id
|
||||
? 'text-foreground border-b-2 border-primary'
|
||||
: 'text-foreground-secondary hover:text-foreground'
|
||||
|
|
@ -227,7 +230,7 @@ export function ToolPlayground({ tool }: ToolPlaygroundProps): React.ReactElemen
|
|||
{tab.badge}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -237,15 +240,14 @@ export function ToolPlayground({ tool }: ToolPlaygroundProps): React.ReactElemen
|
|||
{activeTab === 'input' && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="prompt" className="block text-sm font-medium text-foreground mb-2">
|
||||
Prompt
|
||||
</label>
|
||||
<textarea
|
||||
<Label htmlFor="prompt" className="mb-2">Prompt</Label>
|
||||
<Textarea
|
||||
id="prompt"
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
placeholder="Enter your prompt here... (e.g., 'Create a blog post about TypeScript best practices')"
|
||||
className="w-full h-32 px-4 py-3 rounded-lg border border-input bg-white text-foreground placeholder:text-foreground-tertiary focus:outline-none focus:ring-2 focus:ring-ring resize-none"
|
||||
className="h-32"
|
||||
resize="none"
|
||||
disabled={isExecuting}
|
||||
/>
|
||||
<p className="text-xs text-foreground-tertiary mt-2">
|
||||
|
|
@ -253,21 +255,13 @@ export function ToolPlayground({ tool }: ToolPlaygroundProps): React.ReactElemen
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
onClick={handleExecute}
|
||||
disabled={isExecuting || !prompt.trim()}
|
||||
className="px-6 py-3 bg-primary text-primary-foreground rounded-lg font-medium hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
loading={isExecuting}
|
||||
>
|
||||
{isExecuting ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<Spinner size="sm" />
|
||||
Executing...
|
||||
</span>
|
||||
) : (
|
||||
'Execute'
|
||||
)}
|
||||
</button>
|
||||
{isExecuting ? 'Executing...' : 'Execute'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue