import { DraggableFieldState, IAssistantChatMessage, IChannelScales, IChatMessage, IUserChatMessage, IViewField } from '@/interfaces'; import { useCompututaion, useVizStore } from '@/store'; import { observer } from 'mobx-react-lite'; import React, { useMemo, useState, useContext } from 'react'; import { Card, CardContent, CardTitle } from '../ui/card'; import { CpuChipIcon, TrashIcon, UserIcon, ArrowPathIcon } from '@heroicons/react/24/outline'; import { Button } from '../ui/button'; import { useTranslation } from 'react-i18next'; import SpecRenderer from '@/renderer/specRenderer'; import { vegaThemeContext } from '@/store/theme'; import { useRenderer } from '@/renderer/hooks'; import { getSort, parseErrorMessage } from '@/utils'; import { useReporter } from '@/utils/reportError'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible'; import { Textarea } from '../ui/textarea'; import LoadingLayer from '../loadingLayer'; async function fetchQueryChat(api: string, metas: IViewField[], messages: IChatMessage[], headers: Record) { const res = await fetch(api, { headers: { 'Content-Type': 'application/json', ...headers, }, credentials: 'include', method: 'POST', body: JSON.stringify({ metas, messages, }), }); const result: { success: boolean; data: any; message?: string; } = await res.json(); if (result.success) { return result.data; } else { throw new Error(result.message); } } async function queryChat( api: string | ((metas: IViewField[], chats: IChatMessage[]) => PromiseLike | any), data: { metas: IViewField[]; chats: IChatMessage[]; query: string; }, headers: Record ) { const chats = data.chats.concat({ role: 'user', content: data.query, type: 'normal', }); if (typeof api === 'string') { return fetchQueryChat(api, data.metas, chats, headers); } return api(data.metas, chats); } function UserMessage(props: { message: IUserChatMessage; onRemove?: () => void }) { const collapasable = props.message.type === 'generated'; return (
You {props.onRemove && ( )}
{collapasable && ( Click to expand auto generated message {props.message.content} )} {!collapasable && props.message.content}
); } const AssistantMessage = observer(function AssistantMessage(props: { message: IAssistantChatMessage; onRemove?: () => void; scales?: IChannelScales }) { const computation = useCompututaion(); const { config, encodings, layout, name } = props.message.chart; const { vizThemeConfig } = useContext(vegaThemeContext); const sort = getSort(encodings); const { allFields, viewDimensions, viewMeasures, filters } = useMemo(() => { const viewDimensions: IViewField[] = []; const viewMeasures: IViewField[] = []; const { dimensions, measures, filters, ...state } = encodings; const allFields = [...dimensions, ...measures]; const dKeys = Object.keys(state) as (keyof DraggableFieldState)[]; for (const dKey of dKeys) { for (const f of state[dKey]) { if (f.analyticType === 'dimension') { viewDimensions.push(f); } else if (f.analyticType === 'measure') { viewMeasures.push(f); } } } return { allFields, viewDimensions, viewMeasures, filters }; }, [encodings]); const { viewData: data, loading: waiting } = useRenderer({ allFields, viewDimensions, viewMeasures, filters, defaultAggregated: config.defaultAggregated, sort, folds: config.folds, limit: config.limit ?? -1, computationFunction: computation, timezoneDisplayOffset: config['timezoneDisplayOffset'], }); const { i18n } = useTranslation(); return (
Viz.GPT {props.onRemove && ( )}
{waiting && }
); }); const api = 'https://api.kanaries.net/vis/chat2gw'; export const VegaliteChat = observer(function VegaliteChat(props: { api: string | ((metas: IViewField[], chats: IChatMessage[]) => PromiseLike | any); headers?: Record; scales?: IChannelScales; }) { const [query, setQuery] = useState(''); const [loading, setLoading] = useState(false); const vizStore = useVizStore(); const { chatMessages, allFields } = vizStore; const { reportError } = useReporter(); const submit = async () => { setLoading(true); queryChat(props.api || api, { chats: chatMessages, metas: allFields, query }, props.headers ?? {}) .then((res) => { vizStore.replaceWithNLPQuery(query, JSON.stringify(res)); setQuery(''); }) .catch((err) => { reportError(parseErrorMessage(err), 502); }) .finally(() => { setLoading(false); }); }; return (
{chatMessages.map((m, i, arr) => { if (m.role === 'assistant') { return ( vizStore.undo() : undefined} /> ); } if (m.role === 'user') { return vizStore.undo() : undefined} />; } })}