import React, {useEffect, useRef, useState} from 'react';
import {Link} from 'react-router';
import {getAiCatalog, getChatModes} from '../api';
import {useAiChat} from '../contexts/AiChatContext';
import {InfoMessage} from './Common';
import {Button, Group, Icon, Loader, Text, TextInput} from './ui';
const MODE_LABELS = {help: 'Help', research: 'Research', system: 'System'};
const MODE_DESCRIPTIONS = {
help: 'Learn how to use WROLPi',
research: 'Find and summarize library content',
system: 'Troubleshoot this WROLPi',
};
// Render an assistant message with WROLPi links clickable. The model only ever returns
// relative links (/videos/123), which the server computed — never external URLs.
export function LinkifiedText({content}) {
const parts = String(content || '').split(/(\/[a-zA-Z0-9_\-./?=&%#]+)/g);
return
{parts.map((part, i) => part.startsWith('/') && part.length > 1
? {part}
: part)}
;
}
function ToolActivity({activity}) {
if (!activity || !activity.length) {
return null;
}
return
{activity.map((item, i) =>
{item.pending
?
: }
{' '}using {String(item.tool || '').replace(/_/g, ' ')}
)}
;
}
export function ChatMessage({message}) {
if (message.role === 'user') {
return
{message.content}
;
}
return
{message.status && {message.status}}
{message.content && }
{message.streaming && !message.content && !message.activity?.length && }
{message.error && {message.error}}
;
}
export function AiChat() {
const {mode, setMode, messages, sending, send, stop, disabledCode} = useAiChat();
const [modes, setModes] = useState([]);
const [slowHardware, setSlowHardware] = useState(false);
const [aiEnabled, setAiEnabled] = useState(null);
const [input, setInput] = useState('');
const bottomRef = useRef(null);
useEffect(() => {
getChatModes().then(i => i && setModes(i)).catch(console.error);
getAiCatalog().then(catalog => {
if (catalog) {
setSlowHardware(catalog.slow_hardware === true);
setAiEnabled(Boolean(catalog.enabled && catalog.active_model));
}
}).catch(console.error);
}, []);
useEffect(() => {
bottomRef.current?.scrollIntoView?.({behavior: 'smooth'});
}, [messages]);
const handleSend = async (content) => {
setInput('');
await send(content);
};
const notReady = aiEnabled === false || disabledCode === 'AI_DISABLED';
if (notReady) {
return
The AI assistant is not ready. Download a model and enable it on
the Manage tab.
;
}
const suggestions = (modes.find(i => i.name === mode) || {}).suggestions || [];
return <>
{slowHardware &&
This hardware runs the assistant slowly — answers can take minutes.
}
{(modes.length ? modes.map(i => i.name) : Object.keys(MODE_LABELS)).map(name =>
)}
{mode && {MODE_DESCRIPTIONS[mode]}}
{!mode &&
Pick a mode to start: Help teaches WROLPi, Research searches your
library, System troubleshoots this device.
}
{mode && <>
{messages.length === 0 &&
{suggestions.map(suggestion =>
)}
}
{messages.map((message, i) =>
)}
>}
>;
}