"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RemoteOllamaInterface = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const outline_1 = require("@heroicons/react/24/outline");
const RemoteOllamaInterface = ({ endpoints: initialEndpoints = [], onEndpointChange, onMessage, className = '', autoSelectOptimal = true, showEndpointSelector = true, credentials, }) => {
    const [endpoints, setEndpoints] = (0, react_1.useState)(initialEndpoints);
    const [selectedEndpoint, setSelectedEndpoint] = (0, react_1.useState)(null);
    const [messages, setMessages] = (0, react_1.useState)([]);
    const [currentMessage, setCurrentMessage] = (0, react_1.useState)('');
    const [isLoading, setIsLoading] = (0, react_1.useState)(false);
    const [isDiscovering, setIsDiscovering] = (0, react_1.useState)(false);
    const [models, setModels] = (0, react_1.useState)([]);
    const [selectedModel, setSelectedModel] = (0, react_1.useState)('llama3.2:latest');
    const messagesEndRef = (0, react_1.useRef)(null);
    /**
     * Initialize endpoints and discover available ones
     */
    (0, react_1.useEffect)(() => {
        if (initialEndpoints.length === 0) {
            discoverEndpoints();
        }
        else {
            setEndpoints(initialEndpoints);
            if (autoSelectOptimal) {
                selectOptimalEndpoint(initialEndpoints);
            }
        }
    }, [initialEndpoints, autoSelectOptimal]);
    /**
     * Auto-scroll to bottom when new messages arrive
     */
    (0, react_1.useEffect)(() => {
        messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
    }, [messages]);
    /**
     * Check endpoint health periodically
     */
    (0, react_1.useEffect)(() => {
        const interval = setInterval(() => {
            if (endpoints.length > 0) {
                checkEndpointHealth();
            }
        }, 30000); // Check every 30 seconds
        return () => clearInterval(interval);
    }, [endpoints]);
    /**
     * Load available models when endpoint changes
     */
    (0, react_1.useEffect)(() => {
        if (selectedEndpoint) {
            loadModels(selectedEndpoint);
            onEndpointChange?.(selectedEndpoint);
        }
    }, [selectedEndpoint, onEndpointChange]);
    /**
     * Discover available Ollama endpoints
     */
    const discoverEndpoints = (0, react_1.useCallback)(async () => {
        setIsDiscovering(true);
        const candidateEndpoints = [
            {
                url: 'http://localhost:11434',
                type: 'local',
                authenticated: false,
                name: 'Local Ollama',
            },
            // Add discovered ngrok endpoints
            ...(await discoverNgrokEndpoints()),
            // Add configured cloud endpoints
            ...(await loadCloudEndpoints()),
        ];
        const discoveredEndpoints = [];
        for (const candidate of candidateEndpoints) {
            const health = await checkEndpointHealth(candidate);
            discoveredEndpoints.push({
                ...candidate,
                health,
            });
        }
        setEndpoints(discoveredEndpoints);
        if (autoSelectOptimal) {
            selectOptimalEndpoint(discoveredEndpoints);
        }
        setIsDiscovering(false);
    }, [autoSelectOptimal]);
    /**
     * Discover ngrok endpoints from environment or API
     */
    const discoverNgrokEndpoints = async () => {
        const ngrokEndpoints = [];
        // Try to get ngrok tunnel info from environment
        const ngrokUrl = process.env.REACT_APP_NGROK_OLLAMA_URL;
        if (ngrokUrl) {
            ngrokEndpoints.push({
                url: ngrokUrl,
                type: 'remote',
                authenticated: true,
                name: 'Ngrok Tunnel',
            });
        }
        // Try to discover local ngrok tunnels
        try {
            const response = await fetch('http://localhost:4040/api/tunnels');
            if (response.ok) {
                const data = await response.json();
                for (const tunnel of data.tunnels) {
                    if (tunnel.config?.addr === '11434' || tunnel.config?.addr === 'localhost:11434') {
                        ngrokEndpoints.push({
                            url: tunnel.public_url,
                            type: 'remote',
                            authenticated: tunnel.config?.basic_auth !== undefined,
                            name: `Ngrok (${tunnel.name})`,
                        });
                    }
                }
            }
        }
        catch (error) {
            console.debug('Could not discover local ngrok tunnels:', error);
        }
        return ngrokEndpoints;
    };
    /**
     * Load cloud endpoints from configuration
     */
    const loadCloudEndpoints = async () => {
        const cloudEndpoints = [];
        // Add configured cloud endpoints
        const cloudUrl = process.env.REACT_APP_CLOUD_OLLAMA_URL;
        if (cloudUrl) {
            cloudEndpoints.push({
                url: cloudUrl,
                type: 'cloud',
                authenticated: true,
                name: 'Cloud Ollama',
            });
        }
        return cloudEndpoints;
    };
    /**
     * Check health of a specific endpoint
     */
    const checkEndpointHealth = async (endpoint) => {
        try {
            const start = Date.now();
            const response = await fetch(`${endpoint.url}/api/tags`, {
                method: 'GET',
                headers: getAuthHeaders(endpoint),
                signal: AbortSignal.timeout(5000),
            });
            const latency = Date.now() - start;
            if (response.ok) {
                // Update latency if this is an existing endpoint
                if ('health' in endpoint) {
                    endpoint.latency = latency;
                }
                return 'healthy';
            }
            return 'unhealthy';
        }
        catch (error) {
            return 'unhealthy';
        }
    };
    /**
     * Check health of all endpoints
     */
    const checkEndpointHealth = (0, react_1.useCallback)(async () => {
        const updatedEndpoints = await Promise.all(endpoints.map(async (endpoint) => ({
            ...endpoint,
            health: 'checking',
        })));
        setEndpoints(updatedEndpoints);
        const healthChecks = endpoints.map(async (endpoint) => {
            const health = await checkEndpointHealth(endpoint);
            return { ...endpoint, health };
        });
        const checkedEndpoints = await Promise.all(healthChecks);
        setEndpoints(checkedEndpoints);
    }, [endpoints]);
    /**
     * Select optimal endpoint based on health and latency
     */
    const selectOptimalEndpoint = (0, react_1.useCallback)((endpointList) => {
        const healthyEndpoints = endpointList.filter(ep => ep.health === 'healthy');
        if (healthyEndpoints.length === 0) {
            setSelectedEndpoint(null);
            return;
        }
        // Sort by preference: local > remote > cloud, then by latency
        const sorted = healthyEndpoints.sort((a, b) => {
            const typeOrder = { local: 0, remote: 1, cloud: 2 };
            const aOrder = typeOrder[a.type];
            const bOrder = typeOrder[b.type];
            if (aOrder !== bOrder) {
                return aOrder - bOrder;
            }
            return (a.latency || Infinity) - (b.latency || Infinity);
        });
        setSelectedEndpoint(sorted[0]);
    }, []);
    /**
     * Load available models from endpoint
     */
    const loadModels = (0, react_1.useCallback)(async (endpoint) => {
        try {
            const response = await fetch(`${endpoint.url}/api/tags`, {
                headers: getAuthHeaders(endpoint),
            });
            if (response.ok) {
                const data = await response.json();
                const modelNames = data.models?.map((model) => model.name) || [];
                setModels(modelNames);
                // Set default model if current selection isn't available
                if (!modelNames.includes(selectedModel) && modelNames.length > 0) {
                    setSelectedModel(modelNames[0]);
                }
            }
        }
        catch (error) {
            console.error('Failed to load models:', error);
            setModels([]);
        }
    }, [selectedModel]);
    /**
     * Get authentication headers for endpoint
     */
    const getAuthHeaders = (endpoint) => {
        const headers = {};
        if (!endpoint.authenticated) {
            return headers;
        }
        if (credentials?.apiKey) {
            headers.Authorization = `Bearer ${credentials.apiKey}`;
        }
        else if (credentials?.username && credentials?.password) {
            const auth = btoa(`${credentials.username}:${credentials.password}`);
            headers.Authorization = `Basic ${auth}`;
        }
        return headers;
    };
    /**
     * Send message to selected endpoint
     */
    const sendMessage = (0, react_1.useCallback)(async () => {
        if (!currentMessage.trim() || !selectedEndpoint || isLoading) {
            return;
        }
        const userMessage = {
            id: Date.now().toString(),
            role: 'user',
            content: currentMessage.trim(),
            timestamp: new Date(),
            endpoint: selectedEndpoint.name,
        };
        setMessages(prev => [...prev, userMessage]);
        setCurrentMessage('');
        setIsLoading(true);
        try {
            const start = Date.now();
            const response = await fetch(`${selectedEndpoint.url}/api/generate`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    ...getAuthHeaders(selectedEndpoint),
                },
                body: JSON.stringify({
                    model: selectedModel,
                    prompt: userMessage.content,
                    stream: false,
                    options: {
                        temperature: 0.7,
                        num_predict: 2048,
                    },
                }),
            });
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }
            const data = await response.json();
            const latency = Date.now() - start;
            const assistantMessage = {
                id: (Date.now() + 1).toString(),
                role: 'assistant',
                content: data.response || 'No response received',
                timestamp: new Date(),
                endpoint: selectedEndpoint.name,
                latency,
            };
            setMessages(prev => [...prev, assistantMessage]);
            onMessage?.(assistantMessage);
        }
        catch (error) {
            const errorMessage = {
                id: (Date.now() + 1).toString(),
                role: 'assistant',
                content: `Error: ${error.message}`,
                timestamp: new Date(),
                endpoint: selectedEndpoint.name,
            };
            setMessages(prev => [...prev, errorMessage]);
        }
        finally {
            setIsLoading(false);
        }
    }, [currentMessage, selectedEndpoint, selectedModel, isLoading, onMessage, credentials]);
    /**
     * Handle key press in message input
     */
    const handleKeyPress = (e) => {
        if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            sendMessage();
        }
    };
    /**
     * Get endpoint status icon
     */
    const getEndpointIcon = (endpoint) => {
        const baseClasses = "h-5 w-5";
        switch (endpoint.type) {
            case 'local':
                return (0, jsx_runtime_1.jsx)(outline_1.ComputerDesktopIcon, { className: baseClasses });
            case 'remote':
                return (0, jsx_runtime_1.jsx)(outline_1.SignalIcon, { className: baseClasses });
            case 'cloud':
                return (0, jsx_runtime_1.jsx)(outline_1.CloudIcon, { className: baseClasses });
            default:
                return (0, jsx_runtime_1.jsx)(outline_1.ComputerDesktopIcon, { className: baseClasses });
        }
    };
    /**
     * Get health status color
     */
    const getHealthColor = (health) => {
        switch (health) {
            case 'healthy':
                return 'text-green-500';
            case 'unhealthy':
                return 'text-red-500';
            case 'checking':
                return 'text-yellow-500';
            default:
                return 'text-gray-500';
        }
    };
    return ((0, jsx_runtime_1.jsxs)("div", { className: `flex flex-col h-full bg-white dark:bg-gray-900 ${className}`, children: [showEndpointSelector && ((0, jsx_runtime_1.jsxs)("div", { className: "border-b border-gray-200 dark:border-gray-700 p-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white", children: "Ollama Chat" }), (0, jsx_runtime_1.jsx)("button", { onClick: discoverEndpoints, disabled: isDiscovering, className: "px-3 py-1 text-sm bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50", children: isDiscovering ? 'Discovering...' : 'Refresh' })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center space-x-4", children: [(0, jsx_runtime_1.jsxs)("select", { value: selectedEndpoint?.url || '', onChange: (e) => {
                                    const endpoint = endpoints.find(ep => ep.url === e.target.value);
                                    setSelectedEndpoint(endpoint || null);
                                }, className: "flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white", children: [(0, jsx_runtime_1.jsx)("option", { value: "", children: "Select endpoint..." }), endpoints.map((endpoint) => ((0, jsx_runtime_1.jsxs)("option", { value: endpoint.url, children: [endpoint.name, " (", endpoint.type, ")", endpoint.latency && ` - ${endpoint.latency}ms`] }, endpoint.url)))] }), (0, jsx_runtime_1.jsx)("select", { value: selectedModel, onChange: (e) => setSelectedModel(e.target.value), disabled: !selectedEndpoint || models.length === 0, className: "px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white disabled:opacity-50", children: models.map((model) => ((0, jsx_runtime_1.jsx)("option", { value: model, children: model }, model))) })] }), selectedEndpoint && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center mt-2 text-sm", children: [getEndpointIcon(selectedEndpoint), (0, jsx_runtime_1.jsx)("span", { className: "ml-2 text-gray-600 dark:text-gray-400", children: selectedEndpoint.name }), (0, jsx_runtime_1.jsxs)("span", { className: `ml-2 ${getHealthColor(selectedEndpoint.health)}`, children: [selectedEndpoint.health === 'healthy' && (0, jsx_runtime_1.jsx)(outline_1.CheckCircleIcon, { className: "h-4 w-4 inline" }), selectedEndpoint.health === 'unhealthy' && (0, jsx_runtime_1.jsx)(outline_1.ExclamationTriangleIcon, { className: "h-4 w-4 inline" }), (0, jsx_runtime_1.jsx)("span", { className: "ml-1 capitalize", children: selectedEndpoint.health })] }), selectedEndpoint.latency && ((0, jsx_runtime_1.jsxs)("span", { className: "ml-2 text-gray-500", children: [selectedEndpoint.latency, "ms"] }))] }))] })), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 overflow-y-auto p-4 space-y-4", children: [messages.length === 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "text-center text-gray-500 dark:text-gray-400 mt-8", children: [(0, jsx_runtime_1.jsx)(outline_1.ChatBubbleLeftIcon, { className: "h-12 w-12 mx-auto mb-4 opacity-50" }), (0, jsx_runtime_1.jsx)("p", { children: "Start a conversation with Ollama" }), !selectedEndpoint && ((0, jsx_runtime_1.jsx)("p", { className: "text-sm mt-2", children: "Select an endpoint to begin" }))] })), messages.map((message) => ((0, jsx_runtime_1.jsx)("div", { className: `flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`, children: (0, jsx_runtime_1.jsxs)("div", { className: `max-w-xs lg:max-w-md px-4 py-2 rounded-lg ${message.role === 'user'
                                ? 'bg-blue-500 text-white'
                                : 'bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-white'}`, children: [(0, jsx_runtime_1.jsx)("div", { className: "whitespace-pre-wrap", children: message.content }), (0, jsx_runtime_1.jsxs)("div", { className: "text-xs mt-1 opacity-70", children: [message.timestamp.toLocaleTimeString(), message.endpoint && ` • ${message.endpoint}`, message.latency && ` • ${message.latency}ms`] })] }) }, message.id))), isLoading && ((0, jsx_runtime_1.jsx)("div", { className: "flex justify-start", children: (0, jsx_runtime_1.jsx)("div", { className: "bg-gray-200 dark:bg-gray-700 px-4 py-2 rounded-lg", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex space-x-1", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-2 h-2 bg-gray-500 rounded-full animate-bounce" }), (0, jsx_runtime_1.jsx)("div", { className: "w-2 h-2 bg-gray-500 rounded-full animate-bounce", style: { animationDelay: '0.1s' } }), (0, jsx_runtime_1.jsx)("div", { className: "w-2 h-2 bg-gray-500 rounded-full animate-bounce", style: { animationDelay: '0.2s' } })] }) }) })), (0, jsx_runtime_1.jsx)("div", { ref: messagesEndRef })] }), (0, jsx_runtime_1.jsx)("div", { className: "border-t border-gray-200 dark:border-gray-700 p-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex space-x-2", children: [(0, jsx_runtime_1.jsx)("textarea", { value: currentMessage, onChange: (e) => setCurrentMessage(e.target.value), onKeyPress: handleKeyPress, placeholder: selectedEndpoint ? "Type your message..." : "Select an endpoint to start chatting", disabled: !selectedEndpoint || isLoading, className: "flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white resize-none disabled:opacity-50", rows: 1, style: {
                                resize: 'none',
                                minHeight: '40px',
                                maxHeight: '120px',
                            } }), (0, jsx_runtime_1.jsx)("button", { onClick: sendMessage, disabled: !currentMessage.trim() || !selectedEndpoint || isLoading, className: "px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed", children: "Send" })] }) })] }));
};
exports.RemoteOllamaInterface = RemoteOllamaInterface;
//# sourceMappingURL=RemoteOllamaInterface.js.map