chore(dev): add styling to custom agents

This commit is contained in:
Soumyadas15 2025-05-29 15:16:30 +05:30
parent 8132fc59dd
commit a95ae458ef
10 changed files with 2219 additions and 41 deletions

View File

@ -51,6 +51,8 @@ class AgentCreateRequest(BaseModel):
configured_mcps: Optional[List[Dict[str, Any]]] = []
agentpress_tools: Optional[Dict[str, Any]] = {}
is_default: Optional[bool] = False
avatar: Optional[str] = None
avatar_color: Optional[str] = None
class AgentUpdateRequest(BaseModel):
name: Optional[str] = None
@ -59,6 +61,8 @@ class AgentUpdateRequest(BaseModel):
configured_mcps: Optional[List[Dict[str, Any]]] = None
agentpress_tools: Optional[Dict[str, Any]] = None
is_default: Optional[bool] = None
avatar: Optional[str] = None
avatar_color: Optional[str] = None
class AgentResponse(BaseModel):
agent_id: str
@ -69,6 +73,8 @@ class AgentResponse(BaseModel):
configured_mcps: List[Dict[str, Any]]
agentpress_tools: Dict[str, Any]
is_default: bool
avatar: Optional[str]
avatar_color: Optional[str]
created_at: str
updated_at: str
@ -528,6 +534,8 @@ async def get_thread_agent(thread_id: str, user_id: str = Depends(get_current_us
configured_mcps=agent_data.get('configured_mcps', []),
agentpress_tools=agent_data.get('agentpress_tools', {}),
is_default=agent_data.get('is_default', False),
avatar=agent_data.get('avatar'),
avatar_color=agent_data.get('avatar_color'),
created_at=agent_data['created_at'],
updated_at=agent_data['updated_at']
),
@ -1007,6 +1015,8 @@ async def get_agents(user_id: str = Depends(get_current_user_id_from_jwt)):
configured_mcps=agent.get('configured_mcps', []),
agentpress_tools=agent.get('agentpress_tools', {}),
is_default=agent.get('is_default', False),
avatar=agent.get('avatar'),
avatar_color=agent.get('avatar_color'),
created_at=agent['created_at'],
updated_at=agent['updated_at']
))
@ -1041,6 +1051,8 @@ async def get_agent(agent_id: str, user_id: str = Depends(get_current_user_id_fr
configured_mcps=agent_data.get('configured_mcps', []),
agentpress_tools=agent_data.get('agentpress_tools', {}),
is_default=agent_data.get('is_default', False),
avatar=agent_data.get('avatar'),
avatar_color=agent_data.get('avatar_color'),
created_at=agent_data['created_at'],
updated_at=agent_data['updated_at']
)
@ -1078,7 +1090,9 @@ async def create_agent(
"system_prompt": agent_data.system_prompt,
"configured_mcps": agent_data.configured_mcps or [],
"agentpress_tools": agent_data.agentpress_tools or {},
"is_default": agent_data.is_default or False
"is_default": agent_data.is_default or False,
"avatar": agent_data.avatar,
"avatar_color": agent_data.avatar_color
}
new_agent = await client.table('agents').insert(insert_data).execute()
@ -1098,6 +1112,8 @@ async def create_agent(
configured_mcps=agent.get('configured_mcps', []),
agentpress_tools=agent.get('agentpress_tools', {}),
is_default=agent.get('is_default', False),
avatar=agent.get('avatar'),
avatar_color=agent.get('avatar_color'),
created_at=agent['created_at'],
updated_at=agent['updated_at']
)
@ -1150,6 +1166,10 @@ async def update_agent(
# If setting as default, unset other defaults first
if agent_data.is_default:
await client.table('agents').update({"is_default": False}).eq("account_id", user_id).eq("is_default", True).neq("agent_id", agent_id).execute()
if agent_data.avatar is not None:
update_data["avatar"] = agent_data.avatar
if agent_data.avatar_color is not None:
update_data["avatar_color"] = agent_data.avatar_color
if not update_data:
# No fields to update, return existing agent
@ -1180,6 +1200,8 @@ async def update_agent(
configured_mcps=agent.get('configured_mcps', []),
agentpress_tools=agent.get('agentpress_tools', {}),
is_default=agent.get('is_default', False),
avatar=agent.get('avatar'),
avatar_color=agent.get('avatar_color'),
created_at=agent['created_at'],
updated_at=agent['updated_at']
)

View File

@ -0,0 +1,13 @@
BEGIN;
ALTER TABLE agents
ADD COLUMN IF NOT EXISTS avatar VARCHAR(10),
ADD COLUMN IF NOT EXISTS avatar_color VARCHAR(7);
CREATE INDEX IF NOT EXISTS idx_agents_avatar ON agents(avatar);
CREATE INDEX IF NOT EXISTS idx_agents_avatar_color ON agents(avatar_color);
COMMENT ON COLUMN agents.avatar IS 'Emoji character used as agent avatar (e.g., 🤖)';
COMMENT ON COLUMN agents.avatar_color IS 'Hex color code for agent avatar background (e.g., #3b82f6)';
COMMIT;

View File

@ -1,8 +1,19 @@
import React from 'react';
import { Bot, Calendar, Settings, Sparkles, User } from 'lucide-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import React, { useState, useRef, useCallback, useEffect } from 'react';
import { Badge } from '@/components/ui/badge';
import { DEFAULT_AGENTPRESS_TOOLS, getToolDisplayName } from '../_data/tools';
import { cn } from '@/lib/utils';
import { toast } from 'sonner';
import { getAgentAvatar } from '../_utils/get-agent-style';
import {
ChatInput,
ChatInputHandles
} from '@/components/thread/chat-input/chat-input';
import { ThreadContent } from '@/components/thread/content/ThreadContent';
import { UnifiedMessage } from '@/components/thread/types';
import { useInitiateAgentWithInvalidation } from '@/hooks/react-query/dashboard/use-initiate-agent';
import { useAgentStream } from '@/hooks/useAgentStream';
import { useAddUserMessageMutation } from '@/hooks/react-query/threads/use-messages';
import { useStartAgentMutation, useStopAgentMutation } from '@/hooks/react-query/threads/use-agent-run';
import { BillingError } from '@/lib/api';
interface Agent {
agent_id: string;
@ -21,15 +32,349 @@ interface AgentPreviewProps {
}
export const AgentPreview = ({ agent }: AgentPreviewProps) => {
const enabledTools = Object.entries(agent.agentpress_tools || {})
.filter(([_, tool]) => tool.enabled)
.map(([toolName]) => toolName);
const [messages, setMessages] = useState<UnifiedMessage[]>([]);
const [inputValue, setInputValue] = useState('');
const [threadId, setThreadId] = useState<string | null>(null);
const [agentRunId, setAgentRunId] = useState<string | null>(null);
const [agentStatus, setAgentStatus] = useState<'idle' | 'running' | 'connecting' | 'error'>('idle');
const [isSubmitting, setIsSubmitting] = useState(false);
const [hasStartedConversation, setHasStartedConversation] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
const chatInputRef = useRef<ChatInputHandles>(null);
const getAgentStyling = () => {
const agentData = agent as any;
if (agentData.avatar && agentData.avatar_color) {
return {
avatar: agentData.avatar,
color: agentData.avatar_color,
};
}
return getAgentAvatar(agent.agent_id);
};
const { avatar, color } = getAgentStyling();
const initiateAgentMutation = useInitiateAgentWithInvalidation();
const addUserMessageMutation = useAddUserMessageMutation();
const startAgentMutation = useStartAgentMutation();
const stopAgentMutation = useStopAgentMutation();
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const handleNewMessageFromStream = useCallback((message: UnifiedMessage) => {
console.log(`[PREVIEW STREAM] Received message: ID=${message.message_id}, Type=${message.type}`);
setMessages((prev) => {
const messageExists = prev.some((m) => m.message_id === message.message_id);
if (messageExists) {
return prev.map((m) => m.message_id === message.message_id ? message : m);
} else {
return [...prev, message];
}
});
}, []);
const handleStreamStatusChange = useCallback((hookStatus: string) => {
console.log(`[PREVIEW] Stream status changed: ${hookStatus}`);
switch (hookStatus) {
case 'idle':
case 'completed':
case 'stopped':
case 'agent_not_running':
case 'error':
case 'failed':
setAgentStatus('idle');
setAgentRunId(null);
break;
case 'connecting':
setAgentStatus('connecting');
break;
case 'streaming':
setAgentStatus('running');
break;
}
}, []);
const handleStreamError = useCallback((errorMessage: string) => {
console.error(`[PREVIEW] Stream error: ${errorMessage}`);
if (!errorMessage.toLowerCase().includes('not found') &&
!errorMessage.toLowerCase().includes('agent run is not running')) {
toast.error(`Stream Error: ${errorMessage}`);
}
}, []);
const handleStreamClose = useCallback(() => {
console.log(`[PREVIEW] Stream closed`);
}, []);
const {
status: streamHookStatus,
textContent: streamingTextContent,
toolCall: streamingToolCall,
error: streamError,
agentRunId: currentHookRunId,
startStreaming,
stopStreaming,
} = useAgentStream(
{
onMessage: handleNewMessageFromStream,
onStatusChange: handleStreamStatusChange,
onError: handleStreamError,
onClose: handleStreamClose,
},
threadId,
setMessages,
);
useEffect(() => {
if (agentRunId && agentRunId !== currentHookRunId && threadId) {
console.log(`[PREVIEW] Starting stream for agentRunId: ${agentRunId}, threadId: ${threadId}`);
startStreaming(agentRunId);
}
}, [agentRunId, startStreaming, currentHookRunId, threadId]);
useEffect(() => {
console.log('[PREVIEW] State update:', {
threadId,
agentRunId,
currentHookRunId,
agentStatus,
streamHookStatus,
messagesCount: messages.length,
hasStartedConversation
});
}, [threadId, agentRunId, currentHookRunId, agentStatus, streamHookStatus, messages.length, hasStartedConversation]);
useEffect(() => {
if (streamingTextContent) {
scrollToBottom();
}
}, [streamingTextContent]);
const handleSubmitFirstMessage = async (
message: string,
options?: {
model_name?: string;
enable_thinking?: boolean;
reasoning_effort?: string;
stream?: boolean;
enable_context_manager?: boolean;
},
) => {
if (!message.trim() && !chatInputRef.current?.getPendingFiles().length) return;
setIsSubmitting(true);
setHasStartedConversation(true);
try {
const files = chatInputRef.current?.getPendingFiles() || [];
const formData = new FormData();
formData.append('prompt', message);
formData.append('agent_id', agent.agent_id);
files.forEach((file, index) => {
formData.append('files', file, file.name);
});
if (options?.model_name) formData.append('model_name', options.model_name);
formData.append('enable_thinking', String(options?.enable_thinking ?? false));
formData.append('reasoning_effort', options?.reasoning_effort ?? 'low');
formData.append('stream', String(options?.stream ?? true));
formData.append('enable_context_manager', String(options?.enable_context_manager ?? false));
console.log('[PREVIEW] Initiating agent...');
const result = await initiateAgentMutation.mutateAsync(formData);
console.log('[PREVIEW] Agent initiated:', result);
if (result.thread_id) {
setThreadId(result.thread_id);
if (result.agent_run_id) {
console.log('[PREVIEW] Setting agent run ID:', result.agent_run_id);
setAgentRunId(result.agent_run_id);
} else {
console.log('[PREVIEW] No agent_run_id in result, starting agent manually...');
try {
const agentResult = await startAgentMutation.mutateAsync({
threadId: result.thread_id,
options
});
console.log('[PREVIEW] Agent started manually:', agentResult);
setAgentRunId(agentResult.agent_run_id);
} catch (startError) {
console.error('[PREVIEW] Error starting agent manually:', startError);
toast.error('Failed to start agent');
}
}
const userMessage: UnifiedMessage = {
message_id: `user-${Date.now()}`,
thread_id: result.thread_id,
type: 'user',
is_llm_message: false,
content: message,
metadata: '{}',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};
setMessages([userMessage]);
}
chatInputRef.current?.clearPendingFiles();
setInputValue('');
} catch (error: any) {
console.error('[PREVIEW] Error during initiation:', error);
if (error instanceof BillingError) {
toast.error('Billing limit reached. Please upgrade your plan.');
} else {
toast.error('Failed to start conversation');
}
setHasStartedConversation(false);
} finally {
setIsSubmitting(false);
}
};
const handleSubmitMessage = useCallback(
async (
message: string,
options?: { model_name?: string; enable_thinking?: boolean },
) => {
if (!message.trim() || !threadId) return;
setIsSubmitting(true);
const optimisticUserMessage: UnifiedMessage = {
message_id: `temp-${Date.now()}`,
thread_id: threadId,
type: 'user',
is_llm_message: false,
content: message,
metadata: '{}',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};
setMessages((prev) => [...prev, optimisticUserMessage]);
setInputValue('');
try {
const messagePromise = addUserMessageMutation.mutateAsync({
threadId,
message
});
const agentPromise = startAgentMutation.mutateAsync({
threadId,
options
});
const results = await Promise.allSettled([messagePromise, agentPromise]);
if (results[0].status === 'rejected') {
throw new Error(`Failed to send message: ${results[0].reason?.message || results[0].reason}`);
}
if (results[1].status === 'rejected') {
const error = results[1].reason;
if (error instanceof BillingError) {
toast.error('Billing limit reached. Please upgrade your plan.');
setMessages(prev => prev.filter(m => m.message_id !== optimisticUserMessage.message_id));
return;
}
throw new Error(`Failed to start agent: ${error?.message || error}`);
}
const agentResult = results[1].value;
setAgentRunId(agentResult.agent_run_id);
} catch (err) {
console.error('[PREVIEW] Error sending message:', err);
toast.error(err instanceof Error ? err.message : 'Operation failed');
setMessages((prev) => prev.filter((m) => m.message_id !== optimisticUserMessage.message_id));
} finally {
setIsSubmitting(false);
}
},
[threadId, addUserMessageMutation, startAgentMutation],
);
const handleStopAgent = useCallback(async () => {
console.log('[PREVIEW] Stopping agent...');
setAgentStatus('idle');
await stopStreaming();
if (agentRunId) {
try {
await stopAgentMutation.mutateAsync(agentRunId);
} catch (error) {
console.error('[PREVIEW] Error stopping agent:', error);
}
}
}, [stopStreaming, agentRunId, stopAgentMutation]);
const handleToolClick = useCallback((assistantMessageId: string | null, toolName: string) => {
console.log('[PREVIEW] Tool clicked:', toolName);
toast.info(`Tool: ${toolName} (Preview mode - tool details not available)`);
}, []);
const enabledMCPs = agent.configured_mcps || [];
return (
<div className="space-y-6">
<div className="h-full flex flex-col bg-muted dark:bg-muted/30">
<div className="flex-shrink-0 flex items-center gap-3 p-8">
<div
className="h-10 w-10 flex items-center justify-center rounded-lg text-lg"
style={{ backgroundColor: color }}
>
{avatar}
</div>
<div className="flex-1">
<h3 className="font-semibold">{agent.name || 'Unnamed Agent'}</h3>
{agent.description && (
<p className="text-sm text-muted-foreground">{agent.description}</p>
)}
</div>
<Badge variant="outline" className="text-xs">Preview Mode</Badge>
</div>
<div className="flex-1 overflow-hidden">
<div className="h-full overflow-y-auto scrollbar-hide">
<ThreadContent
messages={messages}
streamingTextContent={streamingTextContent}
streamingToolCall={streamingToolCall}
agentStatus={agentStatus}
handleToolClick={handleToolClick}
handleOpenFileViewer={() => {}}
streamHookStatus={streamHookStatus}
isPreviewMode={true}
/>
<div ref={messagesEndRef} />
</div>
</div>
<div className="flex-shrink-0">
<div className="p-4">
<ChatInput
ref={chatInputRef}
onSubmit={threadId ? handleSubmitMessage : handleSubmitFirstMessage}
loading={isSubmitting}
placeholder={`Message ${agent.name || 'agent'}...`}
value={inputValue}
onChange={setInputValue}
disabled={isSubmitting}
isAgentRunning={agentStatus === 'running' || agentStatus === 'connecting'}
onStopAgent={handleStopAgent}
agentName={agent.name}
hideAttachments={false}
bgColor='bg-muted-foreground/10'
/>
</div>
</div>
</div>
);
};
};

View File

@ -61,7 +61,7 @@ export const AgentToolsConfiguration = ({ tools, onToolsChange }: AgentToolsConf
/>
</div>
<div className="space-y-3 max-h-[400px] overflow-y-auto scrollbar-thin scrollbar-thumb-zinc-300 dark:scrollbar-thumb-zinc-700 scrollbar-track-transparent">
<div className="gap-4 grid grid-cols-1 md:grid-cols-2 max-h-[400px] overflow-y-auto scrollbar-thin scrollbar-thumb-zinc-300 dark:scrollbar-thumb-zinc-700 scrollbar-track-transparent">
{getFilteredTools().map(([toolName, toolInfo]) => (
<div
key={toolName}

View File

@ -16,7 +16,7 @@ interface Agent {
configured_mcps?: Array<{ name: string }>;
agentpress_tools?: Record<string, any>;
avatar?: string;
color?: string;
avatar_color?: string;
}
interface AgentsGridProps {
@ -28,7 +28,17 @@ interface AgentsGridProps {
}
const AgentModal = ({ agent, isOpen, onClose, onCustomize, onChat }) => {
const { avatar, color } = getAgentAvatar(agent.agent_id);
const getAgentStyling = (agent: Agent) => {
if (agent.avatar && agent.avatar_color) {
return {
avatar: agent.avatar,
color: agent.avatar_color,
};
}
return getAgentAvatar(agent.agent_id);
};
const { avatar, color } = getAgentStyling(agent);
const truncateDescription = (text, maxLength = 120) => {
if (!text || text.length <= maxLength) return text || 'Try out this agent';
@ -40,9 +50,9 @@ const AgentModal = ({ agent, isOpen, onClose, onCustomize, onChat }) => {
<DialogContent className="max-w-md p-0 overflow-hidden border-none">
<DialogTitle className="sr-only">Agent actions</DialogTitle>
<div className="relative">
<div className={`${color} h-32 flex items-center justify-center relative bg-gradient-to-br from-opacity-90 to-opacity-100`}>
<div className={`h-32 flex items-center justify-center relative bg-gradient-to-br from-opacity-90 to-opacity-100`} style={{ backgroundColor: color }}>
<div className="text-6xl drop-shadow-sm">
{agent.avatar || avatar}
{avatar}
</div>
{agent.is_default && (
<div className="absolute top-4 right-4">
@ -109,11 +119,21 @@ export const AgentsGrid = ({
setSelectedAgent(null);
};
const getAgentStyling = (agent: Agent) => {
if (agent.avatar && agent.avatar_color) {
return {
avatar: agent.avatar,
color: agent.avatar_color,
};
}
return getAgentAvatar(agent.agent_id);
};
return (
<>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{agents.map((agent) => {
const { avatar, color } = getAgentAvatar(agent.agent_id);
const { avatar, color } = getAgentStyling(agent);
return (
<div
@ -121,9 +141,9 @@ export const AgentsGrid = ({
className="bg-neutral-100 dark:bg-sidebar border border-border rounded-2xl overflow-hidden hover:bg-muted/50 transition-all duration-200 cursor-pointer group"
onClick={() => handleAgentClick(agent)}
>
<div className={`${color} h-50 flex items-center justify-center relative`}>
<div className={`h-50 flex items-center justify-center relative`} style={{ backgroundColor: color }}>
<div className="text-4xl">
{agent.avatar || avatar}
{avatar}
</div>
{agent.is_default && (
<div className="absolute top-3 right-3">

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
export const getAgentAvatar = (agentId: string) => {
const avatars = ['🤖', '🎯', '⚡', '🚀', '🔮', '🎨', '📊', '🔧', '💡', '🌟'];
const colors = ['bg-cyan-400', 'bg-green-400', 'bg-purple-400', 'bg-blue-400', 'bg-pink-400', 'bg-yellow-400', 'bg-red-400', 'bg-indigo-400'];
const colors = ['#06b6d4', '#22c55e', '#8b5cf6', '#3b82f6', '#ec4899', '#eab308', '#ef4444', '#6366f1'];
const avatarIndex = agentId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) % avatars.length;
const colorIndex = agentId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) % colors.length;
return {

View File

@ -14,6 +14,22 @@ import { AgentPreview } from '../../_components/agent-preview';
import { cn } from '@/lib/utils';
import { getAgentAvatar } from '../../_utils/get-agent-style';
import { EditableText } from '@/components/ui/editable';
import { StylePicker } from '../../_components/style-picker';
// Extended agent interface for styling
interface AgentWithStyling {
agent_id: string;
name?: string;
description?: string;
system_prompt?: string;
agentpress_tools?: Record<string, { enabled: boolean; description: string }>;
configured_mcps?: Array<{ name: string; qualifiedName: string; config: any; enabledTools?: string[] }>;
is_default?: boolean;
avatar?: string;
avatar_color?: string;
created_at?: string;
updated_at?: string;
}
type SaveStatus = 'idle' | 'saving' | 'saved' | 'error';
@ -33,6 +49,8 @@ export default function AgentConfigurationPage() {
agentpress_tools: {},
configured_mcps: [],
is_default: false,
avatar: '',
avatar_color: '',
});
const originalDataRef = useRef<typeof formData | null>(null);
@ -42,13 +60,16 @@ export default function AgentConfigurationPage() {
useEffect(() => {
if (agent) {
const agentData = agent as any; // Safe casting for extended properties
const initialData = {
name: agent.name || '',
description: agent.description || '',
system_prompt: agent.system_prompt || '',
agentpress_tools: agent.agentpress_tools || {},
configured_mcps: agent.configured_mcps || [],
is_default: agent.is_default || false,
name: agentData.name || '',
description: agentData.description || '',
system_prompt: agentData.system_prompt || '',
agentpress_tools: agentData.agentpress_tools || {},
configured_mcps: agentData.configured_mcps || [],
is_default: agentData.is_default || false,
avatar: agentData.avatar || '',
avatar_color: agentData.avatar_color || '',
};
setFormData(initialData);
originalDataRef.current = { ...initialData };
@ -128,6 +149,29 @@ export default function AgentConfigurationPage() {
}
}, []);
const handleStyleChange = useCallback((emoji: string, color: string) => {
const newFormData = {
...formData,
avatar: emoji,
avatar_color: color,
};
setFormData(newFormData);
debouncedSave(newFormData);
}, [formData, debouncedSave]);
// Get current style with fallback to generated defaults
const getCurrentStyle = useCallback(() => {
if (formData.avatar && formData.avatar_color) {
return {
avatar: formData.avatar,
color: formData.avatar_color,
};
}
return getAgentAvatar(agentId);
}, [formData.avatar, formData.avatar_color, agentId]);
const currentStyle = getCurrentStyle();
const getSaveStatusBadge = () => {
const showSaved = saveStatus === 'idle' && !hasDataChanged(formData, originalDataRef.current);
switch (saveStatus) {
@ -212,9 +256,19 @@ export default function AgentConfigurationPage() {
</div>
<div className='flex items-center'>
<div className={cn(color, 'h-16 w-16 flex items-center justify-center rounded-md text-2xl')}>
{avatar}
</div>
<StylePicker
agentId={agentId}
currentEmoji={currentStyle.avatar}
currentColor={currentStyle.color}
onStyleChange={handleStyleChange}
>
<div
className="h-16 w-16 flex items-center justify-center rounded-2xl text-2xl cursor-pointer hover:opacity-80 transition-opacity"
style={{ backgroundColor: currentStyle.color }}
>
{currentStyle.avatar}
</div>
</StylePicker>
<div className='flex flex-col ml-3'>
<EditableText
value={formData.name}
@ -285,14 +339,8 @@ export default function AgentConfigurationPage() {
</div>
</div>
<div className="w-1/2 bg-muted/30 overflow-y-auto">
<div className="p-6">
<div className="flex items-center gap-2 mb-4">
<MessageSquare className="h-5 w-5" />
<h2 className="text-lg font-semibold">Preview</h2>
</div>
<AgentPreview agent={{ ...agent, ...formData }} />
</div>
<div className="w-1/2 overflow-y-auto">
<AgentPreview agent={{ ...agent, ...formData }} />
</div>
</div>
</div>

View File

@ -43,6 +43,7 @@ export interface ChatInputProps {
onAgentSelect?: (agentId: string | undefined) => void;
agentName?: string;
messages?: any[];
bgColor?: string;
}
export interface UploadedFile {
@ -72,6 +73,7 @@ export const ChatInput = forwardRef<ChatInputHandles, ChatInputProps>(
onAgentSelect,
agentName,
messages = [],
bgColor = 'bg-sidebar',
},
ref,
) => {
@ -237,7 +239,7 @@ export const ChatInput = forwardRef<ChatInputHandles, ChatInputProps>(
}}
>
<div className="w-full text-sm flex flex-col justify-between items-start rounded-lg">
<CardContent className="w-full p-1.5 pb-2 bg-sidebar rounded-2xl border">
<CardContent className={`w-full p-1.5 pb-2 ${bgColor} rounded-2xl border`}>
{onAgentSelect && (
<div className="mb-2 px-2">
<AgentSelector

View File

@ -255,6 +255,7 @@ export interface ThreadContentProps {
sandboxId?: string; // Add sandboxId prop
project?: Project; // Add project prop
debugMode?: boolean; // Add debug mode parameter
isPreviewMode?: boolean;
}
export const ThreadContent: React.FC<ThreadContentProps> = ({
@ -272,7 +273,8 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
streamHookStatus = "idle",
sandboxId,
project,
debugMode = false
debugMode = false,
isPreviewMode = false,
}) => {
const messagesEndRef = useRef<HTMLDivElement>(null);
const messagesContainerRef = useRef<HTMLDivElement>(null);
@ -284,6 +286,10 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
// React Query file preloader
const { preloadFiles } = useFilePreloader();
const containerClassName = isPreviewMode
? "flex-1 overflow-y-auto scrollbar-thin scrollbar-track-secondary/0 scrollbar-thumb-primary/10 scrollbar-thumb-rounded-full hover:scrollbar-thumb-primary/10 px-6 py-4 pb-72"
: "flex-1 overflow-y-auto scrollbar-thin scrollbar-track-secondary/0 scrollbar-thumb-primary/10 scrollbar-thumb-rounded-full hover:scrollbar-thumb-primary/10 px-6 py-4 pb-72 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60";
// In playback mode, we use visibleMessages instead of messages
const displayMessages = readOnly && visibleMessages ? visibleMessages : messages;
@ -339,7 +345,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
<div
ref={messagesContainerRef}
className="flex-1 overflow-y-auto scrollbar-thin scrollbar-track-secondary/0 scrollbar-thumb-primary/10 scrollbar-thumb-rounded-full hover:scrollbar-thumb-primary/10 px-6 py-4 pb-72 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"
className={containerClassName}
onScroll={handleScroll}
>
<div className="mx-auto max-w-3xl">