修复搜索界面样式,更新README文档;
This commit is contained in:
parent
7d2042673e
commit
8b8ce06e25
|
@ -8,6 +8,8 @@ cv/
|
||||||
layout_app.py
|
layout_app.py
|
||||||
api/flask_session
|
api/flask_session
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
@ -41,3 +43,7 @@ nltk_data/
|
||||||
|
|
||||||
# Exclude hash-like temporary files like 9b5ad71b2ce5302211f9c61530b329a4922fc6a4
|
# Exclude hash-like temporary files like 9b5ad71b2ce5302211f9c61530b329a4922fc6a4
|
||||||
*[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]*
|
*[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]*
|
||||||
|
web/public/logo_secret.svg
|
||||||
|
web/public/logo_old.svg
|
||||||
|
web/public/logo.svg
|
||||||
|
web/src/locales/zh.ts
|
||||||
|
|
26
README.md
26
README.md
|
@ -12,6 +12,31 @@ Ragflow-Plus,该名字不是说比 Ragflow 项目牛的意思,而是对标 D
|
||||||
### 一. 用户批量注册/批量加入团队
|
### 一. 用户批量注册/批量加入团队
|
||||||
隐藏了原本用户注册的功能,改为管理员通过后台批量注册,并加入管理员团队,可共享团队知识库及默认模型配置
|
隐藏了原本用户注册的功能,改为管理员通过后台批量注册,并加入管理员团队,可共享团队知识库及默认模型配置
|
||||||
|
|
||||||
|
使用方式:
|
||||||
|
1. python环境安装依赖:
|
||||||
|
|
||||||
|
参考python版本:python == 3.10.16
|
||||||
|
```python
|
||||||
|
pip install mysql-connector-python
|
||||||
|
pip install pycryptodomex
|
||||||
|
pip install werkzeug
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 修改`python_sql\add.json`文件内容:
|
||||||
|
|
||||||
|
- student_id 为 待添加用户学号
|
||||||
|
- tenant_id 为 共享知识库的队长id,需要连接数据库查看
|
||||||
|
|
||||||
|
3. 执行批量插入操作:
|
||||||
|
|
||||||
|
```python
|
||||||
|
python python_sql/add_sql_final.py
|
||||||
|
```
|
||||||
|
|
||||||
|
默认用户名为 `学号@xidian.cn`
|
||||||
|
默认密码为 `学号`
|
||||||
|
|
||||||
|
|
||||||
### 二. 优化对话显示
|
### 二. 优化对话显示
|
||||||
微调了对话界面的样式,使其观感更为友好
|
微调了对话界面的样式,使其观感更为友好
|
||||||
|
|
||||||
|
@ -65,6 +90,7 @@ docker cp dist ragflow-server:/ragflow/web/
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
版权说明:本项目在 Ragflow 项目基础上进行二开,需要遵守 Ragflow 的开源协议,如下
|
版权说明:本项目在 Ragflow 项目基础上进行二开,需要遵守 Ragflow 的开源协议,如下
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Available options:
|
# Available options:
|
||||||
# - `elasticsearch` (default)
|
# - `elasticsearch` (default)
|
||||||
# - `infinity` (https://github.com/infiniflow/infinity)
|
# - `infinity` (https://github.com/infiniflow/infinity)
|
||||||
DOC_ENGINE=${DOC_ENGINE:-infinity}
|
DOC_ENGINE=${DOC_ENGINE:-elasticsearch}
|
||||||
|
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
# docker env var for specifying vector db type at startup
|
# docker env var for specifying vector db type at startup
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
from openai import OpenAI
|
|
||||||
|
|
||||||
model = "deepseek-r1:1.5b"
|
|
||||||
client = OpenAI(api_key="ragflow-FiNzM5YTEyMDk0MjExZjA5OTg4MDI0Mm", base_url=f"http://localhost/api/v1/chats_openai/ec69b3f4fbeb11ef862c0242ac120002")
|
|
||||||
|
|
||||||
completion = client.chat.completions.create(
|
|
||||||
model=model,
|
|
||||||
messages=[
|
|
||||||
{"role": "system", "content": "你是一个乐于助人的助手"},
|
|
||||||
{"role": "user", "content": "你是谁?"},
|
|
||||||
],
|
|
||||||
stream=False
|
|
||||||
)
|
|
||||||
|
|
||||||
stream = False
|
|
||||||
if stream:
|
|
||||||
for chunk in completion:
|
|
||||||
print(chunk)
|
|
||||||
else:
|
|
||||||
print(completion.choices[0].message.content)
|
|
|
@ -7,3 +7,7 @@
|
||||||
/src/.umi-test
|
/src/.umi-test
|
||||||
/dist
|
/dist
|
||||||
.swc
|
.swc
|
||||||
|
/public/logo_secret.svg
|
||||||
|
/public/logo_old.svg
|
||||||
|
/public/logo.svg
|
||||||
|
/src/locales/zh.ts
|
||||||
|
|
|
@ -52,8 +52,10 @@
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.4",
|
"cmdk": "^1.0.4",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
|
"docx": "^9.3.0",
|
||||||
"dompurify": "^3.1.6",
|
"dompurify": "^3.1.6",
|
||||||
"eventsource-parser": "^1.1.2",
|
"eventsource-parser": "^1.1.2",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
"human-id": "^4.1.1",
|
"human-id": "^4.1.1",
|
||||||
"i18next": "^23.7.16",
|
"i18next": "^23.7.16",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
|
@ -102,6 +104,7 @@
|
||||||
"@testing-library/jest-dom": "^6.4.5",
|
"@testing-library/jest-dom": "^6.4.5",
|
||||||
"@testing-library/react": "^15.0.7",
|
"@testing-library/react": "^15.0.7",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/lodash": "^4.14.202",
|
"@types/lodash": "^4.14.202",
|
||||||
"@types/react": "^18.0.33",
|
"@types/react": "^18.0.33",
|
||||||
|
@ -5746,6 +5749,13 @@
|
||||||
"@types/estree": "*"
|
"@types/estree": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/file-saver": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/file-saver/-/file-saver-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/geojson": {
|
"node_modules/@types/geojson": {
|
||||||
"version": "7946.0.16",
|
"version": "7946.0.16",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.16.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||||
|
@ -11984,6 +11994,41 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/docx": {
|
||||||
|
"version": "9.3.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/docx/-/docx-9.3.0.tgz",
|
||||||
|
"integrity": "sha512-IQwbSLBEMKNJmP9CDqSu3vJDhJps6tv/DlxsxuvfTclapd0JPCz1IFJTU//WdTjUenPMFaUD2Kg1nkQb/BWPIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^22.7.5",
|
||||||
|
"hash.js": "^1.1.7",
|
||||||
|
"jszip": "^3.10.1",
|
||||||
|
"nanoid": "^5.1.3",
|
||||||
|
"xml": "^1.0.1",
|
||||||
|
"xml-js": "^1.6.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/docx/node_modules/nanoid": {
|
||||||
|
"version": "5.1.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.5.tgz",
|
||||||
|
"integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"nanoid": "bin/nanoid.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18 || >=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dom-accessibility-api": {
|
"node_modules/dom-accessibility-api": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmmirror.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
|
"resolved": "https://registry.npmmirror.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
|
||||||
|
@ -13863,6 +13908,12 @@
|
||||||
"node": "^10.12.0 || >=12.0.0"
|
"node": "^10.12.0 || >=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/file-saver": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/file-selector": {
|
"node_modules/file-selector": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmmirror.com/file-selector/-/file-selector-2.1.2.tgz",
|
"resolved": "https://registry.npmmirror.com/file-selector/-/file-selector-2.1.2.tgz",
|
||||||
|
@ -26425,8 +26476,7 @@
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz",
|
"resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz",
|
||||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
||||||
"license": "ISC",
|
"license": "ISC"
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/saxes": {
|
"node_modules/saxes": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
|
@ -30739,6 +30789,24 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xml": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/xml/-/xml-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/xml-js": {
|
||||||
|
"version": "1.6.11",
|
||||||
|
"resolved": "https://registry.npmmirror.com/xml-js/-/xml-js-1.6.11.tgz",
|
||||||
|
"integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sax": "^1.2.4"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"xml-js": "bin/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xml-name-validator": {
|
"node_modules/xml-name-validator": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 30 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 30 KiB |
|
@ -56,7 +56,7 @@ export default {
|
||||||
nicknamePlaceholder: '请输入名称',
|
nicknamePlaceholder: '请输入名称',
|
||||||
register: '创建账户',
|
register: '创建账户',
|
||||||
continue: '继续',
|
continue: '继续',
|
||||||
title: 'Ragflow-Plus大模型平台',
|
title: '西电711大模型平台',
|
||||||
description:
|
description:
|
||||||
'免费注册以探索顶级 RAG 技术。 创建知识库和人工智能来增强您的业务',
|
'免费注册以探索顶级 RAG 技术。 创建知识库和人工智能来增强您的业务',
|
||||||
review: '来自 500 多条评论',
|
review: '来自 500 多条评论',
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
|
|
||||||
.input() {
|
.input() {
|
||||||
:global(.ant-input-affix-wrapper) {
|
:global(.ant-input-affix-wrapper) {
|
||||||
padding: 4px 12px;
|
padding: 0px 12px;
|
||||||
border-start-start-radius: 30px !important;
|
border-start-start-radius: 30px !important;
|
||||||
border-end-start-radius: 30px !important;
|
border-end-start-radius: 30px !important;
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
height: 50px !important;
|
height: 40px !important;
|
||||||
border-start-end-radius: 30px !important;
|
border-start-end-radius: 30px !important;
|
||||||
border-end-end-radius: 30px !important;
|
border-end-end-radius: 30px !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,22 @@
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Card, Input, Button, Space, Flex, Layout, List, Tabs, message } from 'antd';
|
|
||||||
import { useState, useRef, useEffect } from 'react';
|
|
||||||
import HightLightMarkdown from '@/components/highlight-markdown';
|
import HightLightMarkdown from '@/components/highlight-markdown';
|
||||||
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
|
import { aiAssistantConfig } from '@/pages/write/ai-assistant-config';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Flex,
|
||||||
|
Input,
|
||||||
|
Layout,
|
||||||
|
List,
|
||||||
|
Space,
|
||||||
|
Tabs,
|
||||||
|
message,
|
||||||
|
} from 'antd';
|
||||||
|
import axios from 'axios';
|
||||||
import { Document, Packer, Paragraph, TextRun } from 'docx';
|
import { Document, Packer, Paragraph, TextRun } from 'docx';
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
import axios from 'axios';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { aiAssistantConfig } from '@/pages/write/ai-assistant-config';
|
|
||||||
|
|
||||||
const { Sider, Content } = Layout;
|
const { Sider, Content } = Layout;
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
@ -35,10 +45,11 @@ const Write = () => {
|
||||||
|
|
||||||
## ${t('introduction')}
|
## ${t('introduction')}
|
||||||
|
|
||||||
|
|
||||||
## ${t('mainContent')}
|
## ${t('mainContent')}
|
||||||
|
|
||||||
## ${t('conclusion')}
|
## ${t('conclusion')}
|
||||||
`
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
|
@ -58,7 +69,7 @@ const Write = () => {
|
||||||
## ${t('deployment')}
|
## ${t('deployment')}
|
||||||
|
|
||||||
## ${t('maintenance')}
|
## ${t('maintenance')}
|
||||||
`
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
|
@ -78,11 +89,13 @@ const Write = () => {
|
||||||
## ${t('actionItems')}
|
## ${t('actionItems')}
|
||||||
|
|
||||||
## ${t('nextMeeting')}
|
## ${t('nextMeeting')}
|
||||||
`
|
`,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const [selectedTemplate, setSelectedTemplate] = useState<string>('1');
|
const [selectedTemplate, setSelectedTemplate] = useState<string>('1');
|
||||||
const [viewMode, setViewMode] = useState<'edit' | 'preview' | 'split'>('split');
|
const [viewMode, setViewMode] = useState<'edit' | 'preview' | 'split'>(
|
||||||
|
'split',
|
||||||
|
);
|
||||||
|
|
||||||
// 在组件加载时获取对话列表
|
// 在组件加载时获取对话列表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -93,8 +106,8 @@ const Write = () => {
|
||||||
|
|
||||||
const response = await axios.get('/v1/dialog/list', {
|
const response = await axios.get('/v1/dialog/list', {
|
||||||
headers: {
|
headers: {
|
||||||
'authorization': authorization
|
authorization: authorization,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data?.data?.length > 0) {
|
if (response.data?.data?.length > 0) {
|
||||||
|
@ -113,7 +126,9 @@ const Write = () => {
|
||||||
const handleTemplateSelect = (templateId: string) => {
|
const handleTemplateSelect = (templateId: string) => {
|
||||||
setSelectedTemplate(templateId);
|
setSelectedTemplate(templateId);
|
||||||
// 查找选中的模板
|
// 查找选中的模板
|
||||||
const selectedTemplateItem = templates.find(item => item.id === templateId);
|
const selectedTemplateItem = templates.find(
|
||||||
|
(item) => item.id === templateId,
|
||||||
|
);
|
||||||
if (selectedTemplateItem) {
|
if (selectedTemplateItem) {
|
||||||
// 填充模板内容
|
// 填充模板内容
|
||||||
setContent(selectedTemplateItem.content);
|
setContent(selectedTemplateItem.content);
|
||||||
|
@ -123,7 +138,9 @@ const Write = () => {
|
||||||
// 实现保存为Word功能
|
// 实现保存为Word功能
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
// 获取当前选中的模板名称
|
// 获取当前选中的模板名称
|
||||||
const selectedTemplateItem = templates.find(item => item.id === selectedTemplate);
|
const selectedTemplateItem = templates.find(
|
||||||
|
(item) => item.id === selectedTemplate,
|
||||||
|
);
|
||||||
if (!selectedTemplateItem) return;
|
if (!selectedTemplateItem) return;
|
||||||
|
|
||||||
// 生成文件名:模板名+当前日期,例如:学术论文20250319.docx
|
// 生成文件名:模板名+当前日期,例如:学术论文20250319.docx
|
||||||
|
@ -137,11 +154,18 @@ const Write = () => {
|
||||||
// 创建段落数组
|
// 创建段落数组
|
||||||
const paragraphs: Paragraph[] = [];
|
const paragraphs: Paragraph[] = [];
|
||||||
|
|
||||||
tokens.forEach(token => {
|
tokens.forEach((token) => {
|
||||||
if (token.type === 'heading') {
|
if (token.type === 'heading') {
|
||||||
// 处理标题
|
// 处理标题
|
||||||
const headingToken = token as any; // 使用 any 类型绕过 marked.Tokens 问题
|
const headingToken = token as any; // 使用 any 类型绕过 marked.Tokens 问题
|
||||||
let headingType: 'Heading1' | 'Heading2' | 'Heading3' | 'Heading4' | 'Heading5' | 'Heading6' | undefined;
|
let headingType:
|
||||||
|
| 'Heading1'
|
||||||
|
| 'Heading2'
|
||||||
|
| 'Heading3'
|
||||||
|
| 'Heading4'
|
||||||
|
| 'Heading5'
|
||||||
|
| 'Heading6'
|
||||||
|
| undefined;
|
||||||
|
|
||||||
// 根据标题级别设置正确的标题类型
|
// 根据标题级别设置正确的标题类型
|
||||||
switch (headingToken.depth) {
|
switch (headingToken.depth) {
|
||||||
|
@ -172,12 +196,12 @@ const Write = () => {
|
||||||
children: [
|
children: [
|
||||||
new TextRun({
|
new TextRun({
|
||||||
text: headingToken.text,
|
text: headingToken.text,
|
||||||
size: 28 - (headingToken.depth * 2)
|
size: 28 - headingToken.depth * 2,
|
||||||
})
|
}),
|
||||||
],
|
],
|
||||||
heading: headingType,
|
heading: headingType,
|
||||||
spacing: { after: 200 - (headingToken.depth * 40) }
|
spacing: { after: 200 - headingToken.depth * 40 },
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
} else if (token.type === 'paragraph') {
|
} else if (token.type === 'paragraph') {
|
||||||
// 处理段落
|
// 处理段落
|
||||||
|
@ -186,11 +210,11 @@ const Write = () => {
|
||||||
new Paragraph({
|
new Paragraph({
|
||||||
children: [
|
children: [
|
||||||
new TextRun({
|
new TextRun({
|
||||||
text: paraToken.text
|
text: paraToken.text,
|
||||||
})
|
}),
|
||||||
],
|
],
|
||||||
spacing: { after: 80 }
|
spacing: { after: 80 },
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
} else if (token.type === 'space') {
|
} else if (token.type === 'space') {
|
||||||
// 处理空行
|
// 处理空行
|
||||||
|
@ -201,13 +225,15 @@ const Write = () => {
|
||||||
|
|
||||||
// 将所有段落添加到文档中
|
// 将所有段落添加到文档中
|
||||||
const doc = new Document({
|
const doc = new Document({
|
||||||
sections: [{
|
sections: [
|
||||||
children: paragraphs
|
{
|
||||||
}]
|
children: paragraphs,
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
// 生成并下载 Word 文档
|
// 生成并下载 Word 文档
|
||||||
Packer.toBlob(doc).then(blob => {
|
Packer.toBlob(doc).then((blob) => {
|
||||||
saveAs(blob, fileName);
|
saveAs(blob, fileName);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -263,12 +289,14 @@ const Write = () => {
|
||||||
|
|
||||||
// 渲染预览部分
|
// 渲染预览部分
|
||||||
const renderPreview = () => (
|
const renderPreview = () => (
|
||||||
<div style={{
|
<div
|
||||||
|
style={{
|
||||||
height: '100%',
|
height: '100%',
|
||||||
padding: 24,
|
padding: 24,
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<HightLightMarkdown>
|
<HightLightMarkdown>
|
||||||
{content || t('previewPlaceholder')}
|
{content || t('previewPlaceholder')}
|
||||||
</HightLightMarkdown>
|
</HightLightMarkdown>
|
||||||
|
@ -286,7 +314,13 @@ const Write = () => {
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<Flex style={{ height: '100%' }}>
|
<Flex style={{ height: '100%' }}>
|
||||||
<div style={{ width: '50%', borderRight: '1px solid #f0f0f0', height: '100%' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
width: '50%',
|
||||||
|
borderRight: '1px solid #f0f0f0',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{renderEditor()}
|
{renderEditor()}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ width: '50%', height: '100%' }}>
|
<div style={{ width: '50%', height: '100%' }}>
|
||||||
|
@ -297,8 +331,10 @@ const Write = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理 AI 助手问题提交
|
// 处理 AI 助手问题提交
|
||||||
const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const handleAiQuestionSubmit = async (
|
||||||
|
e: React.KeyboardEvent<HTMLTextAreaElement>,
|
||||||
|
) => {
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
@ -342,7 +378,8 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成一个随机的会话ID
|
// 生成一个随机的会话ID
|
||||||
const conversationId = Math.random().toString(36).substring(2) + Date.now().toString(36);
|
const conversationId =
|
||||||
|
Math.random().toString(36).substring(2) + Date.now().toString(36);
|
||||||
|
|
||||||
// 创建新会话
|
// 创建新会话
|
||||||
try {
|
try {
|
||||||
|
@ -350,22 +387,22 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
'v1/conversation/set',
|
'v1/conversation/set',
|
||||||
{
|
{
|
||||||
dialog_id: dialogId,
|
dialog_id: dialogId,
|
||||||
name: "文档撰写对话",
|
name: '文档撰写对话',
|
||||||
is_new: true,
|
is_new: true,
|
||||||
conversation_id: conversationId,
|
conversation_id: conversationId,
|
||||||
message: [
|
message: [
|
||||||
{
|
{
|
||||||
role: "assistant",
|
role: 'assistant',
|
||||||
content: "新对话"
|
content: '新对话',
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'authorization': authorization
|
authorization: authorization,
|
||||||
|
},
|
||||||
|
signal: controller.signal,
|
||||||
},
|
},
|
||||||
signal: controller.signal
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!createSessionResponse.data?.data?.id) {
|
if (!createSessionResponse.data?.data?.id) {
|
||||||
|
@ -394,23 +431,25 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
conversation_id: conversationId,
|
conversation_id: conversationId,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "user",
|
role: 'user',
|
||||||
content: combinedQuestion
|
content: combinedQuestion,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timeout: aiAssistantConfig.api.timeout,
|
timeout: aiAssistantConfig.api.timeout,
|
||||||
headers: {
|
headers: {
|
||||||
'authorization': authorization
|
authorization: authorization,
|
||||||
|
},
|
||||||
|
signal: controller.signal,
|
||||||
},
|
},
|
||||||
signal: controller.signal
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 修改响应处理逻辑,实现在光标位置动态插入内容
|
// 修改响应处理逻辑,实现在光标位置动态插入内容
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
const lines = response.data.split('\n').filter((line: string) => line.trim());
|
const lines = response.data
|
||||||
|
.split('\n')
|
||||||
|
.filter((line: string) => line.trim());
|
||||||
|
|
||||||
// 直接处理每一行数据,不使用嵌套的 Promise
|
// 直接处理每一行数据,不使用嵌套的 Promise
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
@ -422,16 +461,22 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
const answer = jsonData.data.answer;
|
const answer = jsonData.data.answer;
|
||||||
|
|
||||||
// 过滤掉 think 标签内容
|
// 过滤掉 think 标签内容
|
||||||
const cleanedAnswer = answer.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
const cleanedAnswer = answer
|
||||||
|
.replace(/<think>[\s\S]*?<\/think>/g, '')
|
||||||
|
.trim();
|
||||||
// 检查是否还有未闭合的 think 标签
|
// 检查是否还有未闭合的 think 标签
|
||||||
const hasUnclosedThink = cleanedAnswer.includes('<think>') &&
|
const hasUnclosedThink =
|
||||||
|
cleanedAnswer.includes('<think>') &&
|
||||||
(!cleanedAnswer.includes('</think>') ||
|
(!cleanedAnswer.includes('</think>') ||
|
||||||
cleanedAnswer.indexOf('<think>') > cleanedAnswer.lastIndexOf('</think>'));
|
cleanedAnswer.indexOf('<think>') >
|
||||||
|
cleanedAnswer.lastIndexOf('</think>'));
|
||||||
|
|
||||||
if (cleanedAnswer && !hasUnclosedThink) {
|
if (cleanedAnswer && !hasUnclosedThink) {
|
||||||
// 计算新增的内容部分
|
// 计算新增的内容部分
|
||||||
const newContent = cleanedAnswer;
|
const newContent = cleanedAnswer;
|
||||||
const incrementalContent = newContent.substring(lastContent.length);
|
const incrementalContent = newContent.substring(
|
||||||
|
lastContent.length,
|
||||||
|
);
|
||||||
|
|
||||||
// 只有当有新增内容时才更新编辑器
|
// 只有当有新增内容时才更新编辑器
|
||||||
if (incrementalContent) {
|
if (incrementalContent) {
|
||||||
|
@ -444,7 +489,8 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
setContent(beforeCursor + newContent + afterCursor);
|
setContent(beforeCursor + newContent + afterCursor);
|
||||||
|
|
||||||
// 更新光标位置到插入内容之后
|
// 更新光标位置到插入内容之后
|
||||||
const newPosition = initialCursorPos + newContent.length;
|
const newPosition =
|
||||||
|
initialCursorPos + newContent.length;
|
||||||
setCursorPosition(newPosition);
|
setCursorPosition(newPosition);
|
||||||
} else {
|
} else {
|
||||||
// 如果没有光标位置,则追加到末尾
|
// 如果没有光标位置,则追加到末尾
|
||||||
|
@ -460,7 +506,7 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
|
|
||||||
// 添加一个小延迟,让UI有时间更新
|
// 添加一个小延迟,让UI有时间更新
|
||||||
if (i < lines.length - 1) {
|
if (i < lines.length - 1) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 10));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -479,14 +525,18 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
|
|
||||||
// 在处理完所有响应后,删除临时会话
|
// 在处理完所有响应后,删除临时会话
|
||||||
try {
|
try {
|
||||||
await axios.post('/v1/conversation/rm', {
|
await axios.post(
|
||||||
|
'/v1/conversation/rm',
|
||||||
|
{
|
||||||
conversation_ids: [conversationId],
|
conversation_ids: [conversationId],
|
||||||
dialog_id: dialogId
|
dialog_id: dialogId,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'authorization': authorization
|
authorization: authorization,
|
||||||
}
|
},
|
||||||
});
|
},
|
||||||
|
);
|
||||||
console.log('临时会话已删除:', conversationId);
|
console.log('临时会话已删除:', conversationId);
|
||||||
|
|
||||||
// 处理完成后,重新设置光标焦点
|
// 处理完成后,重新设置光标焦点
|
||||||
|
@ -506,13 +556,9 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
setAiQuestion('');
|
setAiQuestion('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ... existing code ...
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ height: 'auto', padding: 24, overflow: 'hidden'}}>
|
<Layout style={{ height: 'auto', padding: 24, overflow: 'hidden' }}>
|
||||||
<Sider
|
<Sider
|
||||||
width={280}
|
width={280}
|
||||||
theme="light"
|
theme="light"
|
||||||
|
@ -520,20 +566,29 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
borderRight: '1px solid #f0f0f0',
|
borderRight: '1px solid #f0f0f0',
|
||||||
padding: '0 16px',
|
padding: '0 16px',
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
height: '100%'
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<List
|
<List
|
||||||
header={<div className="templateHeader" style={{ textAlign: 'center'}}>{t('templateList')}</div>}
|
header={
|
||||||
|
<div className="templateHeader" style={{ textAlign: 'center' }}>
|
||||||
|
{t('templateList')}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
dataSource={templates}
|
dataSource={templates}
|
||||||
renderItem={(item) => (
|
renderItem={(item) => (
|
||||||
<List.Item
|
<List.Item
|
||||||
actions={[<a key="select" onClick={() => handleTemplateSelect(item.id)}></a>]}
|
actions={[
|
||||||
|
<a
|
||||||
|
key="select"
|
||||||
|
onClick={() => handleTemplateSelect(item.id)}
|
||||||
|
></a>,
|
||||||
|
]}
|
||||||
style={{
|
style={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
background: selectedTemplate === item.id ? '#f0f7ff' : 'none',
|
background: selectedTemplate === item.id ? '#f0f7ff' : 'none',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
paddingLeft: 12
|
paddingLeft: 12,
|
||||||
}}
|
}}
|
||||||
onClick={() => handleTemplateSelect(item.id)}
|
onClick={() => handleTemplateSelect(item.id)}
|
||||||
>
|
>
|
||||||
|
@ -544,7 +599,11 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
</Sider>
|
</Sider>
|
||||||
<Content style={{ paddingLeft: 24 }}>
|
<Content style={{ paddingLeft: 24 }}>
|
||||||
<Flex vertical style={{ height: '100%', gap: 16 }}>
|
<Flex vertical style={{ height: '100%', gap: 16 }}>
|
||||||
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
|
<Flex
|
||||||
|
justify="space-between"
|
||||||
|
align="center"
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
>
|
||||||
<h2 className="pageTitle">{t('writeDocument')}</h2>
|
<h2 className="pageTitle">{t('writeDocument')}</h2>
|
||||||
<Space>
|
<Space>
|
||||||
<Button.Group>
|
<Button.Group>
|
||||||
|
@ -572,12 +631,14 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Card bodyStyle={{
|
<Card
|
||||||
|
bodyStyle={{
|
||||||
padding: 0,
|
padding: 0,
|
||||||
height: 'calc(70vh - 100px)',
|
height: 'calc(70vh - 100px)',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
position: 'relative'
|
position: 'relative',
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</Card>
|
</Card>
|
||||||
<Card
|
<Card
|
||||||
|
@ -587,16 +648,15 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: 10
|
gap: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="dialogContainer">
|
<div className="dialogContainer">
|
||||||
{isAiLoading && (
|
{isAiLoading && (
|
||||||
<div style={{ textAlign: 'center'}}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<div>AI 助手正在思考中...</div>
|
<div>AI 助手正在思考中...</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
className="inputArea"
|
className="inputArea"
|
||||||
placeholder={t('askAI')}
|
placeholder={t('askAI')}
|
||||||
|
|
3196
web/yarn.lock
3196
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue