refactor: replace raw HTML elements with design system components
- Replace <select>, <input>, <label>, <textarea> with UI components - Update various dashboard and docs pages - Simplify unsandbox package.json - Add DESIGN_SYSTEM.md documentation - Add Claude skills configuration
This commit is contained in:
parent
7d9321d9c6
commit
23d5159b28
26 changed files with 1280 additions and 335 deletions
2
apps/web/next-env.d.ts
vendored
2
apps/web/next-env.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
|
|
|||
|
|
@ -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 { useState } from 'react';
|
||||
import { signIn } from '~/lib/auth-client';
|
||||
|
|
@ -77,9 +78,7 @@ export default function SignInPage() {
|
|||
)}
|
||||
|
||||
<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"
|
||||
|
|
@ -92,9 +91,7 @@ export default function SignInPage() {
|
|||
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<label htmlFor="password" className="block text-sm font-medium text-foreground">
|
||||
Password
|
||||
</label>
|
||||
<Label htmlFor="password" className="mb-0">Password</Label>
|
||||
<Link
|
||||
href="/forgot-password"
|
||||
className="text-sm text-foreground-secondary hover:text-foreground hover:underline"
|
||||
|
|
@ -112,14 +109,16 @@ export default function SignInPage() {
|
|||
placeholder="Enter your password"
|
||||
className="pr-10"
|
||||
/>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-foreground-tertiary hover:text-foreground transition-colors"
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 h-8 w-8 p-0"
|
||||
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
||||
>
|
||||
<Icon icon={showPassword ? 'eyeOff' : 'eye'} size="sm" />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ export default function PrettyAgentDetailPage(): React.ReactElement {
|
|||
</div>
|
||||
) : error ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-red-500">{error}</p>
|
||||
<p className="text-error">{error}</p>
|
||||
</div>
|
||||
) : agent ? (
|
||||
<div className="space-y-8">
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ export default function PrettyCollectionDetailPage(): React.ReactElement {
|
|||
</div>
|
||||
) : error ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-red-500">{error}</p>
|
||||
<p className="text-error">{error}</p>
|
||||
</div>
|
||||
) : collection ? (
|
||||
<div className="space-y-8">
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export default function UserProfilePage(): React.ReactElement {
|
|||
</div>
|
||||
) : error ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-red-500">{error}</p>
|
||||
<p className="text-error">{error}</p>
|
||||
</div>
|
||||
) : profile ? (
|
||||
<div className="space-y-8">
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type { AIProvider } from '@tpmjs/types/agent';
|
|||
import { Badge } from '@tpmjs/ui/Badge/Badge';
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import { Textarea } from '@tpmjs/ui/Textarea/Textarea';
|
||||
import Link from 'next/link';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
|
@ -102,10 +103,10 @@ function ToolCallCard({
|
|||
onToggle: () => void;
|
||||
}) {
|
||||
const statusColors = {
|
||||
pending: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
|
||||
running: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
|
||||
success: 'bg-green-500/20 text-green-400 border-green-500/30',
|
||||
error: 'bg-red-500/20 text-red-400 border-red-500/30',
|
||||
pending: 'bg-warning/10 text-warning border-warning/30',
|
||||
running: 'bg-info/10 text-info border-info/30',
|
||||
success: 'bg-success/10 text-success border-success/30',
|
||||
error: 'bg-error/10 text-error border-error/30',
|
||||
};
|
||||
|
||||
const statusIcons: Record<ToolCall['status'], 'loader' | 'check' | 'alertCircle' | 'info'> = {
|
||||
|
|
@ -126,10 +127,10 @@ function ToolCallCard({
|
|||
return (
|
||||
<div className="rounded-lg border border-border bg-surface-secondary/50 overflow-hidden font-mono text-xs">
|
||||
{/* Header */}
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onToggle}
|
||||
className="w-full flex items-center gap-3 p-3 hover:bg-surface-secondary/80 transition-colors"
|
||||
className="w-full flex items-center gap-3 p-3 h-auto justify-start rounded-none hover:bg-surface-secondary/80"
|
||||
>
|
||||
<div className={`p-1.5 rounded ${statusColors[toolCall.status]}`}>
|
||||
<Icon
|
||||
|
|
@ -151,7 +152,7 @@ function ToolCallCard({
|
|||
size="xs"
|
||||
className={`text-foreground-tertiary transition-transform ${isExpanded ? 'rotate-90' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
{/* Expanded Content */}
|
||||
{isExpanded && (
|
||||
|
|
@ -180,7 +181,7 @@ function ToolCallCard({
|
|||
</span>
|
||||
<div className="flex-1 h-px bg-border/50" />
|
||||
</div>
|
||||
<pre className="text-[11px] text-green-400 overflow-x-auto whitespace-pre-wrap break-all max-h-48 overflow-y-auto">
|
||||
<pre className="text-[11px] text-success overflow-x-auto whitespace-pre-wrap break-all max-h-48 overflow-y-auto">
|
||||
{formatJson(toolCall.output)}
|
||||
</pre>
|
||||
</div>
|
||||
|
|
@ -661,14 +662,14 @@ export default function PublicAgentChatPage(): React.ReactElement {
|
|||
<div className="border-b border-border bg-surface/50 px-4 py-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||
className="p-2 hover:bg-surface-secondary rounded-lg transition-colors"
|
||||
title={sidebarOpen ? 'Hide sidebar' : 'Show sidebar'}
|
||||
>
|
||||
<Icon icon="menu" size="sm" />
|
||||
</button>
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold text-foreground">{agent.name}</h1>
|
||||
<p className="text-sm text-foreground-tertiary">
|
||||
|
|
@ -683,28 +684,20 @@ export default function PublicAgentChatPage(): React.ReactElement {
|
|||
</div>
|
||||
{/* View Mode Tabs */}
|
||||
<div className="flex gap-1 mt-3">
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant={viewMode === 'chat' ? 'default' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => setViewMode('chat')}
|
||||
className={`px-3 py-1.5 text-sm rounded-md transition-colors ${
|
||||
viewMode === 'chat'
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-foreground-secondary hover:text-foreground hover:bg-surface-secondary'
|
||||
}`}
|
||||
>
|
||||
Chat
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</Button>
|
||||
<Button
|
||||
variant={viewMode === 'debug' ? 'default' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => setViewMode('debug')}
|
||||
className={`px-3 py-1.5 text-sm rounded-md transition-colors ${
|
||||
viewMode === 'debug'
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-foreground-secondary hover:text-foreground hover:bg-surface-secondary'
|
||||
}`}
|
||||
>
|
||||
Debug JSON
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -776,13 +769,14 @@ export default function PublicAgentChatPage(): React.ReactElement {
|
|||
<span className="text-sm">Loading older messages...</span>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={loadMoreMessages}
|
||||
className="text-sm text-primary hover:text-primary/80 transition-colors"
|
||||
className="text-primary hover:text-primary/80"
|
||||
>
|
||||
Load older messages
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : null,
|
||||
|
|
@ -950,22 +944,23 @@ export default function PublicAgentChatPage(): React.ReactElement {
|
|||
|
||||
{/* Error Message */}
|
||||
{error && (
|
||||
<div className="px-4 py-2 bg-red-50 dark:bg-red-900/20 border-t border-red-200 dark:border-red-800">
|
||||
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
|
||||
<div className="px-4 py-2 bg-error/10 border-t border-error/20">
|
||||
<p className="text-sm text-error">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Input Area */}
|
||||
<div className="border-t border-border p-4">
|
||||
<div className="flex items-end gap-2">
|
||||
<textarea
|
||||
<Textarea
|
||||
ref={inputRef}
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Type a message..."
|
||||
rows={1}
|
||||
className="flex-1 px-4 py-3 bg-background border border-border rounded-lg text-foreground placeholder:text-foreground-tertiary focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary resize-none min-h-[48px] max-h-[200px]"
|
||||
resize="none"
|
||||
className="flex-1 min-h-[48px] max-h-[200px]"
|
||||
style={{
|
||||
height: 'auto',
|
||||
minHeight: '48px',
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import type { AIProvider } from '@tpmjs/types/agent';
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import { Textarea } from '@tpmjs/ui/Textarea/Textarea';
|
||||
import Link from 'next/link';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
|
@ -87,14 +88,10 @@ function ToolCallCard({
|
|||
const effectiveStatus = hasError ? 'error' : toolCall.status;
|
||||
|
||||
const statusColors = {
|
||||
pending:
|
||||
'bg-yellow-100 dark:bg-yellow-500/20 text-yellow-700 dark:text-yellow-400 border-yellow-300 dark:border-yellow-500/30',
|
||||
running:
|
||||
'bg-blue-100 dark:bg-blue-500/20 text-blue-700 dark:text-blue-400 border-blue-300 dark:border-blue-500/30',
|
||||
success:
|
||||
'bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-400 border-green-300 dark:border-green-500/30',
|
||||
error:
|
||||
'bg-red-100 dark:bg-red-500/20 text-red-700 dark:text-red-400 border-red-300 dark:border-red-500/30',
|
||||
pending: 'bg-warning/10 text-warning border-warning/30',
|
||||
running: 'bg-info/10 text-info border-info/30',
|
||||
success: 'bg-success/10 text-success border-success/30',
|
||||
error: 'bg-error/10 text-error border-error/30',
|
||||
};
|
||||
|
||||
const statusIcons: Record<ToolCall['status'], 'loader' | 'check' | 'alertCircle' | 'info'> = {
|
||||
|
|
@ -114,13 +111,13 @@ function ToolCallCard({
|
|||
|
||||
return (
|
||||
<div
|
||||
className={`rounded-lg border overflow-hidden font-mono text-xs ${hasError ? 'border-red-500/50 bg-red-50 dark:bg-red-500/5' : 'border-border bg-slate-50 dark:bg-surface-secondary/50'}`}
|
||||
className={`rounded-lg border overflow-hidden font-mono text-xs ${hasError ? 'border-error/50 bg-error/5' : 'border-border bg-surface-secondary/50'}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onToggle}
|
||||
className="w-full flex items-center gap-3 p-3 hover:bg-slate-100 dark:hover:bg-surface-secondary/80 transition-colors"
|
||||
className="w-full flex items-center gap-3 p-3 h-auto justify-start rounded-none hover:bg-surface-secondary/80"
|
||||
>
|
||||
<div className={`p-1.5 rounded ${statusColors[effectiveStatus]}`}>
|
||||
<Icon
|
||||
|
|
@ -136,14 +133,14 @@ function ToolCallCard({
|
|||
{toolCall.toolCallId.slice(0, 8)}...
|
||||
</span>
|
||||
{hasError && (
|
||||
<span className="text-[10px] px-1.5 py-0.5 rounded bg-red-100 dark:bg-red-500/20 text-red-600 dark:text-red-400">
|
||||
<span className="text-[10px] px-1.5 py-0.5 rounded bg-error/10 text-error">
|
||||
ERROR
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Show error message preview in header */}
|
||||
{hasError && errorMessage && !isExpanded && (
|
||||
<div className="text-red-600 dark:text-red-400 text-[10px] mt-1 truncate max-w-[300px]">
|
||||
<div className="text-error text-[10px] mt-1 truncate max-w-[300px]">
|
||||
{errorMessage}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -153,7 +150,7 @@ function ToolCallCard({
|
|||
size="xs"
|
||||
className={`text-foreground-tertiary transition-transform ${isExpanded ? 'rotate-90' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
{/* Expanded Content */}
|
||||
{isExpanded && (
|
||||
|
|
@ -175,14 +172,14 @@ function ToolCallCard({
|
|||
|
||||
{/* Error Message Section */}
|
||||
{hasError && errorMessage && (
|
||||
<div className="p-3 bg-red-100 dark:bg-red-500/10 border-b border-red-200 dark:border-red-500/20">
|
||||
<div className="p-3 bg-error/10 border-b border-error/20">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Icon icon="alertCircle" size="xs" className="text-red-600 dark:text-red-400" />
|
||||
<span className="text-[10px] uppercase tracking-wider text-red-600 dark:text-red-400 font-semibold">
|
||||
<Icon icon="alertCircle" size="xs" className="text-error" />
|
||||
<span className="text-[10px] uppercase tracking-wider text-error font-semibold">
|
||||
Error
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-[11px] text-red-600 dark:text-red-400">{errorMessage}</p>
|
||||
<p className="text-[11px] text-error">{errorMessage}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -196,7 +193,7 @@ function ToolCallCard({
|
|||
<div className="flex-1 h-px bg-border/50" />
|
||||
</div>
|
||||
<pre
|
||||
className={`text-[11px] overflow-x-auto whitespace-pre-wrap break-all max-h-48 overflow-y-auto ${hasError ? 'text-red-600 dark:text-red-300' : 'text-emerald-700 dark:text-green-400'}`}
|
||||
className={`text-[11px] overflow-x-auto whitespace-pre-wrap break-all max-h-48 overflow-y-auto ${hasError ? 'text-error' : 'text-success'}`}
|
||||
>
|
||||
{formatJson(toolCall.output)}
|
||||
</pre>
|
||||
|
|
@ -573,19 +570,21 @@ export default function AgentChatPage(): React.ReactElement {
|
|||
</p>
|
||||
) : (
|
||||
conversations.map((conv) => (
|
||||
<button
|
||||
<Button
|
||||
key={conv.id}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => handleSelectConversation(conv.slug)}
|
||||
className={`w-full text-left px-3 py-2 rounded-lg mb-1 transition-colors ${
|
||||
className={`w-full text-left h-auto py-2 px-3 justify-start mb-1 ${
|
||||
chatId === conv.slug
|
||||
? 'bg-primary/10 text-primary'
|
||||
: 'text-foreground-secondary hover:bg-surface-secondary'
|
||||
}`}
|
||||
>
|
||||
<p className="text-sm font-medium truncate">{conv.title || 'Untitled Chat'}</p>
|
||||
<p className="text-xs text-foreground-tertiary">{conv.messageCount} messages</p>
|
||||
</button>
|
||||
<div className="flex flex-col items-start">
|
||||
<p className="text-sm font-medium truncate">{conv.title || 'Untitled Chat'}</p>
|
||||
<p className="text-xs text-foreground-tertiary">{conv.messageCount} messages</p>
|
||||
</div>
|
||||
</Button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -738,22 +737,23 @@ export default function AgentChatPage(): React.ReactElement {
|
|||
|
||||
{/* Error Message */}
|
||||
{error && (
|
||||
<div className="px-4 py-2 bg-red-50 dark:bg-red-900/20 border-t border-red-200 dark:border-red-800">
|
||||
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
|
||||
<div className="px-4 py-2 bg-error/10 border-t border-error/20">
|
||||
<p className="text-sm text-error">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Input Area */}
|
||||
<div className="border-t border-border p-4">
|
||||
<div className="flex items-end gap-2">
|
||||
<textarea
|
||||
<Textarea
|
||||
ref={inputRef}
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Type a message..."
|
||||
rows={1}
|
||||
className="flex-1 px-4 py-3 bg-surface border border-border rounded-lg text-foreground placeholder:text-foreground-tertiary focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary resize-none min-h-[48px] max-h-[200px]"
|
||||
resize="none"
|
||||
className="flex-1 min-h-[48px] max-h-[200px]"
|
||||
style={{
|
||||
height: 'auto',
|
||||
minHeight: '48px',
|
||||
|
|
|
|||
|
|
@ -7,8 +7,12 @@ import { Badge } from '@tpmjs/ui/Badge/Badge';
|
|||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import { Input } from '@tpmjs/ui/Input/Input';
|
||||
import { Label } from '@tpmjs/ui/Label/Label';
|
||||
import { Select } from '@tpmjs/ui/Select/Select';
|
||||
import { Spinner } from '@tpmjs/ui/Spinner/Spinner';
|
||||
import { Switch } from '@tpmjs/ui/Switch/Switch';
|
||||
import { Textarea } from '@tpmjs/ui/Textarea/Textarea';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
|
@ -259,17 +263,12 @@ conv = resp.json() # conv['data']['messages']`,
|
|||
onTabChange={setActiveSection}
|
||||
size="sm"
|
||||
/>
|
||||
<select
|
||||
<Select
|
||||
value={effectiveLang}
|
||||
onChange={(e) => setActiveLang(e.target.value)}
|
||||
className="px-2 py-1 text-sm bg-surface border border-border rounded-md text-foreground focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
>
|
||||
{langOptions.map((opt) => (
|
||||
<option key={opt.id} value={opt.id}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
size="sm"
|
||||
options={langOptions.map((opt) => ({ value: opt.id, label: opt.label }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-4">
|
||||
|
|
@ -1067,121 +1066,82 @@ export default function AgentDetailPage(): React.ReactElement {
|
|||
<div className="space-y-4">
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-foreground mb-1">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 bg-surface border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="uid" className="block text-sm font-medium text-foreground mb-1">
|
||||
UID
|
||||
</label>
|
||||
<input
|
||||
<Label htmlFor="uid">UID</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="uid"
|
||||
name="uid"
|
||||
value={formData.uid}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 bg-surface border border-border rounded-lg text-foreground font-mono text-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="description"
|
||||
className="block text-sm font-medium text-foreground mb-1"
|
||||
>
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
name="description"
|
||||
value={formData.description}
|
||||
onChange={handleChange}
|
||||
rows={2}
|
||||
className="w-full px-3 py-2 bg-surface border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary resize-none"
|
||||
resize="none"
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="provider"
|
||||
className="block text-sm font-medium text-foreground mb-1"
|
||||
>
|
||||
Provider
|
||||
</label>
|
||||
<select
|
||||
<Label htmlFor="provider">Provider</Label>
|
||||
<Select
|
||||
id="provider"
|
||||
name="provider"
|
||||
value={formData.provider}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 bg-surface border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
||||
>
|
||||
{SUPPORTED_PROVIDERS.map((p) => (
|
||||
<option key={p} value={p}>
|
||||
{PROVIDER_DISPLAY_NAMES[p]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
options={SUPPORTED_PROVIDERS.map((p) => ({
|
||||
value: p,
|
||||
label: PROVIDER_DISPLAY_NAMES[p],
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="modelId"
|
||||
className="block text-sm font-medium text-foreground mb-1"
|
||||
>
|
||||
Model
|
||||
</label>
|
||||
<select
|
||||
<Label htmlFor="modelId">Model</Label>
|
||||
<Select
|
||||
id="modelId"
|
||||
name="modelId"
|
||||
value={formData.modelId}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 bg-surface border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
||||
>
|
||||
{models.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
options={models.map((m) => ({ value: m.id, label: m.name }))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="systemPrompt"
|
||||
className="block text-sm font-medium text-foreground mb-1"
|
||||
>
|
||||
System Prompt
|
||||
</label>
|
||||
<textarea
|
||||
<Label htmlFor="systemPrompt">System Prompt</Label>
|
||||
<Textarea
|
||||
id="systemPrompt"
|
||||
name="systemPrompt"
|
||||
value={formData.systemPrompt}
|
||||
onChange={handleChange}
|
||||
rows={6}
|
||||
className="w-full px-3 py-2 bg-surface border border-border rounded-lg text-foreground font-mono text-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary resize-none"
|
||||
resize="none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-3">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="temperature"
|
||||
className="block text-sm font-medium text-foreground mb-1"
|
||||
>
|
||||
Temperature
|
||||
</label>
|
||||
<input
|
||||
<Label htmlFor="temperature">Temperature</Label>
|
||||
<Input
|
||||
type="number"
|
||||
id="temperature"
|
||||
name="temperature"
|
||||
|
|
@ -1190,17 +1150,11 @@ export default function AgentDetailPage(): React.ReactElement {
|
|||
min={0}
|
||||
max={2}
|
||||
step={0.1}
|
||||
className="w-full px-3 py-2 bg-surface border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="maxToolCallsPerTurn"
|
||||
className="block text-sm font-medium text-foreground mb-1"
|
||||
>
|
||||
Max Tool Calls
|
||||
</label>
|
||||
<input
|
||||
<Label htmlFor="maxToolCallsPerTurn">Max Tool Calls</Label>
|
||||
<Input
|
||||
type="number"
|
||||
id="maxToolCallsPerTurn"
|
||||
name="maxToolCallsPerTurn"
|
||||
|
|
@ -1208,17 +1162,11 @@ export default function AgentDetailPage(): React.ReactElement {
|
|||
onChange={handleChange}
|
||||
min={1}
|
||||
max={100}
|
||||
className="w-full px-3 py-2 bg-surface border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="maxMessagesInContext"
|
||||
className="block text-sm font-medium text-foreground mb-1"
|
||||
>
|
||||
Context Messages
|
||||
</label>
|
||||
<input
|
||||
<Label htmlFor="maxMessagesInContext">Context Messages</Label>
|
||||
<Input
|
||||
type="number"
|
||||
id="maxMessagesInContext"
|
||||
name="maxMessagesInContext"
|
||||
|
|
@ -1226,7 +1174,6 @@ export default function AgentDetailPage(): React.ReactElement {
|
|||
onChange={handleChange}
|
||||
min={1}
|
||||
max={100}
|
||||
className="w-full px-3 py-2 bg-surface border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { Badge } from '@tpmjs/ui/Badge/Badge';
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import { Input } from '@tpmjs/ui/Input/Input';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
|
@ -277,16 +278,13 @@ export default function TpmjsApiKeysPage(): React.ReactElement {
|
|||
<div className="bg-surface border border-border rounded-lg p-6 mb-6">
|
||||
<h2 className="text-lg font-medium text-foreground mb-4">Create API Key</h2>
|
||||
{createError && <p className="text-error text-sm mb-3">{createError}</p>}
|
||||
<div className="space-y-3">
|
||||
<input
|
||||
type="text"
|
||||
value={newKeyName}
|
||||
onChange={(e) => setNewKeyName(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
|
||||
placeholder="Key name (e.g., Production Server, CI/CD)"
|
||||
className="w-full px-3 py-2 bg-surface border border-border rounded-lg text-foreground text-sm placeholder:text-foreground-tertiary focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
type="text"
|
||||
value={newKeyName}
|
||||
onChange={(e) => setNewKeyName(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
|
||||
placeholder="Key name (e.g., Production Server, CI/CD)"
|
||||
/>
|
||||
<div className="flex items-center gap-2 mt-4">
|
||||
<Button onClick={handleCreate} disabled={creating || !newKeyName.trim()}>
|
||||
{creating ? 'Creating...' : 'Create Key'}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { Badge } from '@tpmjs/ui/Badge/Badge';
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import { Select } from '@tpmjs/ui/Select/Select';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { DashboardLayout } from '~/components/dashboard/DashboardLayout';
|
||||
|
|
@ -107,17 +108,16 @@ export default function UsagePage(): React.ReactElement {
|
|||
title="Usage"
|
||||
subtitle="Monitor your API usage and costs"
|
||||
actions={
|
||||
<div className="flex items-center gap-2">
|
||||
<select
|
||||
value={period}
|
||||
onChange={(e) => setPeriod(e.target.value as Period)}
|
||||
className="px-3 py-1.5 bg-surface border border-border rounded-lg text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
>
|
||||
<option value="hourly">Hourly</option>
|
||||
<option value="daily">Daily</option>
|
||||
<option value="monthly">Monthly</option>
|
||||
</select>
|
||||
</div>
|
||||
<Select
|
||||
value={period}
|
||||
onChange={(e) => setPeriod(e.target.value as Period)}
|
||||
size="sm"
|
||||
options={[
|
||||
{ value: 'hourly', label: 'Hourly' },
|
||||
{ value: 'daily', label: 'Daily' },
|
||||
{ value: 'monthly', label: 'Monthly' },
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{isLoading ? (
|
||||
|
|
|
|||
|
|
@ -88,8 +88,8 @@ export default function AuthenticationPage(): React.ReactElement {
|
|||
<li>Copy your key and store it securely</li>
|
||||
</ol>
|
||||
|
||||
<div className="bg-amber-500/10 border border-amber-500/20 rounded-lg p-4">
|
||||
<p className="text-amber-600 dark:text-amber-400 font-medium">Important</p>
|
||||
<div className="bg-warning/10 border border-warning/20 rounded-lg p-4">
|
||||
<p className="text-warning font-medium">Important</p>
|
||||
<p className="text-foreground-secondary text-sm mt-1">
|
||||
Your API key is displayed only once when created. Make sure to copy and store it
|
||||
securely. If you lose it, you'll need to generate a new one.
|
||||
|
|
|
|||
|
|
@ -120,10 +120,10 @@ function EndpointCard({
|
|||
children?: React.ReactNode;
|
||||
}) {
|
||||
const methodColors = {
|
||||
GET: 'bg-green-500/10 text-green-500 border-green-500/30',
|
||||
POST: 'bg-blue-500/10 text-blue-500 border-blue-500/30',
|
||||
PUT: 'bg-yellow-500/10 text-yellow-500 border-yellow-500/30',
|
||||
DELETE: 'bg-red-500/10 text-red-500 border-red-500/30',
|
||||
GET: 'bg-success/10 text-success border-success/30',
|
||||
POST: 'bg-info/10 text-info border-info/30',
|
||||
PUT: 'bg-warning/10 text-warning border-warning/30',
|
||||
DELETE: 'bg-error/10 text-error border-error/30',
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -377,7 +377,7 @@ export default function APIDocsPage(): React.ReactElement {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-4 border border-yellow-500/30 rounded-lg bg-yellow-500/5">
|
||||
<div className="p-4 border border-warning/30 rounded-lg bg-warning/5">
|
||||
<h3 className="font-semibold text-foreground mb-2">API Key Scopes</h3>
|
||||
<ul className="text-sm text-foreground-secondary list-disc list-inside space-y-1">
|
||||
<li>
|
||||
|
|
@ -398,7 +398,7 @@ export default function APIDocsPage(): React.ReactElement {
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="p-4 border border-green-500/30 rounded-lg bg-green-500/5">
|
||||
<div className="p-4 border border-success/30 rounded-lg bg-success/5">
|
||||
<h3 className="font-semibold text-foreground mb-2">Rate Limits</h3>
|
||||
<ul className="text-sm text-foreground-secondary list-disc list-inside space-y-1">
|
||||
<li>FREE tier: 100 requests/hour</li>
|
||||
|
|
|
|||
|
|
@ -174,10 +174,10 @@ export default function ExecutorsDocsPage(): React.ReactElement {
|
|||
<li>Optionally add an API key if your executor requires authentication</li>
|
||||
<li>Click "Verify Connection" to test the configuration</li>
|
||||
</ol>
|
||||
<div className="p-4 bg-amber-500/10 border border-amber-500/30 rounded-lg">
|
||||
<p className="text-sm text-amber-600 dark:text-amber-400">
|
||||
<div className="p-4 bg-warning/10 border border-warning/30 rounded-lg">
|
||||
<p className="text-sm text-warning">
|
||||
<strong>Security tip:</strong> Set the{' '}
|
||||
<code className="px-1 bg-amber-500/20 rounded">EXECUTOR_API_KEY</code> environment
|
||||
<code className="px-1 bg-warning/20 rounded">EXECUTOR_API_KEY</code> environment
|
||||
variable in your Vercel project to require authentication for all requests.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -214,7 +214,7 @@ export default function ExecutorsDocsPage(): React.ReactElement {
|
|||
{/* GET /health */}
|
||||
<div className="mb-8">
|
||||
<h3 className="text-lg font-medium text-foreground mb-2">
|
||||
<code className="px-2 py-1 bg-green-500/10 text-green-500 rounded">GET</code>{' '}
|
||||
<code className="px-2 py-1 bg-success/10 text-success rounded">GET</code>{' '}
|
||||
/health
|
||||
</h3>
|
||||
<p className="text-foreground-secondary mb-4">
|
||||
|
|
|
|||
|
|
@ -224,15 +224,15 @@ const slides: Slide[] = [
|
|||
</p>
|
||||
<ul className="space-y-2 text-sm text-foreground-secondary">
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="text-green-500">✓</span>
|
||||
<span className="text-success">✓</span>
|
||||
<span>Execute JavaScript/Python</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="text-green-500">✓</span>
|
||||
<span className="text-success">✓</span>
|
||||
<span>Fetch web pages</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="text-green-500">✓</span>
|
||||
<span className="text-success">✓</span>
|
||||
<span>Web search</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -244,15 +244,15 @@ const slides: Slide[] = [
|
|||
</p>
|
||||
<ul className="space-y-2 text-sm text-foreground-secondary">
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="text-green-500">✓</span>
|
||||
<span className="text-success">✓</span>
|
||||
<span>Pre-curated tool sets</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="text-green-500">✓</span>
|
||||
<span className="text-success">✓</span>
|
||||
<span>One-click to add many tools</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="text-green-500">✓</span>
|
||||
<span className="text-success">✓</span>
|
||||
<span>Community collections</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -308,7 +308,7 @@ const slides: Slide[] = [
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-2xl mx-auto p-4 bg-green-500/10 border border-green-500/20 rounded-lg">
|
||||
<div className="max-w-2xl mx-auto p-4 bg-success/10 border border-success/20 rounded-lg">
|
||||
<p className="text-sm text-foreground text-center">
|
||||
🎉 <strong>Conversations are automatically saved</strong> — pick up where you left off
|
||||
anytime!
|
||||
|
|
|
|||
|
|
@ -66,23 +66,23 @@ const slides: Slide[] = [
|
|||
<h4 className="text-lg font-semibold text-foreground mb-3">Cloud-Only Tools</h4>
|
||||
<ul className="space-y-3 text-foreground-secondary">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-green-500 mt-0.5">✓</span>
|
||||
<span className="text-success mt-0.5">✓</span>
|
||||
<span>Code execution (sandboxed)</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-green-500 mt-0.5">✓</span>
|
||||
<span className="text-success mt-0.5">✓</span>
|
||||
<span>Web fetching</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-green-500 mt-0.5">✓</span>
|
||||
<span className="text-success mt-0.5">✓</span>
|
||||
<span>Web search</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-red-500 mt-0.5">✗</span>
|
||||
<span className="text-error mt-0.5">✗</span>
|
||||
<span>Browser automation</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-red-500 mt-0.5">✗</span>
|
||||
<span className="text-error mt-0.5">✗</span>
|
||||
<span>Local file access</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -91,23 +91,23 @@ const slides: Slide[] = [
|
|||
<h4 className="text-lg font-semibold text-foreground mb-3">With Bridge</h4>
|
||||
<ul className="space-y-3 text-foreground-secondary">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-green-500 mt-0.5">✓</span>
|
||||
<span className="text-success mt-0.5">✓</span>
|
||||
<span>Chrome DevTools control</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-green-500 mt-0.5">✓</span>
|
||||
<span className="text-success mt-0.5">✓</span>
|
||||
<span>Read/write local files</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-green-500 mt-0.5">✓</span>
|
||||
<span className="text-success mt-0.5">✓</span>
|
||||
<span>Local database access</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-green-500 mt-0.5">✓</span>
|
||||
<span className="text-success mt-0.5">✓</span>
|
||||
<span>Custom internal APIs</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-green-500 mt-0.5">✓</span>
|
||||
<span className="text-success mt-0.5">✓</span>
|
||||
<span>Any stdio MCP server</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -439,13 +439,13 @@ Opening browser for authentication...
|
|||
</div>
|
||||
<div className="flex justify-center gap-3 text-sm text-foreground-secondary">
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-green-500">✓</span> Chrome automation
|
||||
<span className="text-success">✓</span> Chrome automation
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-green-500">✓</span> Local files
|
||||
<span className="text-success">✓</span> Local files
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-green-500">✓</span> Custom tools
|
||||
<span className="text-success">✓</span> Custom tools
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -176,15 +176,15 @@ export default function CustomExecutorTutorialPage(): React.ReactElement {
|
|||
|
||||
<CodeBlock language="bash" code={envVarsExample} />
|
||||
|
||||
<div className="mt-4 p-4 bg-amber-500/10 border border-amber-500/30 rounded-lg">
|
||||
<div className="mt-4 p-4 bg-warning/10 border border-warning/30 rounded-lg">
|
||||
<div className="flex items-start gap-2">
|
||||
<Icon icon="alertCircle" className="w-5 h-5 text-amber-500 flex-shrink-0 mt-0.5" />
|
||||
<Icon icon="alertCircle" className="w-5 h-5 text-warning flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium text-amber-600 dark:text-amber-400">
|
||||
<p className="font-medium text-warning">
|
||||
Security Recommendation
|
||||
</p>
|
||||
<p className="text-sm text-foreground-secondary mt-1">
|
||||
Set <code className="px-1 bg-amber-500/20 rounded">EXECUTOR_API_KEY</code> to
|
||||
Set <code className="px-1 bg-warning/20 rounded">EXECUTOR_API_KEY</code> to
|
||||
require authentication. Without it, anyone with your executor URL can execute
|
||||
tools.
|
||||
</p>
|
||||
|
|
@ -221,12 +221,12 @@ export default function CustomExecutorTutorialPage(): React.ReactElement {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 p-4 bg-green-500/10 border border-green-500/30 rounded-lg">
|
||||
<div className="mt-4 p-4 bg-success/10 border border-success/30 rounded-lg">
|
||||
<div className="flex items-start gap-2">
|
||||
<Icon icon="check" className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<Icon icon="check" className="w-5 h-5 text-success flex-shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-foreground-secondary">
|
||||
If you see{' '}
|
||||
<code className="px-1 bg-green-500/20 rounded">
|
||||
<code className="px-1 bg-success/20 rounded">
|
||||
"status": "ok"
|
||||
</code>
|
||||
, your executor is running and ready to use!
|
||||
|
|
|
|||
|
|
@ -65,19 +65,19 @@ const slides: Slide[] = [
|
|||
<h4 className="text-lg font-semibold text-foreground mb-3">Without MCP</h4>
|
||||
<ul className="space-y-3 text-foreground-secondary">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-red-500 mt-0.5">✗</span>
|
||||
<span className="text-error mt-0.5">✗</span>
|
||||
<span>AI can only process text</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-red-500 mt-0.5">✗</span>
|
||||
<span className="text-error mt-0.5">✗</span>
|
||||
<span>No access to external data</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-red-500 mt-0.5">✗</span>
|
||||
<span className="text-error mt-0.5">✗</span>
|
||||
<span>Can't take real actions</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-red-500 mt-0.5">✗</span>
|
||||
<span className="text-error mt-0.5">✗</span>
|
||||
<span>Limited to knowledge cutoff</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -86,19 +86,19 @@ const slides: Slide[] = [
|
|||
<h4 className="text-lg font-semibold text-foreground mb-3">With MCP + TPMJS</h4>
|
||||
<ul className="space-y-3 text-foreground-secondary">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-green-500 mt-0.5">✓</span>
|
||||
<span className="text-success mt-0.5">✓</span>
|
||||
<span>Execute code in 40+ languages</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-green-500 mt-0.5">✓</span>
|
||||
<span className="text-success mt-0.5">✓</span>
|
||||
<span>Fetch data from any URL</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-green-500 mt-0.5">✓</span>
|
||||
<span className="text-success mt-0.5">✓</span>
|
||||
<span>Search the web in real-time</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-green-500 mt-0.5">✓</span>
|
||||
<span className="text-success mt-0.5">✓</span>
|
||||
<span>Use specialized tools</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -362,16 +362,16 @@ const slides: Slide[] = [
|
|||
</div>
|
||||
<div className="flex justify-center gap-3 text-sm text-foreground-secondary">
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-green-500">✓</span> Code execution
|
||||
<span className="text-success">✓</span> Code execution
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-green-500">✓</span> Web fetching
|
||||
<span className="text-success">✓</span> Web fetching
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-green-500">✓</span> Web search
|
||||
<span className="text-success">✓</span> Web search
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-green-500">✓</span> And more!
|
||||
<span className="text-success">✓</span> And more!
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ export default function PublishPage(): React.ReactElement {
|
|||
</div>
|
||||
|
||||
{/* Auto-discovery callout */}
|
||||
<div className="mb-8 p-6 border-2 border-amber-500/30 rounded-lg bg-amber-500/5">
|
||||
<div className="mb-8 p-6 border-2 border-warning/30 rounded-lg bg-warning/5">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="text-3xl">🔍</div>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { TPMJS_CATEGORIES } from '@tpmjs/types/tpmjs';
|
||||
import { Badge } from '@tpmjs/ui/Badge/Badge';
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
|
||||
import { Container } from '@tpmjs/ui/Container/Container';
|
||||
import { useState } from 'react';
|
||||
|
|
@ -122,28 +123,22 @@ export default function SpecPage(): React.ReactElement {
|
|||
|
||||
{/* View Toggle */}
|
||||
<div className="flex gap-1 p-1 bg-surface rounded-lg border border-border self-center md:self-auto">
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant={view === 'spec' ? 'default' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => setView('spec')}
|
||||
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
view === 'spec'
|
||||
? 'bg-foreground text-background'
|
||||
: 'text-foreground-secondary hover:text-foreground'
|
||||
}`}
|
||||
className="rounded-md"
|
||||
>
|
||||
Specification
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</Button>
|
||||
<Button
|
||||
variant={view === 'example' ? 'default' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => setView('example')}
|
||||
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
view === 'example'
|
||||
? 'bg-foreground text-background'
|
||||
: 'text-foreground-secondary hover:text-foreground'
|
||||
}`}
|
||||
className="rounded-md"
|
||||
>
|
||||
Full Example
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Container } from '@tpmjs/ui/Container/Container';
|
||||
import Link from 'next/link';
|
||||
import { AppHeader } from '~/components/AppHeader';
|
||||
|
|
@ -450,20 +451,10 @@ export default function TermsPage(): React.ReactElement {
|
|||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<a href="mailto:hello@tpmjs.com">
|
||||
<button
|
||||
type="button"
|
||||
className="px-6 py-3 bg-primary text-primary-foreground rounded-lg font-medium hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
Contact Us
|
||||
</button>
|
||||
<Button size="lg">Contact Us</Button>
|
||||
</a>
|
||||
<Link href="/">
|
||||
<button
|
||||
type="button"
|
||||
className="px-6 py-3 border border-border rounded-lg font-medium hover:bg-surface transition-colors text-foreground"
|
||||
>
|
||||
Back to Home
|
||||
</button>
|
||||
<Button variant="outline" size="lg">Back to Home</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import { Input } from '@tpmjs/ui/Input/Input';
|
||||
import { Textarea } from '@tpmjs/ui/Textarea/Textarea';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { parseEnvString } from '~/lib/utils/env-parser';
|
||||
|
||||
|
|
@ -148,9 +150,6 @@ export function EnvVarsEditor({
|
|||
return parseEnvString(pasteContent);
|
||||
}, [pasteContent]);
|
||||
|
||||
const inputClassName =
|
||||
'flex-1 px-3 py-2 bg-surface border border-border rounded-lg text-foreground font-mono text-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary disabled:opacity-50 disabled:cursor-not-allowed';
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{/* Header */}
|
||||
|
|
@ -182,11 +181,12 @@ export function EnvVarsEditor({
|
|||
<Icon icon="x" size="xs" />
|
||||
</Button>
|
||||
</div>
|
||||
<textarea
|
||||
<Textarea
|
||||
value={pasteContent}
|
||||
onChange={(e) => setPasteContent(e.target.value)}
|
||||
placeholder={`# Paste your .env content here\nAPI_KEY=your-api-key\nDATABASE_URL="postgres://..."\n`}
|
||||
className="w-full h-32 px-3 py-2 bg-surface border border-border rounded-lg text-foreground font-mono text-xs focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary resize-none"
|
||||
className="h-32 text-xs"
|
||||
resize="none"
|
||||
disabled={disabled}
|
||||
/>
|
||||
{parsedPreview.length > 0 && (
|
||||
|
|
@ -217,20 +217,20 @@ export function EnvVarsEditor({
|
|||
<div className="space-y-2 mb-3">
|
||||
{envVars.map((env, index) => (
|
||||
<div key={`env-${env.key || index}`} className="flex items-center gap-2">
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
value={env.key}
|
||||
onChange={(e) => updateEnvVar(index, 'key', e.target.value)}
|
||||
placeholder="KEY"
|
||||
className={inputClassName}
|
||||
size="sm"
|
||||
disabled={disabled}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
type="password"
|
||||
value={env.value}
|
||||
onChange={(e) => updateEnvVar(index, 'value', e.target.value)}
|
||||
placeholder="value"
|
||||
className={inputClassName}
|
||||
size="sm"
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Button
|
||||
|
|
@ -249,12 +249,12 @@ export function EnvVarsEditor({
|
|||
|
||||
{/* Add new env var */}
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
value={newEnvKey}
|
||||
onChange={(e) => setNewEnvKey(e.target.value.toUpperCase())}
|
||||
placeholder={keyPlaceholder}
|
||||
className={inputClassName}
|
||||
size="sm"
|
||||
disabled={disabled}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && newEnvKey.trim()) {
|
||||
|
|
@ -262,12 +262,12 @@ export function EnvVarsEditor({
|
|||
}
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
value={newEnvValue}
|
||||
onChange={(e) => setNewEnvValue(e.target.value)}
|
||||
placeholder={valuePlaceholder}
|
||||
className={inputClassName}
|
||||
size="sm"
|
||||
disabled={disabled}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && newEnvKey.trim()) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue