mirror of https://github.com/kortix-ai/suna.git
414 lines
15 KiB
TypeScript
414 lines
15 KiB
TypeScript
import { useTheme } from '@/hooks/useThemeColor';
|
|
import {
|
|
AlertTriangle,
|
|
Briefcase,
|
|
CheckCircle,
|
|
Database,
|
|
Home,
|
|
MessageCircle,
|
|
ShoppingBag,
|
|
TrendingUp,
|
|
Users
|
|
} from 'lucide-react-native';
|
|
import { ActivityIndicator, ScrollView, StyleSheet, View } from 'react-native';
|
|
import { Body, Caption } from '../Typography';
|
|
import { Card, CardContent } from '../ui/Card';
|
|
import { ToolViewProps } from './ToolViewRegistry';
|
|
|
|
interface DataProviderEndpointsToolViewProps extends ToolViewProps {
|
|
assistantContent?: string;
|
|
toolContent?: string;
|
|
assistantTimestamp?: string;
|
|
toolTimestamp?: string;
|
|
toolCall?: any;
|
|
}
|
|
|
|
const PROVIDER_CONFIG = {
|
|
'linkedin': {
|
|
name: 'LinkedIn Data Provider',
|
|
icon: Users,
|
|
color: '#0077B5',
|
|
},
|
|
'twitter': {
|
|
name: 'Twitter Data Provider',
|
|
icon: MessageCircle,
|
|
color: '#1DA1F2',
|
|
},
|
|
'zillow': {
|
|
name: 'Zillow Data Provider',
|
|
icon: Home,
|
|
color: '#10B981',
|
|
},
|
|
'amazon': {
|
|
name: 'Amazon Data Provider',
|
|
icon: ShoppingBag,
|
|
color: '#F59E0B',
|
|
},
|
|
'yahoo_finance': {
|
|
name: 'Yahoo Finance Data Provider',
|
|
icon: TrendingUp,
|
|
color: '#8B5CF6',
|
|
},
|
|
'active_jobs': {
|
|
name: 'Active Jobs Data Provider',
|
|
icon: Briefcase,
|
|
color: '#6366F1',
|
|
}
|
|
};
|
|
|
|
const extractDataProviderEndpointsData = (toolCall?: any, toolContent?: string) => {
|
|
let serviceName = '';
|
|
let endpoints: any = {};
|
|
let isSuccess = true;
|
|
let errorMessage = '';
|
|
|
|
// Extract from tool call parameters
|
|
if (toolCall?.parameters) {
|
|
serviceName = toolCall.parameters.service_name || toolCall.parameters.serviceName || '';
|
|
endpoints = toolCall.parameters.endpoints || {};
|
|
}
|
|
|
|
// Parse tool content if available
|
|
if (toolContent) {
|
|
try {
|
|
const parsed = JSON.parse(toolContent);
|
|
|
|
if (parsed.tool_execution) {
|
|
const toolExecution = parsed.tool_execution;
|
|
|
|
// Extract arguments
|
|
if (toolExecution.arguments) {
|
|
serviceName = toolExecution.arguments.service_name || toolExecution.arguments.serviceName || serviceName;
|
|
endpoints = toolExecution.arguments.endpoints || endpoints;
|
|
}
|
|
|
|
// Extract result
|
|
if (toolExecution.result) {
|
|
const result = toolExecution.result;
|
|
|
|
if (result.success !== undefined) {
|
|
isSuccess = result.success;
|
|
}
|
|
|
|
if (result.error) {
|
|
errorMessage = result.error;
|
|
isSuccess = false;
|
|
}
|
|
|
|
if (result.output) {
|
|
if (typeof result.output === 'string') {
|
|
try {
|
|
const outputParsed = JSON.parse(result.output);
|
|
serviceName = outputParsed.service_name || outputParsed.serviceName || serviceName;
|
|
endpoints = outputParsed.endpoints || endpoints;
|
|
} catch {
|
|
// If not JSON, treat as service name
|
|
serviceName = result.output;
|
|
}
|
|
} else {
|
|
serviceName = result.output.service_name || result.output.serviceName || serviceName;
|
|
endpoints = result.output.endpoints || endpoints;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error parsing tool content:', error);
|
|
}
|
|
}
|
|
|
|
return {
|
|
serviceName,
|
|
endpoints,
|
|
isSuccess,
|
|
errorMessage
|
|
};
|
|
};
|
|
|
|
export function DataProviderEndpointsToolView({
|
|
name = 'get-data-provider-endpoints',
|
|
toolCall,
|
|
toolContent,
|
|
isStreaming = false,
|
|
isSuccess = true,
|
|
...props
|
|
}: DataProviderEndpointsToolViewProps) {
|
|
const theme = useTheme();
|
|
|
|
// Convert color-mix(in oklab, var(--muted) 20%, transparent) to hex
|
|
const mutedBg = theme.muted === '#e8e8e8' ? '#e8e8e833' : '#30303033';
|
|
|
|
const extractedData = extractDataProviderEndpointsData(toolCall, toolContent);
|
|
const { serviceName, endpoints, isSuccess: actualIsSuccess, errorMessage } = extractedData;
|
|
|
|
const providerConfig = serviceName && PROVIDER_CONFIG[serviceName as keyof typeof PROVIDER_CONFIG]
|
|
? PROVIDER_CONFIG[serviceName as keyof typeof PROVIDER_CONFIG]
|
|
: PROVIDER_CONFIG['linkedin'];
|
|
|
|
const IconComponent = providerConfig.icon;
|
|
const endpointCount = endpoints && typeof endpoints === 'object' ? Object.keys(endpoints).length : 0;
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: theme.background,
|
|
padding: 16,
|
|
},
|
|
loadingContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
gap: 16,
|
|
},
|
|
emptyState: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
gap: 16,
|
|
},
|
|
section: {
|
|
marginBottom: 16,
|
|
},
|
|
sectionTitle: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginBottom: 8,
|
|
gap: 8,
|
|
},
|
|
sectionTitleText: {
|
|
color: theme.foreground,
|
|
fontWeight: '600' as const,
|
|
},
|
|
providerHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
},
|
|
providerIcon: {
|
|
width: 48,
|
|
height: 48,
|
|
borderRadius: 12,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
backgroundColor: providerConfig.color,
|
|
},
|
|
providerInfo: {
|
|
flex: 1,
|
|
},
|
|
providerName: {
|
|
color: theme.foreground,
|
|
fontWeight: '600' as const,
|
|
marginBottom: 4,
|
|
},
|
|
providerDescription: {
|
|
color: theme.mutedForeground,
|
|
},
|
|
statusBadge: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 4,
|
|
borderRadius: 12,
|
|
backgroundColor: actualIsSuccess ? theme.secondary : theme.destructive,
|
|
gap: 4,
|
|
},
|
|
statusText: {
|
|
fontSize: 12,
|
|
fontWeight: '500' as const,
|
|
color: actualIsSuccess ? theme.secondaryForeground : theme.destructiveForeground,
|
|
},
|
|
statusItem: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
marginBottom: 8,
|
|
},
|
|
statusLeft: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
},
|
|
statusDot: {
|
|
width: 8,
|
|
height: 8,
|
|
borderRadius: 4,
|
|
},
|
|
statusLabel: {
|
|
color: theme.foreground,
|
|
fontWeight: '500' as const,
|
|
},
|
|
statusValue: {
|
|
color: theme.mutedForeground,
|
|
fontFamily: 'monospace',
|
|
},
|
|
successIcon: {
|
|
color: theme.secondary,
|
|
},
|
|
successText: {
|
|
color: theme.secondary,
|
|
},
|
|
errorText: {
|
|
color: theme.destructive,
|
|
},
|
|
});
|
|
|
|
const renderLoadingState = () => (
|
|
<View style={styles.loadingContainer}>
|
|
<ActivityIndicator size="large" color={theme.secondary} />
|
|
<Body style={{ color: theme.mutedForeground, textAlign: 'center' }}>
|
|
Loading provider...
|
|
</Body>
|
|
<Caption style={{ color: theme.mutedForeground, textAlign: 'center' }}>
|
|
Connecting to data source
|
|
</Caption>
|
|
</View>
|
|
);
|
|
|
|
const renderContent = () => {
|
|
if (!actualIsSuccess && errorMessage) {
|
|
return (
|
|
<View style={styles.emptyState}>
|
|
<AlertTriangle size={48} color={theme.destructive} />
|
|
<Body style={[styles.errorText, { textAlign: 'center' }]}>
|
|
{errorMessage}
|
|
</Body>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<ScrollView style={{ flex: 1 }}>
|
|
{/* Provider Section */}
|
|
<View style={styles.section}>
|
|
<View style={styles.sectionTitle}>
|
|
<Database size={16} color={theme.foreground} />
|
|
<Body style={styles.sectionTitleText}>Data Provider</Body>
|
|
</View>
|
|
<Card
|
|
style={{
|
|
backgroundColor: mutedBg,
|
|
borderColor: theme.muted,
|
|
}}
|
|
bordered
|
|
elevated={false}
|
|
>
|
|
<CardContent style={{ padding: 0 }}>
|
|
<View style={styles.providerHeader}>
|
|
<View style={styles.providerIcon}>
|
|
<IconComponent size={24} color="#ffffff" />
|
|
</View>
|
|
|
|
<View style={styles.providerInfo}>
|
|
<Body style={styles.providerName}>
|
|
{providerConfig.name}
|
|
</Body>
|
|
<Caption style={styles.providerDescription}>
|
|
{endpointCount > 0 ? `${endpointCount} endpoints loaded and ready` : 'Endpoints loaded and ready'}
|
|
</Caption>
|
|
</View>
|
|
|
|
<View style={styles.statusBadge}>
|
|
{actualIsSuccess ? (
|
|
<CheckCircle size={12} color={theme.secondaryForeground} />
|
|
) : (
|
|
<AlertTriangle size={12} color={theme.destructiveForeground} />
|
|
)}
|
|
<Caption style={styles.statusText}>
|
|
{actualIsSuccess ? 'Connected' : 'Failed'}
|
|
</Caption>
|
|
</View>
|
|
</View>
|
|
</CardContent>
|
|
</Card>
|
|
</View>
|
|
|
|
{/* Status Section */}
|
|
<View style={styles.section}>
|
|
<View style={styles.sectionTitle}>
|
|
<CheckCircle size={16} color={theme.foreground} />
|
|
<Body style={styles.sectionTitleText}>Status Overview</Body>
|
|
</View>
|
|
<Card
|
|
style={{
|
|
backgroundColor: mutedBg,
|
|
borderColor: theme.muted,
|
|
}}
|
|
bordered
|
|
elevated={false}
|
|
>
|
|
<CardContent style={{ padding: 0 }}>
|
|
<View style={styles.statusItem}>
|
|
<View style={styles.statusLeft}>
|
|
<View style={[styles.statusDot, { backgroundColor: theme.secondary }]} />
|
|
<Body style={styles.statusLabel}>Connection Status</Body>
|
|
</View>
|
|
<View style={styles.statusBadge}>
|
|
{actualIsSuccess ? (
|
|
<CheckCircle size={12} color={theme.secondaryForeground} />
|
|
) : (
|
|
<AlertTriangle size={12} color={theme.destructiveForeground} />
|
|
)}
|
|
<Caption style={styles.statusText}>
|
|
{actualIsSuccess ? 'Active' : 'Inactive'}
|
|
</Caption>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.statusItem}>
|
|
<View style={styles.statusLeft}>
|
|
<View style={[styles.statusDot, { backgroundColor: '#3b82f6' }]} />
|
|
<Body style={styles.statusLabel}>Endpoints Available</Body>
|
|
</View>
|
|
<Caption style={styles.statusValue}>
|
|
{endpointCount > 0 ? `${endpointCount} endpoints` : 'Ready'}
|
|
</Caption>
|
|
</View>
|
|
|
|
<View style={styles.statusItem}>
|
|
<View style={styles.statusLeft}>
|
|
<View style={[styles.statusDot, { backgroundColor: '#8b5cf6' }]} />
|
|
<Body style={styles.statusLabel}>Data Provider</Body>
|
|
</View>
|
|
<Caption style={styles.statusValue}>
|
|
{serviceName || 'linkedin'}
|
|
</Caption>
|
|
</View>
|
|
</CardContent>
|
|
</Card>
|
|
</View>
|
|
|
|
{/* Success Message */}
|
|
{actualIsSuccess && (
|
|
<View style={styles.section}>
|
|
<Card
|
|
style={{
|
|
backgroundColor: mutedBg,
|
|
borderColor: theme.secondary,
|
|
}}
|
|
bordered
|
|
elevated={false}
|
|
>
|
|
<CardContent style={{ padding: 0 }}>
|
|
<View style={styles.sectionTitle}>
|
|
<CheckCircle size={16} color={theme.secondary} />
|
|
<Body style={[styles.sectionTitleText, { color: theme.secondary }]}>
|
|
Provider Ready
|
|
</Body>
|
|
</View>
|
|
<Caption style={[styles.successText, { marginTop: 0 }]}>
|
|
Data provider endpoints have been loaded successfully and are ready to process requests.
|
|
</Caption>
|
|
</CardContent>
|
|
</Card>
|
|
</View>
|
|
)}
|
|
</ScrollView>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
{isStreaming ? renderLoadingState() : renderContent()}
|
|
</View>
|
|
);
|
|
}
|