diff --git a/app/admin/_components/admin-sidebar.tsx b/app/admin/_components/admin-sidebar.tsx index 3b485b0..6a0b6d2 100644 --- a/app/admin/_components/admin-sidebar.tsx +++ b/app/admin/_components/admin-sidebar.tsx @@ -13,7 +13,7 @@ import { Settings, } from 'lucide-react' -type NavItem = { +interface NavItem { title: string href: Route icon: React.ComponentType<{ className?: string }> diff --git a/app/api/contact/route.ts b/app/api/contact/route.ts index 9620da7..9d7cf71 100644 --- a/app/api/contact/route.ts +++ b/app/api/contact/route.ts @@ -1,4 +1,5 @@ -import { NextRequest, NextResponse } from 'next/server' +import type { NextRequest} from 'next/server'; +import { NextResponse } from 'next/server' interface ContactFormData { firstName: string diff --git a/app/chat/components/agent-artifact.tsx b/app/chat/components/agent-artifact.tsx index 4c6d165..ba766ae 100644 --- a/app/chat/components/agent-artifact.tsx +++ b/app/chat/components/agent-artifact.tsx @@ -30,7 +30,7 @@ import type { ArtifactData } from './chat.types' interface AgentArtifactProps { artifact: ArtifactData onClose?: () => void - // eslint-disable-next-line no-unused-vars + onCodeUpdate?: (artifactId: string, newCode: string) => void } @@ -59,14 +59,17 @@ export function AgentArtifact({ artifact.type === 'code' && PREVIEWABLE_LANGUAGES.includes(normalizeLanguage(artifact.language)) - const handleCopy = useCallback(async () => { - try { - await navigator.clipboard.writeText(editedCode) - setCopied(true) - setTimeout(() => setCopied(false), 2000) - } catch (err) { - void err + const handleCopy = useCallback(() => { + const doCopy = async () => { + try { + await navigator.clipboard.writeText(editedCode) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } catch (err) { + void err + } } + void doCopy() }, [editedCode]) const handleDownload = useCallback(() => { @@ -233,7 +236,7 @@ export function AgentArtifactCompact({ // Floating action button for quick access to editor interface ArtifactEditorFABProps { artifact: ArtifactData - // eslint-disable-next-line no-unused-vars + onCodeChange?: (newCode: string) => void } diff --git a/app/chat/components/agent-confirmation.tsx b/app/chat/components/agent-confirmation.tsx index a6463dc..c53e92a 100644 --- a/app/chat/components/agent-confirmation.tsx +++ b/app/chat/components/agent-confirmation.tsx @@ -28,10 +28,8 @@ interface AgentConfirmationProps { state: ToolUIPart['state'] severity?: ConfirmationSeverity className?: string - // eslint-disable-next-line no-unused-vars - onApprove(approvalId: string): void - // eslint-disable-next-line no-unused-vars - onReject(approvalId: string): void + onApprove: (approvalId: string) => void + onReject: (approvalId: string) => void } const severityConfig: Record< diff --git a/app/chat/components/agent-web-preview.tsx b/app/chat/components/agent-web-preview.tsx index 18440cf..cb720d7 100644 --- a/app/chat/components/agent-web-preview.tsx +++ b/app/chat/components/agent-web-preview.tsx @@ -36,7 +36,7 @@ import { CheckIcon, PlayIcon, RotateCcwIcon, - // eslint-disable-next-line no-unused-vars + Edit3Icon, EyeIcon, SplitIcon, @@ -54,7 +54,7 @@ type PreviewStatus = 'idle' | 'running' | 'success' | 'error' interface AgentWebPreviewProps { preview: WebPreviewData onClose?: () => void - // eslint-disable-next-line no-unused-vars + onCodeChange?: (code: string) => void defaultTab?: 'preview' | 'code' height?: string | number @@ -444,7 +444,7 @@ export function AgentWebPreview({ variant="ghost" size="sm" className="h-6 gap-1 px-2 text-xs" - onClick={handleCopy} + onClick={() => void handleCopy()} > {copied ? ( <> @@ -786,7 +786,7 @@ interface AgentCodeSandboxProps { title?: string dependencies?: Record onClose?: () => void - // eslint-disable-next-line no-unused-vars + onCodeChange?: (code: string) => void editable?: boolean } diff --git a/app/chat/components/chat-messages.tsx b/app/chat/components/chat-messages.tsx index e41b914..21424ec 100644 --- a/app/chat/components/chat-messages.tsx +++ b/app/chat/components/chat-messages.tsx @@ -1,4 +1,4 @@ -/* eslint-disable no-unused-vars */ + /* eslint-disable no-console */ 'use client' @@ -51,6 +51,8 @@ import { MessageSquareIcon, BookmarkPlusIcon, ChevronDownIcon, + ActivityIcon, + NetworkIcon, } from 'lucide-react' import { useState, useCallback, useMemo, Fragment } from 'react' import type { UIMessage, FileUIPart } from 'ai' @@ -82,6 +84,381 @@ type MastraDataPart = | NetworkDataPart | { type: `data-${string}`; id?: string; data: unknown } +/** + * Type guard to check for type property + */ +function hasStringType(part: unknown): part is { type: string } { + return ( + typeof part === 'object' && + part !== null && + 'type' in part && + typeof (part as { type: unknown }).type === 'string' + ) +} + +/** + * Type guard to check if a part is an AgentDataPart + */ +function isAgentDataPart(part: unknown): part is AgentDataPart { + return hasStringType(part) && part.type === 'data-tool-agent' +} + +/** + * Type guard to check if a part is a WorkflowDataPart + */ +function isWorkflowDataPart(part: unknown): part is WorkflowDataPart { + return ( + hasStringType(part) && + (part.type === 'data-workflow' || part.type === 'data-tool-workflow') + ) +} + +/** + * Type guard to check if a part is a NetworkDataPart + */ +function isNetworkDataPart(part: unknown): part is NetworkDataPart { + return ( + hasStringType(part) && + (part.type === 'data-network' || part.type === 'data-tool-network') + ) +} + +/** + * Renders a nested agent execution result + */ +function AgentDataSection({ part }: { part: AgentDataPart }) { + const agentData = part.data + const hasText = Boolean( + typeof agentData.text === 'string' && agentData.text.trim().length > 0 + ) + + return ( + + + + + Agent Execution + +
+ + Nested Agent + + +
+
+ +
+ {hasText && ( +
+

+ {agentData.text} +

+
+ )} +
+
+ Agent Metadata +
+ 0 && { + toolsUsed: agentData.toolResults.map((tr: unknown) => + typeof tr === 'object' && tr !== null && typeof (tr as Record).toolName === 'string' + ? (tr as Record).toolName + : 'unknown' + ), + }), + }, + null, + 2 + )} + language="json" + > + + +
+
+
+
+ ) +} + +/** + * Renders a nested workflow execution result + */ +function WorkflowDataSection({ part }: { part: WorkflowDataPart }) { + const workflowData = part.data + const stepEntries = Object.entries(workflowData.steps ?? {}) + + return ( + + + + + {workflowData.name} + +
+ + {workflowData.status} + + + {stepEntries.length} steps + + +
+
+ +
+ {/* Workflow Steps */} + {stepEntries.length > 0 && ( +
+
+ Workflow Steps +
+ {stepEntries.map(([stepName, stepData]) => ( +
+
+ + {stepName} + + + {stepData.status} + +
+ + + +
+ ))} +
+ )} + + {/* Usage Statistics */} + {workflowData.output?.usage && ( +
+
+ Token Usage +
+
+
+
+ Input +
+
+ {workflowData.output.usage.inputTokens.toLocaleString()} +
+
+
+
+ Output +
+
+ {workflowData.output.usage.outputTokens.toLocaleString()} +
+
+
+
+ Total +
+
+ {workflowData.output.usage.totalTokens.toLocaleString()} +
+
+
+
+ )} +
+
+
+ ) +} + +/** + * Renders a nested network execution result + */ +function NetworkDataSection({ part }: { part: NetworkDataPart }) { + const networkData = part.data + + return ( + + + + + {networkData.name} Network + +
+ + {networkData.status} + + + {networkData.steps.length} steps + + +
+
+ +
+ {/* Network Steps */} + {networkData.steps.length > 0 && ( +
+
+ Execution Steps +
+ {networkData.steps.map((step, idx) => ( +
+
+ + {step.name} + + + {step.status} + +
+ + + +
+ ))} +
+ )} + + {/* Usage Statistics */} + {networkData.usage && ( +
+
+ Token Usage +
+
+ {networkData.usage.inputTokens !== + undefined && ( +
+
+ Input +
+
+ {networkData.usage.inputTokens.toLocaleString()} +
+
+ )} + {networkData.usage.outputTokens !== + undefined && ( +
+
+ Output +
+
+ {networkData.usage.outputTokens.toLocaleString()} +
+
+ )} + {networkData.usage.totalTokens !== + undefined && ( +
+
+ Total +
+
+ {networkData.usage.totalTokens.toLocaleString()} +
+
+ )} +
+
+ )} + + {/* Output */} + {networkData.output !== null && + networkData.output !== undefined && ( +
+
+ Network Output +
+ + + +
+ )} +
+
+
+ ) +} + function extractThoughtSummaryFromParts( parts: UIMessage['parts'] | undefined ): string { @@ -131,6 +508,29 @@ function resolveToolDisplayName(tool: ToolInvocationState): string { return 'unknown' } +/** + * Safely resolve a tool call id for use in React keys without using `any`. + * Falls back to the provided index if no string id is available. + */ +function getToolCallId(tool: unknown, fallbackIndex: number): string { + // Explicitly check for null and non-object values to satisfy strict boolean checks + if (tool === null || typeof tool !== 'object') { + return `idx-${fallbackIndex}` + } + + const maybeTool = tool as { toolCallId?: unknown; id?: unknown } + + if (typeof maybeTool.toolCallId === 'string' && maybeTool.toolCallId.trim().length > 0) { + return maybeTool.toolCallId + } + + if (typeof maybeTool.id === 'string' && maybeTool.id.trim().length > 0) { + return maybeTool.id + } + + return `idx-${fallbackIndex}` +} + // Extract extractTasksFromText to module level to fix scope issues function extractTasksFromText(content: string): AgentTaskData[] { const taskSections: AgentTaskData[] = [] @@ -189,14 +589,16 @@ function extractTasksFromText(content: string): AgentTaskData[] { function CopyButton({ text }: { text: string }) { const [copied, setCopied] = useState(false) - const handleCopy = useCallback(async () => { - try { - await navigator.clipboard.writeText(text) - setCopied(true) - setTimeout(() => setCopied(false), 2000) - } catch (err) { - console.error('Failed to copy:', err) - } + const handleCopy = useCallback(() => { + navigator.clipboard + .writeText(text) + .then(() => { + setCopied(true) + setTimeout(() => setCopied(false), 2000) + }) + .catch((err) => { + console.error('Failed to copy:', err) + }) }, [text]) return ( @@ -332,10 +734,10 @@ function MessageItem({ return tools.length > 0 ? tools : undefined }, [message.parts]) - const dataParts = useMemo(() => { + const dataParts = useMemo((): MastraDataPart[] => { const parts = message.parts ?? [] return parts.filter( - (p) => + (p): p is MastraDataPart => typeof (p as { type?: unknown }).type === 'string' && (p as { type: string }).type.startsWith('data-') ) @@ -605,26 +1007,49 @@ function MessageItem({ {isAssistant && dataParts.length > 0 && (
{dataParts.map((part, index) => { - const partType = (part as { type: string }) - .type + const partId = (part as { id?: string }).id + const partType = (part as { type: string }).type if (partType === 'data-tool-progress') { // Rendered separately below in the dedicated Tool progress panel. return null } - if ( - partType === 'data-tool-agent' || - partType === 'data-tool-workflow' || - partType === 'data-tool-network' - ) { - const nestedPart = part as any + const key = `${message.id}-${partType}-${partId ?? index}` + + if (isAgentDataPart(part)) { + return ( + + ) + } + + if (isWorkflowDataPart(part)) { + return ( + + ) + } + + if (isNetworkDataPart(part)) { + return ( + + ) + } + + // Render tool-specific data parts using the AgentTool UI when available so custom tool events are displayed nicely. + if (typeof partType === 'string' && partType.startsWith('data-tool-')) { return ( ) } @@ -633,7 +1058,7 @@ function MessageItem({ const { data } = part as { data?: unknown } return ( @@ -825,13 +1250,14 @@ function MessageItem({ .map((tool, idx) => { const resolvedName = resolveToolDisplayName(tool) + const callId = getToolCallId(tool, idx) return ( @@ -855,7 +1281,9 @@ function MessageItem({ showTools && messageTools && messageTools.length > 0 && ( - +
+ +
)} {/* Sources */} diff --git a/app/chat/components/chat.types.ts b/app/chat/components/chat.types.ts index 2bf441f..e76c8d8 100644 --- a/app/chat/components/chat.types.ts +++ b/app/chat/components/chat.types.ts @@ -1,112 +1,111 @@ -import type { ReactNode } from 'react' import type { DynamicToolUIPart } from 'ai' +import type { ReactNode } from 'react' import type { ToolInvocationState as ChatToolInvocationState } from '../providers/chat-context-types' export interface Citation { - id: string - number: string - title: string - url: string - description?: string - quote?: string + id: string + number: string + title: string + url: string + description?: string + quote?: string } export interface AgentToolsProps { - tools: Array - className?: string + tools: Array + className?: string } export type TaskStepStatus = 'pending' | 'running' | 'completed' | 'error' export interface TaskStep { - id: string - text: string - status: TaskStepStatus - file?: { - name: string - icon?: string - } + id: string + text: string + status: TaskStepStatus + file?: { + name: string + icon?: string + } } export interface AgentTaskData { - title: string - steps: TaskStep[] + title: string + steps: TaskStep[] } export interface ArtifactData { - id: string - title: string - description?: string - type: 'code' | 'markdown' | 'json' | 'text' | 'html' | 'react' - language?: string - content: string + id: string + title: string + description?: string + type: 'code' | 'markdown' | 'json' | 'text' | 'html' | 'react' + language?: string + content: string } export interface PlanStep { - text: string - completed?: boolean + text: string + completed?: boolean } export interface AgentPlanData { - title: string - description: string - steps: PlanStep[] | string[] - isStreaming?: boolean - currentStep?: number + title: string + description: string + steps: PlanStep[] | string[] + isStreaming?: boolean + currentStep?: number } export interface ReasoningStep { - id: string - label: string - description?: string - status: 'complete' | 'active' | 'pending' - searchResults?: string[] - duration?: number + id: string + label: string + description?: string + status: 'complete' | 'active' | 'pending' + searchResults?: string[] + duration?: number } export interface AgentSuggestionsProps { - suggestions: string[] - // eslint-disable-next-line no-unused-vars - onSelect: (suggestion: string) => void - disabled?: boolean - className?: string + suggestions: string[] + onSelect: (suggestion: string) => void + disabled?: boolean + className?: string } export interface AgentSourcesProps { - sources: Array<{ url: string; title: string }> - className?: string - maxVisible?: number + sources: Array<{ url: string; title: string }> + className?: string + maxVisible?: number } export interface AgentReasoningProps { - reasoning: string - isStreaming: boolean - duration?: number - className?: string + reasoning: string + isStreaming: boolean + duration?: number + className?: string } export type ConfirmationSeverity = 'info' | 'warning' | 'danger' export interface QueuedTask { - id: string - title: string - description?: string - status: 'pending' | 'running' | 'completed' | 'failed' - createdAt?: Date - completedAt?: Date - error?: string + id: string + title: string + description?: string + status: 'pending' | 'running' | 'completed' | 'failed' + createdAt?: Date + completedAt?: Date + error?: string } export interface WebPreviewData { - id: string - url: string - title?: string - code?: string - language?: string - html?: string - editable?: boolean - showConsole?: boolean - height?: number + id: string + url: string + title?: string + code?: string + language?: string + html?: string + editable?: boolean + showConsole?: boolean + height?: number } export type InlineCitationRender = ReactNode[] diff --git a/app/chat/components/nested-agent-chat.tsx b/app/chat/components/nested-agent-chat.tsx index 7f61c8f..f5c292d 100644 --- a/app/chat/components/nested-agent-chat.tsx +++ b/app/chat/components/nested-agent-chat.tsx @@ -1,6 +1,6 @@ import { useChat } from '@ai-sdk/react' import { DefaultChatTransport } from 'ai' -import { useState } from 'react' +import { useState, type FormEvent } from 'react' import type { AgentDataPart } from '@mastra/ai-sdk' export function NestedAgentChat() { @@ -11,15 +11,25 @@ export function NestedAgentChat() { }), }) + const handleSubmit = (e: FormEvent) => { + e.preventDefault() + // Use a self-invoking async function to avoid returning a Promise from the event handler + void (async () => { + try { + await sendMessage({ text: input }) + } catch (err) { + // Log the error and allow the UI to continue + // eslint-disable-next-line no-console + console.error('Failed to send message', err) + } finally { + setInput('') + } + })() + } + return (
-
{ - e.preventDefault() - sendMessage({ text: input }) - setInput('') - }} - > + setInput(e.target.value)} diff --git a/app/chat/providers/chat-context-types.ts b/app/chat/providers/chat-context-types.ts index 4221812..78acead 100644 --- a/app/chat/providers/chat-context-types.ts +++ b/app/chat/providers/chat-context-types.ts @@ -103,44 +103,44 @@ export interface ChatContextValue { // Temporarily indicates an agent switch is in progress and sends are blocked // Actions - // eslint-disable-next-line no-unused-vars + sendMessage: (text: string, files?: File[]) => void stopGeneration: () => void clearMessages: () => void - // eslint-disable-next-line no-unused-vars + selectAgent: (agentId: string) => void - // eslint-disable-next-line no-unused-vars + selectModel: (modelId: string) => void dismissError: () => void - // eslint-disable-next-line no-unused-vars + setFocusMode: (enabled: boolean) => void // Task management - // eslint-disable-next-line no-unused-vars + addTask: (task: Omit) => string - // eslint-disable-next-line no-unused-vars + updateTask: (taskId: string, updates: Partial) => void - // eslint-disable-next-line no-unused-vars + removeTask: (taskId: string) => void // Confirmation management - // eslint-disable-next-line no-unused-vars + approveConfirmation: (confirmationId: string) => void - // eslint-disable-next-line no-unused-vars + rejectConfirmation: (confirmationId: string, reason?: string) => void // Checkpoint management - // eslint-disable-next-line no-unused-vars + createCheckpoint: (messageIndex: number, label?: string) => string - // eslint-disable-next-line no-unused-vars + restoreCheckpoint: (checkpointId: string) => void - // eslint-disable-next-line no-unused-vars + removeCheckpoint: (checkpointId: string) => void // Web Preview management - // eslint-disable-next-line no-unused-vars + setWebPreview: (preview: WebPreviewData | null) => void // Memory management - // eslint-disable-next-line no-unused-vars + setThreadId: (threadId: string) => void - // eslint-disable-next-line no-unused-vars + setResourceId: (resourceId: string) => void } diff --git a/app/chat/providers/chat-context.tsx b/app/chat/providers/chat-context.tsx index 06bc308..dcbae61 100644 --- a/app/chat/providers/chat-context.tsx +++ b/app/chat/providers/chat-context.tsx @@ -345,9 +345,10 @@ export function ChatProvider({ return } setChatError(null) - aiSendMessage({ + // Fire-and-forget to avoid returning a Promise where a void is expected. + void aiSendMessage({ text: text.trim(), - // @ts-ignore - attachments support in AI SDK v5 + // @ts-expect-error - attachments support in AI SDK v5 attachments: files, }) }, @@ -355,7 +356,7 @@ export function ChatProvider({ ) const stopGeneration = useCallback(() => { - stop() + void stop() }, [stop]) const clearMessages = useCallback(() => { diff --git a/app/components/api-components.tsx b/app/components/api-components.tsx index 09d6210..d660c56 100644 --- a/app/components/api-components.tsx +++ b/app/components/api-components.tsx @@ -1,6 +1,7 @@ 'use client' -import { ReactNode, useState } from 'react' +import type { ReactNode} from 'react'; +import { useState } from 'react' import { Button } from '@/ui/button' import { Badge } from '@/ui/badge' import { CopyIcon, CheckIcon } from 'lucide-react' diff --git a/app/components/charts/PieWidget.tsx b/app/components/charts/PieWidget.tsx index 8661b68..9d0f1ad 100644 --- a/app/components/charts/PieWidget.tsx +++ b/app/components/charts/PieWidget.tsx @@ -2,7 +2,7 @@ import { Legend, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts' -export type PieSlice = { name: string; value: number; fill?: string } +export interface PieSlice { name: string; value: number; fill?: string } interface PieWidgetProps { data: PieSlice[] diff --git a/app/dashboard/_components/data-table.tsx b/app/dashboard/_components/data-table.tsx index f8b9c54..fa354a9 100644 --- a/app/dashboard/_components/data-table.tsx +++ b/app/dashboard/_components/data-table.tsx @@ -22,7 +22,7 @@ export interface Column { } interface DataTableProps { - columns: Column[] + columns: Array> data: T[] searchPlaceholder?: string searchKey?: keyof T @@ -52,7 +52,7 @@ export function DataTable({ const [page, setPage] = useState(0) const filteredData = useMemo(() => { - if (!search || !searchKey) return data + if (!search || !searchKey) {return data} const searchLower = search.toLowerCase() return data.filter((row) => { const value = row[searchKey] @@ -64,9 +64,9 @@ export function DataTable({ }, [data, search, searchKey]) const sortedData = useMemo(() => { - if (!sortColumn) return filteredData + if (!sortColumn) {return filteredData} const column = columns.find((c) => c.id === sortColumn) - if (!column) return filteredData + if (!column) {return filteredData} return [...filteredData].sort((a, b) => { let aVal: unknown @@ -82,9 +82,9 @@ export function DataTable({ return 0 } - if (aVal === bVal) return 0 - if (aVal == null) return 1 - if (bVal == null) return -1 + if (aVal === bVal) {return 0} + if (aVal == null) {return 1} + if (bVal == null) {return -1} const comparison = aVal < bVal ? -1 : 1 return sortDirection === 'asc' ? comparison : -comparison @@ -113,7 +113,7 @@ export function DataTable({ } if (column.accessorKey) { const value = row[column.accessorKey] - if (value === null || value === undefined) return '-' + if (value === null || value === undefined) {return '-'} return String(value) } return '-' diff --git a/app/dashboard/_components/empty-state.tsx b/app/dashboard/_components/empty-state.tsx index 0ab1ac8..5fcf92d 100644 --- a/app/dashboard/_components/empty-state.tsx +++ b/app/dashboard/_components/empty-state.tsx @@ -1,6 +1,6 @@ import { cn } from '@/lib/utils' import { Button } from '@/ui/button' -import { LucideIcon } from 'lucide-react' +import type { LucideIcon } from 'lucide-react' interface EmptyStateProps { icon?: LucideIcon diff --git a/app/dashboard/_components/stat-card.tsx b/app/dashboard/_components/stat-card.tsx index 3b7d20a..8f56b3d 100644 --- a/app/dashboard/_components/stat-card.tsx +++ b/app/dashboard/_components/stat-card.tsx @@ -5,7 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/ui/card' import { Skeleton } from '@/ui/skeleton' import Link from 'next/link' import type { Route } from 'next' -import { LucideIcon } from 'lucide-react' +import type { LucideIcon } from 'lucide-react' interface StatCardProps { title: string diff --git a/app/networks/components/network-chat.tsx b/app/networks/components/network-chat.tsx index 3174c69..8a6a401 100644 --- a/app/networks/components/network-chat.tsx +++ b/app/networks/components/network-chat.tsx @@ -266,7 +266,15 @@ function ArtifactDisplay({ navigator.clipboard.writeText(content)} + onClick={() => { + void (async () => { + try { + await navigator.clipboard.writeText(content) + } catch (error) { + console.error(error) + } + })() + }} /> {onClose && } @@ -287,7 +295,7 @@ function WebPreviewDisplay({ url, title }: { url: string; title?: string }) { } return ( -
+
{title &&
{title}
} @@ -493,23 +501,47 @@ function NetworkMessageParts({ (p.type.startsWith('data-tool') || p.type === 'data-network') ) { // Convert Mastra parts to dynamic-tool format - const payload = (p as any).data ?? p - const inner = payload?.data ?? payload + interface MastraData { + toolCallId?: string + id?: string + toolName?: string + name?: string + agentName?: string + input?: unknown + args?: unknown + output?: unknown + result?: unknown + errorText?: string + error?: unknown + data?: MastraData + } + + interface MastraPart { + type: string + data?: MastraData + } + + const mastraPart = p as unknown as MastraPart + const payload = mastraPart.data ?? (p as unknown as MastraData) + const inner = payload.data ?? payload + toolParts.push({ type: 'dynamic-tool', toolCallId: - inner?.toolCallId ?? - inner?.id ?? + inner.toolCallId ?? + inner.id ?? `tool-${message.id}-${partIndex}`, toolName: - inner?.toolName ?? - inner?.name ?? - inner?.agentName ?? + inner.toolName ?? + inner.name ?? + inner.agentName ?? 'network-step', - input: inner?.input ?? inner?.args, - output: inner?.output ?? inner?.result, - errorText: inner?.errorText ?? inner?.error, - state: inner?.output ? 'output-available' : 'input-available', + input: inner.input ?? inner.args, + output: inner.output ?? inner.result, + errorText: + inner.errorText ?? + (typeof inner.error === 'string' ? inner.error : undefined), + state: inner.output ? 'output-available' : 'input-available', } as ToolInvocationState) } } @@ -682,7 +714,8 @@ function NetworkMessageParts({ // Already handled above return null - case 'file': { // Handle file/image parts with AIImage + case 'file': { + // Handle file/image parts with AIImage const filePart: FileUIPart = part const fileData = filePart as { mediaType?: string @@ -777,11 +810,30 @@ function NetworkMessageParts({ (part.type.startsWith('data-tool') || part.type === 'data-network') ) { - const payload = - (part as Record).data ?? part + interface MastraData { + toolCallId?: string + id?: string + toolName?: string + name?: string + agentName?: string + input?: unknown + args?: unknown + output?: unknown + result?: unknown + errorText?: string + error?: unknown + data?: MastraData + } + + interface MastraPart { + type: string + data?: MastraData + } + + const mastraPart = part as MastraPart + const payload = mastraPart.data ?? mastraPart const inner = - (payload as Record).data ?? - payload + (payload as MastraData).data ?? payload const toolName = ((inner as Record) ?.toolName as string) ?? @@ -913,7 +965,9 @@ function NetworkMessage({ - navigator.clipboard.writeText(textPart.text) + void navigator.clipboard.writeText( + textPart.text + ) } label="Copy" tooltip="Copy to clipboard" @@ -1429,7 +1483,7 @@ export function NetworkChat() { value={selectedNetwork} onValueChange={(value) => selectNetwork(value)} > - + @@ -1448,7 +1502,13 @@ export function NetworkChat() { { + stopExecution() + } + : undefined + } /> diff --git a/app/networks/components/network-info-panel.tsx b/app/networks/components/network-info-panel.tsx index 5a592b5..346fb8e 100644 --- a/app/networks/components/network-info-panel.tsx +++ b/app/networks/components/network-info-panel.tsx @@ -219,7 +219,7 @@ function AllNetworksOverview() {
{(['routing', 'pipeline', 'research'] as NetworkCategory[]).map( (category) => { - const networks = networksByCategory[category] + const networks = networksByCategory[category] as Array<{ id: string; name: string; description?: string }> if (networks.length === 0) { return null } @@ -256,9 +256,7 @@ function AllNetworksOverview() { +