@antv/dumi-theme-antv
Version:
AntV website theme based on dumi2.
407 lines (399 loc) • 18.5 kB
JavaScript
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
import { BranchesOutlined, CheckOutlined, CopyOutlined, DeleteOutlined, PlusSquareOutlined, SyncOutlined, ToolOutlined } from '@ant-design/icons';
import { Bubble } from '@ant-design/x';
import { Button, Flex, Space, Tooltip } from 'antd';
import { useChat } from '@ai-sdk/react';
import { TextStreamChatTransport } from 'ai';
import { useIntl, useSiteData } from 'dumi';
import { findLast } from 'lodash-es';
import React, { useEffect, useState, useMemo, useRef } from 'react';
import { useCopyToClipboard } from 'react-use';
import { useSnapshot } from 'valtio';
import { PromptTextarea } from "../../../../components/AI/HomeDialog/PromptTextarea";
import { AIChatStore, branchMessage, clearEmptySession, createPureNewSession, deleteMessage, derivedState } from "../../../../model/AIChat";
import { getCodeFromMarkdown, isPreviewable } from "../../../../utils/code";
import { MarkdownComponent } from "../MarkdownComponent";
import styles from "./index.module.less";
import { useAutoScroll } from "./useAutoScroll";
import { getBaseURL } from "../../../../utils/env";
import { AIMode } from "../../../../components/AI/constant";
import { trackEvent } from "../../../../utils/analytics";
var avatar = {
icon: /*#__PURE__*/React.createElement("img", {
draggable: false,
src: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*7svFR6wkPMoAAAAAAAAAAAAADmJ7AQ/original',
alt: "AntV"
}),
style: {
borderRadius: 0,
backgroundColor: 'transparent'
}
};
var chatScrollIntoView = function chatScrollIntoView() {
setTimeout(function () {
document.getElementById('msgBoxAnchor').scrollIntoView({
block: 'end'
});
}, 100);
};
// 辅助函数:将旧的 Message 格式转换为新的 UIMessage 格式
var convertToUIMessages = function convertToUIMessages(messages) {
return messages.map(function (msg) {
return {
id: msg.id,
role: msg.role,
parts: [{
type: 'text',
text: msg.content
}]
};
});
};
// 辅助函数:从 UIMessage 提取文本内容
var getTextContent = function getTextContent(message) {
return message.parts.filter(function (part) {
return part.type === 'text';
}).map(function (part) {
return part.text;
}).join('');
};
function MsgBox(props) {
var _derivedSnap$activeSe, _derivedSnap$activeSe2, _derivedSnap$activeSe4, _derivedSnap$activeSe7, _derivedSnap$activeSe8, _messages;
var _props$messages = props.messages,
initialMessages = _props$messages === void 0 ? [] : _props$messages,
_props$simple = props.simple,
simple = _props$simple === void 0 ? false : _props$simple,
onCodegen = props.onCodegen,
title = props.title;
var _useIntl = useIntl(),
formatMessage = _useIntl.formatMessage;
var _useState = useState(''),
_useState2 = _slicedToArray(_useState, 2),
promptText = _useState2[0],
setPromptText = _useState2[1];
var _useState3 = useState(''),
_useState4 = _slicedToArray(_useState3, 2),
fileSummary = _useState4[0],
setFileSummary = _useState4[1];
var snap = useSnapshot(AIChatStore);
var derivedSnap = useSnapshot(derivedState);
var _useCopyToClipboard = useCopyToClipboard(),
_useCopyToClipboard2 = _slicedToArray(_useCopyToClipboard, 2),
copyState = _useCopyToClipboard2[0],
copyToClipboard = _useCopyToClipboard2[1];
var latestUserMessage = findLast((_derivedSnap$activeSe = derivedSnap.activeSession) === null || _derivedSnap$activeSe === void 0 ? void 0 : _derivedSnap$activeSe.messages, function (msg) {
return msg.role === 'user';
});
// 使用 ref 存储动态值,避免重新创建 transport
var anonymousUserIdRef = useRef(snap.anonymousUserId);
var activeSessionIdRef = useRef((_derivedSnap$activeSe2 = derivedSnap.activeSession) === null || _derivedSnap$activeSe2 === void 0 ? void 0 : _derivedSnap$activeSe2.id);
var _useSiteData = useSiteData(),
themeConfig = _useSiteData.themeConfig;
useEffect(function () {
var _derivedSnap$activeSe3;
anonymousUserIdRef.current = snap.anonymousUserId;
activeSessionIdRef.current = (_derivedSnap$activeSe3 = derivedSnap.activeSession) === null || _derivedSnap$activeSe3 === void 0 ? void 0 : _derivedSnap$activeSe3.id;
}, [snap.anonymousUserId, (_derivedSnap$activeSe4 = derivedSnap.activeSession) === null || _derivedSnap$activeSe4 === void 0 ? void 0 : _derivedSnap$activeSe4.id]);
// 转换初始消息为新格式
var convertedInitialMessages = useMemo(function () {
return convertToUIMessages(initialMessages);
}, [initialMessages]);
// 核心:使用 useChat hook,配置 DefaultChatTransport
var _useChat = useChat({
transport: new TextStreamChatTransport({
api: getBaseURL() + '/api/modules/antv/ai/chat',
credentials: 'include',
headers: {
// xxx
},
// body 可以是函数,用于获取最新的动态值
body: function body() {
return {
gptConversationId: activeSessionIdRef.current,
anonymousUserId: anonymousUserIdRef.current,
mountId: 'container',
antvContext: (latestUserMessage === null || latestUserMessage === void 0 ? void 0 : latestUserMessage.context) || props.context,
library: AIChatStore.lib,
mode: AIChatStore.mode
};
}
}),
messages: convertedInitialMessages,
// 当AI响应结束时触发
onFinish: function onFinish(_ref) {
var message = _ref.message,
isAbort = _ref.isAbort,
isDisconnect = _ref.isDisconnect,
isError = _ref.isError;
if (!isAbort && !isDisconnect && !isError) {
var _derivedState$activeS;
var messageContent = getTextContent(message);
if (isPreviewable(messageContent)) {
var codeBlock = getCodeFromMarkdown(messageContent).code;
AIChatStore.codeBlock = codeBlock;
onCodegen === null || onCodegen === void 0 || onCodegen(codeBlock);
} else {
AIChatStore.codeBlock = '';
}
(_derivedState$activeS = derivedState.activeSession) === null || _derivedState$activeS === void 0 || (_derivedState$activeS = _derivedState$activeS.messages) === null || _derivedState$activeS === void 0 || _derivedState$activeS.push({
id: crypto.randomUUID(),
role: 'assistant',
content: messageContent,
createdAt: Date.now()
});
}
},
onError: function onError(error) {
var _derivedState$activeS2;
console.error('回答失败', error);
// 同步错误消息到 valtio
(_derivedState$activeS2 = derivedState.activeSession) === null || _derivedState$activeS2 === void 0 || (_derivedState$activeS2 = _derivedState$activeS2.messages) === null || _derivedState$activeS2 === void 0 || _derivedState$activeS2.push({
id: crypto.randomUUID(),
role: 'assistant',
content: formatMessage({
id: 'ai.msgbox.error.response'
}),
createdAt: Date.now()
// mode,
// lib,
});
},
experimental_throttle: 500
}),
messages = _useChat.messages,
setMessages = _useChat.setMessages,
sendMessage = _useChat.sendMessage,
regenerate = _useChat.regenerate,
status = _useChat.status,
stop = _useChat.stop;
// 处理用户提交
var handleSubmit = function handleSubmit() {
var _derivedSnap$activeSe5, _derivedState$activeS3;
var trimmedPrompt = promptText.trim();
if (!trimmedPrompt || status === 'streaming' || status === 'submitted') return;
if (derivedSnap.activeSession && ((_derivedSnap$activeSe5 = derivedSnap.activeSession.messages) === null || _derivedSnap$activeSe5 === void 0 ? void 0 : _derivedSnap$activeSe5.length) === 0) {
derivedState.activeSession.title = trimmedPrompt;
}
// 使用 sendMessage 函数发送新消息
// 第二个参数传递每次请求特定的额外数据
sendMessage({
text: promptText
}, {
body: {
// context: fileSummary,
// lib: snap.lib,
// mode: snap.mode,
}
});
setPromptText('');
(_derivedState$activeS3 = derivedState.activeSession) === null || _derivedState$activeS3 === void 0 || (_derivedState$activeS3 = _derivedState$activeS3.messages) === null || _derivedState$activeS3 === void 0 || _derivedState$activeS3.push({
id: crypto.randomUUID(),
role: 'user',
content: promptText,
createdAt: Date.now(),
context: fileSummary,
lib: snap.lib
});
chatScrollIntoView();
// 埋点
if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object') {
trackEvent('start_ai_chat', {
entry_point: simple ? 'Drawer' : 'MsgBox',
mode: AIChatStore.mode,
lib: AIChatStore.lib,
page_title: document.title,
location: location.href
});
}
};
// 同步 Valtio store -> useChat state
useEffect(function () {
if (simple) {
return;
}
setTimeout(function () {
var _derivedSnap$activeSe6;
var sessionMessages = (_derivedSnap$activeSe6 = derivedSnap.activeSession) === null || _derivedSnap$activeSe6 === void 0 ? void 0 : _derivedSnap$activeSe6.messages;
if ((sessionMessages === null || sessionMessages === void 0 ? void 0 : sessionMessages.length) > 0) {
var converted = convertToUIMessages(sessionMessages);
// 避免无限循环,仅当消息数量或内容不同时更新
if (messages.length !== converted.length || JSON.stringify(messages) !== JSON.stringify(converted)) {
setMessages(converted);
}
} else {
setMessages([]);
}
});
}, [(_derivedSnap$activeSe7 = derivedSnap.activeSession) === null || _derivedSnap$activeSe7 === void 0 ? void 0 : _derivedSnap$activeSe7.id, (_derivedSnap$activeSe8 = derivedSnap.activeSession) === null || _derivedSnap$activeSe8 === void 0 || (_derivedSnap$activeSe8 = _derivedSnap$activeSe8.messages) === null || _derivedSnap$activeSe8 === void 0 ? void 0 : _derivedSnap$activeSe8.length]);
// 处理从外部(如demo页)发起的对话
useEffect(function () {
if (snap.tempMessage) {
var _derivedState$activeS4, _derivedState$activeS5;
sendMessage({
text: snap.tempMessage.content
});
(_derivedState$activeS4 = derivedState.activeSession) === null || _derivedState$activeS4 === void 0 || (_derivedState$activeS4 = _derivedState$activeS4.messages) === null || _derivedState$activeS4 === void 0 || (_derivedState$activeS5 = _derivedState$activeS4.push) === null || _derivedState$activeS5 === void 0 || _derivedState$activeS5.call(_derivedState$activeS4, snap.tempMessage);
AIChatStore.tempMessage = null;
}
}, [snap.tempMessage]);
useEffect(function () {
chatScrollIntoView();
if (simple) {
createPureNewSession(title);
AIChatStore.mode = AIMode.implement;
if (!themeConfig.isAntVSite && themeConfig.title) {
AIChatStore.lib = themeConfig.title;
}
}
return function () {
if (simple) {
clearEmptySession();
}
};
}, []);
useEffect(function () {
chatScrollIntoView();
stop();
AIChatStore.errorMsg = null;
}, [snap.activeSessionId]);
// 将 messages 数组作为依赖项。当它变化时,Hook 会运行。
var _useAutoScroll = useAutoScroll(messages),
containerRef = _useAutoScroll.containerRef,
anchorRef = _useAutoScroll.anchorRef;
var autofix = function autofix() {
var _derivedState$activeS6;
var autoFixPromptText = "".concat(formatMessage({
id: 'ai.msgbox.auto.fix.prompt'
}), " [").concat(snap.errorMsg, "]");
sendMessage({
text: autoFixPromptText
}, {
body: {
antvContext: snap.codeBlock
}
});
AIChatStore.errorMsg = null;
(_derivedState$activeS6 = derivedState.activeSession) === null || _derivedState$activeS6 === void 0 || (_derivedState$activeS6 = _derivedState$activeS6.messages) === null || _derivedState$activeS6 === void 0 || _derivedState$activeS6.push({
id: crypto.randomUUID(),
role: 'user',
content: autoFixPromptText,
createdAt: Date.now(),
context: snap.codeBlock,
lib: snap.lib
});
chatScrollIntoView();
};
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Flex, {
gap: "middle",
vertical: true,
className: styles.chatContainer,
ref: containerRef
}, messages.map(function (msg, index) {
var textContent = getTextContent(msg);
return /*#__PURE__*/React.createElement(Bubble, {
key: msg.id || index,
content: /*#__PURE__*/React.createElement(MarkdownComponent, {
content: textContent,
showRunButton: !props.simple
}),
avatar: msg.role === 'assistant' ? avatar : null,
footer: status === 'ready' && /*#__PURE__*/React.createElement(Space, {
size: "small"
}, msg.role === 'assistant' && index === messages.length - 1 && /*#__PURE__*/React.createElement(Tooltip, {
title: formatMessage({
id: 'ai.msgbox.retry'
})
}, /*#__PURE__*/React.createElement(Button, {
onClick: function onClick() {
regenerate();
derivedState.activeSession.messages.pop();
},
color: "default",
variant: "text",
size: "small",
icon: /*#__PURE__*/React.createElement(SyncOutlined, null)
})), /*#__PURE__*/React.createElement(Tooltip, {
title: formatMessage({
id: 'ai.msgbox.copy'
})
}, /*#__PURE__*/React.createElement(Button, {
color: "default",
variant: "text",
size: "small",
onClick: function onClick() {
return copyToClipboard(textContent);
},
icon: copyState.value === textContent ? /*#__PURE__*/React.createElement(CheckOutlined, null) : /*#__PURE__*/React.createElement(CopyOutlined, null)
})), /*#__PURE__*/React.createElement(Tooltip, {
title: formatMessage({
id: 'ai.msgbox.continue.from.here'
})
}, /*#__PURE__*/React.createElement(Button, {
color: "default",
variant: "text",
size: "small",
onClick: function onClick() {
return branchMessage(index);
},
icon: /*#__PURE__*/React.createElement(BranchesOutlined, null)
})), msg.role === 'assistant' && /*#__PURE__*/React.createElement(Tooltip, {
title: formatMessage({
id: 'ai.msgbox.delete'
})
}, /*#__PURE__*/React.createElement(Button, {
color: "default",
variant: "text",
size: "small",
onClick: function onClick() {
return deleteMessage(msg.id);
},
icon: /*#__PURE__*/React.createElement(DeleteOutlined, null)
}))),
placement: msg.role === 'user' ? 'end' : 'start'
});
}), (status === 'streaming' || status === 'submitted') && ((_messages = messages[messages.length - 1]) === null || _messages === void 0 ? void 0 : _messages.role) === 'user' && /*#__PURE__*/React.createElement(Bubble, {
placement: "start",
avatar: avatar,
loading: true
}), /*#__PURE__*/React.createElement("div", {
ref: anchorRef,
id: "msgBoxAnchor"
})), /*#__PURE__*/React.createElement("div", null, !props.simple && /*#__PURE__*/React.createElement("div", {
className: styles.newButtonContainer
}, /*#__PURE__*/React.createElement(Space, null, /*#__PURE__*/React.createElement("button", {
type: "button",
onClick: function onClick() {
return createPureNewSession();
},
className: styles.newButton
}, /*#__PURE__*/React.createElement(Space, null, /*#__PURE__*/React.createElement(PlusSquareOutlined, null), formatMessage({
id: 'ai.msgbox.start.new.chat'
}))), snap.errorMsg && status === 'ready' && /*#__PURE__*/React.createElement(Button, {
onClick: autofix,
color: "danger",
variant: "filled",
icon: /*#__PURE__*/React.createElement(ToolOutlined, null)
}, formatMessage({
id: 'ai.msgbox.auto.fix'
})))), /*#__PURE__*/React.createElement(PromptTextarea, {
size: "compact",
value: promptText,
onChange: setPromptText,
loading: status === 'streaming' || status === 'submitted',
onCancel: stop,
showAction: !props.simple,
style: {
marginBottom: 0
},
onConfirm: handleSubmit,
onDataSummaryChange: setFileSummary,
showModeSelector: true
})));
}
export default MsgBox;