@alauda/doom
Version:
Doctor Doom making docs.
149 lines (148 loc) • 6.56 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { consumeSmartDocDisplayStream } from '@alauda/doc-stream-sdk';
import { clsx } from 'clsx';
import { useEffect, useRef, useState } from 'react';
import { Tooltip } from 'react-tooltip';
import { ApiMethod, interceptors, ResponseError, xfetch, } from 'x-fetch';
import { getCloudAuth, useCloudAuth } from "../../../login/store.js";
import { getCloudOrigin, isLoggedIn } from "../../../login/utils.js";
import { Chat } from "./Chat/index.js";
import { Preamble } from "./Preamble/index.js";
import { ResizableUserInput } from "./ResizableUserInput/index.js";
import { Thinking } from "./Thinking.js";
import CloseIcon from '@alauda/doom/assets/close.svg?react';
import LogoutIcon from '@alauda/doom/assets/logout.svg?react';
import NewChatIcon from '@alauda/doom/assets/new-chat.svg?react';
import { useMemoizedFn, useTranslation } from '@alauda/doom/runtime';
import classes from '@alauda/doom/styles/ai-assistant.module.scss';
export const AIAssistant = ({ open, onOpenChange }) => {
const t = useTranslation();
const { authInfo, setAuthBasic } = useCloudAuth();
const loggedIn = isLoggedIn(authInfo);
const sessionIdRef = useRef(null);
const [messages, setMessages] = useState([]);
const chatRef = useRef(null);
const onNewChat = useMemoizedFn(() => {
sessionIdRef.current = null;
setMessages([]);
});
const onLogout = useMemoizedFn(() => {
setAuthBasic();
onNewChat();
});
useEffect(() => {
const interceptor = async (req, next) => {
if (!req.url.startsWith('/smart/')) {
return next(req);
}
if (!req.headers.has('Authorization')) {
req.headers.set('Authorization', `Bearer ${authInfo.token}`);
}
if (!req.headers.has('CLOUD_AUTH_ORIGIN')) {
req.headers.set('CLOUD_AUTH_ORIGIN', getCloudOrigin());
}
try {
return await next(req);
}
catch (err) {
if (err instanceof ResponseError &&
// type-coverage:ignore-next-line -- no idea
err.response.status === 401) {
onLogout();
}
throw err;
}
};
interceptors.use(interceptor);
return () => {
interceptors.eject(interceptor);
};
}, [authInfo, onLogout]);
const onClose = useMemoizedFn(() => {
onOpenChange(false);
});
const flushMessages = useMemoizedFn((setMessagesAction) => {
setMessages(setMessagesAction);
setTimeout(() => {
const chatEl = chatRef.current;
if (!chatEl) {
return;
}
chatEl.scrollTop = chatEl.scrollHeight;
}, 200);
});
const assistantMessageIndexRef = useRef(-1);
const onSend_ = async (content) => {
const assistantMessage = {
id: Date.now(),
role: 'assistant',
content: _jsx(Thinking, {}),
};
const index = (assistantMessageIndexRef.current = messages.length + 1);
flushMessages((messages) => [
...messages,
{ id: Date.now(), role: 'user', content },
assistantMessage,
]);
if (!sessionIdRef.current) {
const { session_id } = await xfetch('/smart/api/new_session', {
method: ApiMethod.POST,
});
sessionIdRef.current = session_id;
}
const sessionId = sessionIdRef.current;
const res = await xfetch('/smart/api/smart_answer_with_search', {
type: null,
method: ApiMethod.POST,
body: {
input_text: content,
session_id: sessionId,
},
});
if (!res.body) {
return;
}
await consumeSmartDocDisplayStream(res.body, {
ignoreDocsBlocks: false,
onDisplayMessage(nextMessage) {
if (sessionId !== sessionIdRef.current) {
return;
}
flushMessages((messages) => [
...messages.slice(0, index),
{
...messages[index],
content: nextMessage.content,
thoughtProcess: nextMessage.thoughtProcess || undefined,
refDocs: nextMessage.refDocs,
},
...messages.slice(index + 1),
]);
},
});
};
const [loading, setLoading] = useState(false);
const onSend = useMemoizedFn(async (content) => {
setLoading(true);
try {
await onSend_(content);
}
catch {
if (!isLoggedIn(getCloudAuth())) {
return;
}
flushMessages((messages) => {
const index = assistantMessageIndexRef.current;
return [
...messages.slice(0, index),
{ ...messages[index], content: t('NetworkError') },
...messages.slice(index + 1),
];
});
}
finally {
setLoading(false);
}
});
return (_jsxs("div", { className: clsx(classes.container, 'rp-doc', open && classes.open), children: [_jsxs("div", { className: classes.header, children: [_jsxs("div", { className: classes.title, children: [t('ai_assistant'), loggedIn && (_jsxs(_Fragment, { children: [_jsxs("span", { className: classes.username, children: ["(", authInfo.detail.user.name, ")"] }), _jsx(LogoutIcon, { className: "ai-assistant-logout", onClick: onLogout }), _jsx(Tooltip, { anchorSelect: ".ai-assistant-logout", children: t('logout') })] }))] }), _jsxs("div", { className: classes.icons, children: [messages.length ? (_jsxs(_Fragment, { children: [_jsx(NewChatIcon, { className: "ai-assistant-new-chat", onClick: onNewChat }), _jsx(Tooltip, { anchorSelect: ".ai-assistant-new-chat", children: t('new_chat') })] })) : null, _jsx(CloseIcon, { className: "ai-assistant-close", onClick: onClose }), _jsx(Tooltip, { anchorSelect: ".ai-assistant-close", children: t('close') })] })] }), messages.length ? (_jsx(Chat, { ref: chatRef, messages: messages })) : (_jsx(Preamble, { loggedIn: loggedIn })), loggedIn && _jsx(ResizableUserInput, { loading: loading, onSend: onSend })] }));
};