feat: export chat

This commit is contained in:
archer
2023-05-02 11:45:10 +08:00
parent 89a67ca9c0
commit b0d414ac12
10 changed files with 951 additions and 15 deletions

3
public/js/html2pdf.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1682957006954" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2644" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M942.1 593.9c-22.6 0-41 18.3-41 41v204.8c0 22.6-18.4 41-41 41H163.8c-22.6 0-41-18.4-41-41V634.9c0-22.6-18.3-41-41-41s-41 18.3-41 41v204.8c0 67.8 55.1 122.9 122.9 122.9H860c67.8 0 122.9-55.1 122.9-122.9V634.9c0.1-22.6-18.2-41-40.8-41z" p-id="2645"></path><path d="M309.3 363L471 201.3v515.5c0 22.5 18.4 41 41 41 22.5 0 41-18.4 41-41V201.3L714.7 363c15.9 15.9 42 15.9 57.9 0 15.9-15.9 15.9-42 0-57.9L541.5 73.9c-0.2-0.2-0.3-0.4-0.4-0.5-5.7-5.7-12.7-9.3-20.1-10.9-0.2-0.1-0.5-0.2-0.7-0.2-2.7-0.5-5.4-0.8-8.1-0.8-2.7 0-5.5 0.3-8.1 0.8-0.3 0.1-0.5 0.2-0.7 0.2-7.4 1.6-14.4 5.2-20.1 10.9-0.2 0.2-0.3 0.4-0.4 0.5L251.4 305.1c-15.9 15.9-15.9 42 0 57.9 15.9 15.9 42 15.9 57.9 0z" p-id="2646"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -21,7 +21,8 @@ const map = {
stop: require('./icons/stop.svg').default, stop: require('./icons/stop.svg').default,
shareMarket: require('./icons/shareMarket.svg').default, shareMarket: require('./icons/shareMarket.svg').default,
collectionLight: require('./icons/collectionLight.svg').default, collectionLight: require('./icons/collectionLight.svg').default,
collectionSolid: require('./icons/collectionSolid.svg').default collectionSolid: require('./icons/collectionSolid.svg').default,
export: require('./icons/export.svg').default
}; };
export type IconName = keyof typeof map; export type IconName = keyof typeof map;

View File

@@ -17,7 +17,7 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
return ( return (
<ReactMarkdown <ReactMarkdown
className={`${styles.markdown} ${ className={`markdown ${styles.markdown} ${
isChatting ? (source === '' ? styles.waitingAnimation : styles.animation) : '' isChatting ? (source === '' ? styles.waitingAnimation : styles.animation) : ''
}`} }`}
remarkPlugins={[remarkMath]} remarkPlugins={[remarkMath]}
@@ -31,6 +31,7 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
return !inline || match ? ( return !inline || match ? (
<Box my={3} borderRadius={'md'} overflow={'hidden'} backgroundColor={'#222'}> <Box my={3} borderRadius={'md'} overflow={'hidden'} backgroundColor={'#222'}>
<Flex <Flex
className="code-header"
py={2} py={2}
px={5} px={5}
backgroundColor={useColorModeValue('#323641', 'gray.600')} backgroundColor={useColorModeValue('#323641', 'gray.600')}

View File

@@ -4,3 +4,823 @@ export enum UserAuthTypeEnum {
} }
export const PRICE_SCALE = 100000; export const PRICE_SCALE = 100000;
export const htmlTemplate = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width initial-scale=1.0" />
<title>FastGpt</title>
</head>
<style>
.markdown > :first-child {
margin-top: 0 !important;
}
.markdown > :last-child {
margin-bottom: 0 !important;
}
.markdown a.absent {
color: #cc0000;
}
.markdown a.anchor {
bottom: 0;
cursor: pointer;
display: block;
left: 0;
margin-left: -30px;
padding-left: 30px;
position: absolute;
top: 0;
}
.markdown h1,
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
cursor: text;
font-weight: bold;
margin: 10px 0;
padding: 0;
position: relative;
}
.markdown h1 .mini-icon-link,
.markdown h2 .mini-icon-link,
.markdown h3 .mini-icon-link,
.markdown h4 .mini-icon-link,
.markdown h5 .mini-icon-link,
.markdown h6 .mini-icon-link {
display: none;
}
.markdown h1:hover a.anchor,
.markdown h2:hover a.anchor,
.markdown h3:hover a.anchor,
.markdown h4:hover a.anchor,
.markdown h5:hover a.anchor,
.markdown h6:hover a.anchor {
line-height: 1;
margin-left: -22px;
padding-left: 0;
text-decoration: none;
top: 15%;
}
.markdown h1:hover a.anchor .mini-icon-link,
.markdown h2:hover a.anchor .mini-icon-link,
.markdown h3:hover a.anchor .mini-icon-link,
.markdown h4:hover a.anchor .mini-icon-link,
.markdown h5:hover a.anchor .mini-icon-link,
.markdown h6:hover a.anchor .mini-icon-link {
display: inline-block;
}
.markdown h1 tt,
.markdown h1 code,
.markdown h2 tt,
.markdown h2 code,
.markdown h3 tt,
.markdown h3 code,
.markdown h4 tt,
.markdown h4 code,
.markdown h5 tt,
.markdown h5 code,
.markdown h6 tt,
.markdown h6 code {
font-size: inherit;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 24px;
}
.markdown h3 {
font-size: 18px;
}
.markdown h4 {
font-size: 16px;
}
.markdown h5 {
font-size: 14px;
}
.markdown h6 {
font-size: 12px;
}
.markdown p,
.markdown blockquote,
.markdown ul,
.markdown ol,
.markdown dl,
.markdown table,
.markdown pre {
margin: 10px 0;
}
.markdown > h2:first-child,
.markdown > h1:first-child,
.markdown > h1:first-child + h2,
.markdown > h3:first-child,
.markdown > h4:first-child,
.markdown > h5:first-child,
.markdown > h6:first-child {
margin-top: 0;
padding-top: 0;
}
.markdown a:first-child,
.markdown > h1,
.markdown a:first-child,
.markdown > h2,
.markdown a:first-child,
.markdown > h3,
.markdown a:first-child,
.markdown > h4,
.markdown a:first-child,
.markdown > h5,
.markdown a:first-child,
.markdown > h6 {
margin-top: 0;
padding-top: 0;
}
.markdown h1 + p,
.markdown h2 + p,
.markdown h3 + p,
.markdown h4 + p,
.markdown h5 + p,
.markdown h6 + p {
margin-top: 0;
}
.markdown li p.first {
display: inline-block;
}
.markdown ul,
.markdown ol {
padding-left: 2em;
}
.markdown ul.no-list,
.markdown ol.no-list {
list-style-type: none;
padding: 0;
}
.markdown ul li > :first-child,
.markdown ol li > :first-child {
margin-top: 0;
}
.markdown ul ul,
.markdown ul ol,
.markdown ol ol,
.markdown ol ul {
margin-bottom: 0;
}
.markdown dl {
padding: 0;
}
.markdown dl dt {
font-size: 14px;
font-style: italic;
font-weight: bold;
margin: 15px 0 5px;
padding: 0;
}
.markdown dl dt:first-child {
padding: 0;
}
.markdown dl dt > :first-child {
margin-top: 0;
}
.markdown dl dt > :last-child {
margin-bottom: 0;
}
.markdown dl dd {
margin: 0 0 15px;
padding: 0 15px;
}
.markdown dl dd > *:first-child {
margin-top: 0;
}
.markdown dl dd > *last-child {
margin-bottom: none;
}
.markdown blockquote {
border-left: solid 4px #dddddd;
color: #777777;
padding-left: 15px;
}
.markdown blockquote > * :first-child {
margin-top: 0;
}
.markdown blockquote > * :last-child {
margin-bottom: 0;
}
.markdown table th {
font-weight: bold;
}
.markdown table th,
.markdown table td {
padding: 6px 13px;
}
.markdown table tr {
background-color: #ffffff;
}
.markdown table tr:nth-child(2n) {
background-color: #f0f0f0;
}
.markdown img {
max-width: 100%;
}
.markdown span.frame {
display: block;
overflow: hidden;
}
.markdown span.frame > span {
border: 1px solid #dddddd;
display: block;
float: left;
margin: 13px 0 0;
overflow: hidden;
padding: 7px;
width: auto;
}
.markdown span.frame span img {
display: block;
float: left;
}
.markdown span.frame span span {
clear: both;
color: #333333;
display: block;
padding: 5px;
}
.markdown span.align-center {
clear: both;
display: block;
overflow: hidden;
text-align: center;
}
.markdown span.align-center > span {
display: block;
margin: 13px auto;
overflow: hidden;
}
.markdown span.align-center span img {
margin: 0 auto;
text-align: center;
}
.markdown span.align-right {
clear: both;
display: block;
overflow: hidden;
}
.markdown span.align-right > span {
display: block;
margin: 13px auto;
overflow: hidden;
text-align: right;
}
.markdown span.align-right img {
margin: 0;
text-align: right;
}
.markdown span.float-left {
display: block;
float: left;
margin-right: 13px;
overflow: hidden;
}
.markdown span.float-left > span {
margin: 13px auto;
}
.markdown span.float-right {
display: block;
float: right;
margin-left: 13px;
overflow: hidden;
}
.markdown span.float-right > span {
display: block;
margin: 13px auto;
overflow: hidden;
text-align: right;
}
.markdown code,
.markdown tt {
border: 1px solid #eaeaea;
border-radius: 3px;
margin: 0 2px;
padding: 0 5px;
}
.markdown pre > code {
background-color: transparent;
border: none;
margin: 0;
padding: 0;
white-space: pre;
}
.markdown .highlight pre,
.markdown pre {
border: 1px solid #ccc;
border-radius: 3px;
font-size: max(0.9em, 14px);
line-height: 19px;
overflow: auto;
padding: 6px 10px;
}
.markdown pre code,
.markdown pre tt {
background-color: #f8f8f8;
border: none;
}
.markdown {
text-align: justify;
overflow-y: hidden;
tab-size: 4;
word-spacing: normal;
word-break: break-all;
}
.markdown pre {
display: block;
width: 100%;
padding: 15px;
margin: 0;
border: none;
border-radius: none;
background-color: #222 !important;
overflow-x: auto;
color: #fff;
}
.markdown pre code {
background-color: #222 !important;
width: 100%;
}
.markdown a {
text-decoration: underline;
color: var(--chakra-colors-blue-600);
}
.markdown table {
border-collapse: separate;
border-spacing: 0;
color: #718096;
}
.markdown table thead tr:first-child th {
border-bottom-width: 1px;
border-left-width: 1px;
border-top-width: 1px;
border-color: #ccc;
background-color: #edf2f7;
overflow: hidden;
}
.markdown table thead tr:first-child th:first-child {
border-top-left-radius: 0.375rem;
}
.markdown table thead tr:first-child th:last-child {
border-right-width: 1px;
border-top-right-radius: 0.375rem;
}
.markdown td {
border-bottom-width: 1px;
border-left-width: 1px;
border-color: #ccc;
}
.markdown td:last-of-type {
border-right-width: 1px;
}
.markdown tbody tr:last-child {
overflow: hidden;
}
.markdown tbody tr:last-child td:first-child {
border-bottom-left-radius: 0.375rem;
}
.markdown tbody tr:last-child td:last-child {
border-bottom-right-radius: 0.375rem;
}
.markdown p {
text-align: justify;
white-space: pre-wrap;
}
code[class*='language-'] {
color: #d4d4d4;
text-shadow: none;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
hyphens: none;
}
pre[class*='language-'] {
color: #d4d4d4;
text-shadow: none;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
hyphens: none;
padding: 1em;
margin: 0.5em0;
overflow: auto;
background: #1e1e1e;
}
code[class*='language-'] ::selection,
code[class*='language-']::selection,
pre[class*='language-'] ::selection,
pre[class*='language-']::selection {
text-shadow: none;
background: #264f78;
}
:not(pre) > code[class*='language-'] {
padding: 0.1em 0.3em;
border-radius: 0.3em;
color: #db4c69;
background: #1e1e1e;
}
.namespace {
opacity: 0.7;
}
.doctype.doctype-tag {
color: #569cd6;
}
.doctype.name {
color: #9cdcfe;
}
comment {
color: #6a9955;
}
prolog {
color: #6a9955;
}
.language-html .language-css .token.punctuation,
.language-html .language-javascript .token.punctuation {
color: #d4d4d4;
}
punctuation {
color: #d4d4d4;
}
boolean {
color: #569cd6;
}
constant {
color: #9cdcfe;
}
inserted {
color: #b5cea8;
}
number {
color: #b5cea8;
}
property {
color: #9cdcfe;
}
symbol {
color: #b5cea8;
}
tag {
color: #569cd6;
}
unit {
color: #b5cea8;
}
attr-name {
color: #9cdcfe;
}
builtin {
color: #ce9178;
}
char {
color: #ce9178;
}
deleted {
color: #ce9178;
}
selector {
color: #d7ba7d;
}
string {
color: #ce9178;
}
.language-css .token.string.url {
text-decoration: underline;
}
entity {
color: #569cd6;
}
operator {
color: #d4d4d4;
}
operator.arrow {
color: #569cd6;
}
atrule {
color: #ce9178;
}
atrule.rule {
color: #c586c0;
}
atrule.url {
color: #9cdcfe;
}
atrule.url.function {
color: #dcdcaa;
}
atrule.url.punctuation {
color: #d4d4d4;
}
keyword {
color: #569cd6;
}
keyword.control-flow {
color: #c586c0;
}
keyword.module {
color: #c586c0;
}
function {
color: #dcdcaa;
}
function.maybe-class-name {
color: #dcdcaa;
}
regex {
color: #d16969;
}
important {
color: #569cd6;
}
italic {
font-style: italic;
}
class-name {
color: #4ec9b0;
}
maybe-class-name {
color: #4ec9b0;
}
console {
color: #9cdcfe;
}
parameter {
color: #9cdcfe;
}
interpolation {
color: #9cdcfe;
}
punctuation.interpolation-punctuation {
color: #569cd6;
}
exports.maybe-class-name {
color: #9cdcfe;
}
imports.maybe-class-name {
color: #9cdcfe;
}
variable {
color: #9cdcfe;
}
escape {
color: #d7ba7d;
}
tag.punctuation {
color: grey;
}
cdata {
color: grey;
}
attr-value {
color: #ce9178;
}
attr-value.punctuation {
color: #ce9178;
}
attr-value.punctuation.attr-equals {
color: #d4d4d4;
}
namespace {
color: #4ec9b0;
}
code[class*='language-javascript'],
code[class*='language-jsx'],
code[class*='language-tsx'],
code[class*='language-typescript'] {
color: #9cdcfe;
}
pre[class*='language-javascript'],
pre[class*='language-jsx'],
pre[class*='language-tsx'],
pre[class*='language-typescript'] {
color: #9cdcfe;
}
code[class*='language-css'] {
color: #ce9178;
}
pre[class*='language-css'] {
color: #ce9178;
}
code[class*='language-html'] {
color: #d4d4d4;
}
pre[class*='language-html'] {
color: #d4d4d4;
}
.language-regex .token.anchor {
color: #dcdcaa;
}
.language-html .token.punctuation {
color: grey;
}
pre[class*='language-'] > code[class*='language-'] {
position: relative;
z-index: 1;
}
.line-highlight.line-highlight {
background: #f7ebc6;
box-shadow: inset 5px 0 0 #f7d87c;
z-index: 0;
}
* {
box-sizing: border-box;
}
body,
h1,
h2,
h3,
h4,
hr,
p,
blockquote,
dl,
dt,
dd,
ul,
ol,
li,
pre,
form,
fieldset,
legend,
button,
input,
textarea,
th,
td,
svg {
margin: 0;
}
body,
html {
font-size: 16px;
background-color: #fff;
color: rgba(0, 0, 0, 0.64);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 1em;
}
::-webkit-scrollbar,
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track,
::-webkit-scrollbar-track {
background: transparent;
border-radius: 2px;
}
::-webkit-scrollbar-thumb,
::-webkit-scrollbar-thumb {
background: #bfbfbf;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover,
::-webkit-scrollbar-thumb:hover {
background: #999;
}
</style>
<style>
.chat-item {
display: flex;
align-items: flex-start;
padding: 20px 16px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
justify-content: center;
}
.chat-item img {
width: 30px;
max-height: 50px;
object-fit: contain;
margin-right: 10px;
}
.chat-item:nth-child(even) {
background-color: #f6f6f6;
}
.chat-item:nth-child(odd) {
background-color: #ffffff;
}
.markdown {
overflow-x: hidden;
max-width: 800px;
width: 100%;
}
@media (max-width: 900px) {
html {
font-size: 14px;
}
::-webkit-scrollbar,
::-webkit-scrollbar {
width: 2px;
height: 2px;
}
.chat-item img {
width: 20px;
max-height: 40px;
margin-right: 4px;
}
}
</style>
<body>{{CHAT_CONTENT}}</body>
</html>
`;

View File

@@ -51,8 +51,9 @@ export default function App({ Component, pageProps }: AppProps) {
/> />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<Script src="/js/qrcode.min.js" strategy="afterInteractive"></Script> <Script src="/js/qrcode.min.js" strategy="lazyOnload"></Script>
<Script src="/js/pdf.js" strategy="afterInteractive"></Script> <Script src="/js/pdf.js" strategy="lazyOnload"></Script>
<Script src="/js/html2pdf.bundle.min.js" strategy="lazyOnload"></Script>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ChakraProvider theme={theme}> <ChakraProvider theme={theme}>
<ColorModeScript initialColorMode={theme.config.initialColorMode} /> <ColorModeScript initialColorMode={theme.config.initialColorMode} />

View File

@@ -1,4 +1,4 @@
import React, { useRef, useEffect, useMemo } from 'react'; import React, { useRef, useEffect, useMemo, useCallback } from 'react';
import { AddIcon, ChatIcon, DeleteIcon, MoonIcon, SunIcon } from '@chakra-ui/icons'; import { AddIcon, ChatIcon, DeleteIcon, MoonIcon, SunIcon } from '@chakra-ui/icons';
import { import {
Box, Box,
@@ -13,7 +13,11 @@ import {
IconButton, IconButton,
useDisclosure, useDisclosure,
useColorMode, useColorMode,
useColorModeValue useColorModeValue,
Menu,
MenuButton,
MenuList,
MenuItem
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useUserStore } from '@/store/user'; import { useUserStore } from '@/store/user';
import { useMutation, useQuery } from '@tanstack/react-query'; import { useMutation, useQuery } from '@tanstack/react-query';
@@ -23,16 +27,20 @@ import MyIcon from '@/components/Icon';
import WxConcat from '@/components/WxConcat'; import WxConcat from '@/components/WxConcat';
import { getChatHistory, delChatHistoryById } from '@/api/chat'; import { getChatHistory, delChatHistoryById } from '@/api/chat';
import { getCollectionModels } from '@/api/model'; import { getCollectionModels } from '@/api/model';
import { modelList } from '@/constants/model'; import type { ChatSiteItemType } from '../index';
import { fileDownload } from '@/utils/file';
import { htmlTemplate } from '@/constants/common';
const SlideBar = ({ const SlideBar = ({
chatId, chatId,
modelId, modelId,
history,
resetChat, resetChat,
onClose onClose
}: { }: {
chatId: string; chatId: string;
modelId: string; modelId: string;
history: ChatSiteItemType[];
resetChat: (modelId?: string, chatId?: string) => void; resetChat: (modelId?: string, chatId?: string) => void;
onClose: () => void; onClose: () => void;
}) => { }) => {
@@ -84,6 +92,68 @@ const SlideBar = ({
}, 1000); }, 1000);
}, [loadChatHistory]); }, [loadChatHistory]);
/**
* export md
*/
const onclickExportMd = useCallback(() => {
fileDownload({
text: history.map((item) => item.value).join('\n'),
type: 'text/markdown',
filename: 'chat.md'
});
}, [history]);
const getHistoryHtml = useCallback(() => {
const historyDom = document.getElementById('history');
if (!historyDom) return;
const dom = Array.from(historyDom.children).map((child, i) => {
const avatar = `<img src="${
child.querySelector<HTMLImageElement>('.avatar')?.src
}" alt="" />`;
const chatContent = child.querySelector<HTMLDivElement>('.markdown');
if (!chatContent) {
return '';
}
const chatContentClone = chatContent.cloneNode(true) as HTMLDivElement;
const codeHeader = chatContentClone.querySelectorAll('.code-header');
codeHeader.forEach((childElement: any) => {
childElement.remove();
});
return `<div class="chat-item">
${avatar}
${chatContentClone.outerHTML}
</div>`;
});
const html = htmlTemplate.replace('{{CHAT_CONTENT}}', dom.join('\n'));
return html;
}, []);
const onclickExportHtml = useCallback(() => {
const html = getHistoryHtml();
html &&
fileDownload({
text: html,
type: 'text/html',
filename: '聊天记录.html'
});
}, [getHistoryHtml]);
const onclickExportPdf = useCallback(() => {
const html = getHistoryHtml();
html &&
// @ts-ignore
html2pdf(html, {
margin: 0,
filename: `聊天记录.pdf`
});
}, [getHistoryHtml]);
const RenderHistory = () => ( const RenderHistory = () => (
<> <>
{chatHistory.map((item) => ( {chatHistory.map((item) => (
@@ -145,7 +215,7 @@ const SlideBar = ({
onClick: () => void; onClick: () => void;
children: JSX.Element | string; children: JSX.Element | string;
}) => ( }) => (
<Box px={3} mb={3}> <Box px={3} mb={2}>
<Flex <Flex
alignItems={'center'} alignItems={'center'}
p={2} p={2}
@@ -176,7 +246,7 @@ const SlideBar = ({
w={'90%'} w={'90%'}
variant={'white'} variant={'white'}
h={'40px'} h={'40px'}
mb={4} mb={2}
mx={'auto'} mx={'auto'}
leftIcon={<AddIcon />} leftIcon={<AddIcon />}
onClick={() => resetChat()} onClick={() => resetChat()}
@@ -238,7 +308,33 @@ const SlideBar = ({
</Accordion> </Accordion>
</Box> </Box>
<Divider my={4} colorScheme={useColorModeValue('gray', 'white')} /> <Divider my={3} colorScheme={useColorModeValue('gray', 'white')} />
{history.length > 0 && (
<Menu autoSelect={false}>
<MenuButton
mx={3}
mb={2}
p={2}
display={'flex'}
alignItems={'center'}
cursor={'pointer'}
borderRadius={'md'}
textAlign={'left'}
_hover={{
backgroundColor: 'rgba(255,255,255,0.2)'
}}
>
<MyIcon name="export" fill={'white'} w={'18px'} h={'18px'} mr={4} />
</MenuButton>
<MenuList fontSize={'sm'} color={'blackAlpha.800'}>
<MenuItem onClick={onclickExportHtml}>HTML格式</MenuItem>
<MenuItem onClick={onclickExportPdf}>PDF格式</MenuItem>
<MenuItem onClick={onclickExportMd}>Markdown格式</MenuItem>
</MenuList>
</Menu>
)}
<RenderButton onClick={() => router.push('/')}> <RenderButton onClick={() => router.push('/')}>
<> <>

View File

@@ -403,6 +403,7 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
resetChat={resetChat} resetChat={resetChat}
chatId={chatId} chatId={chatId}
modelId={modelId} modelId={modelId}
history={chatData.history}
onClose={onCloseSlider} onClose={onCloseSlider}
/> />
</Box> </Box>
@@ -434,6 +435,7 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
resetChat={resetChat} resetChat={resetChat}
chatId={chatId} chatId={chatId}
modelId={modelId} modelId={modelId}
history={chatData.history}
onClose={onCloseSlider} onClose={onCloseSlider}
/> />
</DrawerContent> </DrawerContent>
@@ -447,7 +449,15 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
flexDirection={'column'} flexDirection={'column'}
> >
{/* 聊天内容 */} {/* 聊天内容 */}
<Box ref={ChatBox} pb={[4, 0]} flex={'1 0 0'} h={0} w={'100%'} overflowY={'auto'}> <Box
id={'history'}
ref={ChatBox}
pb={[4, 0]}
flex={'1 0 0'}
h={0}
w={'100%'}
overflowY={'auto'}
>
{chatData.history.map((item, index) => ( {chatData.history.map((item, index) => (
<Box <Box
key={item._id} key={item._id}
@@ -463,6 +473,7 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
<Menu autoSelect={false}> <Menu autoSelect={false}>
<MenuButton as={Box} mr={media(4, 1)} cursor={'pointer'}> <MenuButton as={Box} mr={media(4, 1)} cursor={'pointer'}>
<Image <Image
className="avatar"
src={ src={
item.obj === 'Human' item.obj === 'Human'
? '/icon/human.png' ? '/icon/human.png'
@@ -479,14 +490,16 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
<MenuItem onClick={() => delChatRecord(index, item._id)}></MenuItem> <MenuItem onClick={() => delChatRecord(index, item._id)}></MenuItem>
</MenuList> </MenuList>
</Menu> </Menu>
<Box flex={'1 0 0'} w={0} overflow={'hidden'} id={`chat${index}`}> <Box flex={'1 0 0'} w={0} overflow={'hidden'}>
{item.obj === 'AI' ? ( {item.obj === 'AI' ? (
<Markdown <Markdown
source={item.value} source={item.value}
isChatting={isChatting && index === chatData.history.length - 1} isChatting={isChatting && index === chatData.history.length - 1}
/> />
) : ( ) : (
<Box whiteSpace={'pre-wrap'}>{item.value}</Box> <Box className="markdown" whiteSpace={'pre-wrap'}>
<Box as={'p'}>{item.value}</Box>
</Box>
)} )}
</Box> </Box>
{isPc && ( {isPc && (

View File

@@ -75,7 +75,7 @@ export async function generateQA(next = false): Promise<any> {
role: 'system', role: 'system',
content: `你是出题人 content: `你是出题人
${dataItem.prompt || '下面是"一段长文本"'} ${dataItem.prompt || '下面是"一段长文本"'}
从中选出5至20个题目和答案,题目包含问答题,计算题,代码题等.答案详细.按格式返回: Q1: 从中选出5至20个题目和答案.答案详细.按格式返回: Q1:
A1: A1:
Q2: Q2:
A2: A2:

View File

@@ -90,7 +90,7 @@ export const openaiCreateEmbedding = async ({
) )
.then((res) => ({ .then((res) => ({
tokenLen: res.data.usage.total_tokens || 0, tokenLen: res.data.usage.total_tokens || 0,
vector: res?.data?.data?.[0]?.embedding || [] vector: res.data.data?.[0]?.embedding || []
})); }));
pushGenerateVectorBill({ pushGenerateVectorBill({