修复搜索界面样式,更新README文档;

This commit is contained in:
zstar 2025-03-27 12:55:01 +08:00
parent 7d2042673e
commit 8b8ce06e25
12 changed files with 2899 additions and 1554 deletions

6
.gitignore vendored
View File

@ -8,6 +8,8 @@ cv/
layout_app.py
api/flask_session
# 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
Cargo.lock
@ -41,3 +43,7 @@ nltk_data/
# 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]*
web/public/logo_secret.svg
web/public/logo_old.svg
web/public/logo.svg
web/src/locales/zh.ts

View File

@ -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/
![交流群.jpg](assets/group.jpg)
## License
版权说明:本项目在 Ragflow 项目基础上进行二开,需要遵守 Ragflow 的开源协议,如下

View File

@ -2,7 +2,7 @@
# Available options:
# - `elasticsearch` (default)
# - `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

View File

@ -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)

4
web/.gitignore vendored
View File

@ -7,3 +7,7 @@
/src/.umi-test
/dist
.swc
/public/logo_secret.svg
/public/logo_old.svg
/public/logo.svg
/src/locales/zh.ts

72
web/package-lock.json generated
View File

@ -52,8 +52,10 @@
"clsx": "^2.1.1",
"cmdk": "^1.0.4",
"dayjs": "^1.11.10",
"docx": "^9.3.0",
"dompurify": "^3.1.6",
"eventsource-parser": "^1.1.2",
"file-saver": "^2.0.5",
"human-id": "^4.1.1",
"i18next": "^23.7.16",
"i18next-browser-languagedetector": "^8.0.0",
@ -102,6 +104,7 @@
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@types/dompurify": "^3.0.5",
"@types/file-saver": "^2.0.7",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.14.202",
"@types/react": "^18.0.33",
@ -5746,6 +5749,13 @@
"@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": {
"version": "7946.0.16",
"resolved": "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.16.tgz",
@ -11984,6 +11994,41 @@
"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": {
"version": "0.6.3",
"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_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": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/file-selector/-/file-selector-2.1.2.tgz",
@ -26425,8 +26476,7 @@
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"license": "ISC",
"optional": true
"license": "ISC"
},
"node_modules/saxes": {
"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": {
"version": "4.0.0",
"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

View File

@ -56,7 +56,7 @@ export default {
nicknamePlaceholder: '请输入名称',
register: '创建账户',
continue: '继续',
title: 'Ragflow-Plus大模型平台',
title: '西电711大模型平台',
description:
'免费注册以探索顶级 RAG 技术。 创建知识库和人工智能来增强您的业务',
review: '来自 500 多条评论',

View File

@ -105,7 +105,7 @@
.input() {
:global(.ant-input-affix-wrapper) {
padding: 4px 12px;
padding: 0px 12px;
border-start-start-radius: 30px !important;
border-end-start-radius: 30px !important;
}
@ -116,7 +116,7 @@
height: 40px;
}
button {
height: 50px !important;
height: 40px !important;
border-start-end-radius: 30px !important;
border-end-end-radius: 30px !important;
}

View File

@ -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 { 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 { saveAs } from 'file-saver';
import { marked } from 'marked';
import axios from 'axios';
import { aiAssistantConfig } from '@/pages/write/ai-assistant-config';
import { useEffect, useRef, useState } from 'react';
const { Sider, Content } = Layout;
const { TabPane } = Tabs;
@ -35,10 +45,11 @@ const Write = () => {
## ${t('introduction')}
## ${t('mainContent')}
## ${t('conclusion')}
`
`,
},
{
id: '2',
@ -58,7 +69,7 @@ const Write = () => {
## ${t('deployment')}
## ${t('maintenance')}
`
`,
},
{
id: '3',
@ -78,11 +89,13 @@ const Write = () => {
## ${t('actionItems')}
## ${t('nextMeeting')}
`
`,
},
]);
const [selectedTemplate, setSelectedTemplate] = useState<string>('1');
const [viewMode, setViewMode] = useState<'edit' | 'preview' | 'split'>('split');
const [viewMode, setViewMode] = useState<'edit' | 'preview' | 'split'>(
'split',
);
// 在组件加载时获取对话列表
useEffect(() => {
@ -93,8 +106,8 @@ const Write = () => {
const response = await axios.get('/v1/dialog/list', {
headers: {
'authorization': authorization
}
authorization: authorization,
},
});
if (response.data?.data?.length > 0) {
@ -113,7 +126,9 @@ const Write = () => {
const handleTemplateSelect = (templateId: string) => {
setSelectedTemplate(templateId);
// 查找选中的模板
const selectedTemplateItem = templates.find(item => item.id === templateId);
const selectedTemplateItem = templates.find(
(item) => item.id === templateId,
);
if (selectedTemplateItem) {
// 填充模板内容
setContent(selectedTemplateItem.content);
@ -123,7 +138,9 @@ const Write = () => {
// 实现保存为Word功能
const handleSave = () => {
// 获取当前选中的模板名称
const selectedTemplateItem = templates.find(item => item.id === selectedTemplate);
const selectedTemplateItem = templates.find(
(item) => item.id === selectedTemplate,
);
if (!selectedTemplateItem) return;
// 生成文件名:模板名+当前日期例如学术论文20250319.docx
@ -137,11 +154,18 @@ const Write = () => {
// 创建段落数组
const paragraphs: Paragraph[] = [];
tokens.forEach(token => {
tokens.forEach((token) => {
if (token.type === 'heading') {
// 处理标题
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) {
@ -172,12 +196,12 @@ const Write = () => {
children: [
new TextRun({
text: headingToken.text,
size: 28 - (headingToken.depth * 2)
})
size: 28 - headingToken.depth * 2,
}),
],
heading: headingType,
spacing: { after: 200 - (headingToken.depth * 40) }
})
spacing: { after: 200 - headingToken.depth * 40 },
}),
);
} else if (token.type === 'paragraph') {
// 处理段落
@ -186,11 +210,11 @@ const Write = () => {
new Paragraph({
children: [
new TextRun({
text: paraToken.text
})
text: paraToken.text,
}),
],
spacing: { after: 80 }
})
spacing: { after: 80 },
}),
);
} else if (token.type === 'space') {
// 处理空行
@ -201,13 +225,15 @@ const Write = () => {
// 将所有段落添加到文档中
const doc = new Document({
sections: [{
children: paragraphs
}]
sections: [
{
children: paragraphs,
},
],
});
// 生成并下载 Word 文档
Packer.toBlob(doc).then(blob => {
Packer.toBlob(doc).then((blob) => {
saveAs(blob, fileName);
});
};
@ -263,12 +289,14 @@ const Write = () => {
// 渲染预览部分
const renderPreview = () => (
<div style={{
<div
style={{
height: '100%',
padding: 24,
overflow: 'auto',
fontSize: 16,
}}>
}}
>
<HightLightMarkdown>
{content || t('previewPlaceholder')}
</HightLightMarkdown>
@ -286,7 +314,13 @@ const Write = () => {
default:
return (
<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()}
</div>
<div style={{ width: '50%', height: '100%' }}>
@ -297,8 +331,10 @@ const Write = () => {
}
};
// 处理 AI 助手问题提交
const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
// 处理 AI 助手问题提交
const handleAiQuestionSubmit = async (
e: React.KeyboardEvent<HTMLTextAreaElement>,
) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
@ -342,7 +378,8 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
}
// 生成一个随机的会话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 {
@ -350,22 +387,22 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
'v1/conversation/set',
{
dialog_id: dialogId,
name: "文档撰写对话",
name: '文档撰写对话',
is_new: true,
conversation_id: conversationId,
message: [
{
role: "assistant",
content: "新对话"
}
]
role: 'assistant',
content: '新对话',
},
],
},
{
headers: {
'authorization': authorization
authorization: authorization,
},
signal: controller.signal,
},
signal: controller.signal
}
);
if (!createSessionResponse.data?.data?.id) {
@ -394,23 +431,25 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
conversation_id: conversationId,
messages: [
{
role: "user",
content: combinedQuestion
}
]
role: 'user',
content: combinedQuestion,
},
],
},
{
timeout: aiAssistantConfig.api.timeout,
headers: {
'authorization': authorization
authorization: authorization,
},
signal: controller.signal,
},
signal: controller.signal
}
);
// 修改响应处理逻辑,实现在光标位置动态插入内容
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
for (let i = 0; i < lines.length; i++) {
@ -422,16 +461,22 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
const answer = jsonData.data.answer;
// 过滤掉 think 标签内容
const cleanedAnswer = answer.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
const cleanedAnswer = answer
.replace(/<think>[\s\S]*?<\/think>/g, '')
.trim();
// 检查是否还有未闭合的 think 标签
const hasUnclosedThink = cleanedAnswer.includes('<think>') &&
const hasUnclosedThink =
cleanedAnswer.includes('<think>') &&
(!cleanedAnswer.includes('</think>') ||
cleanedAnswer.indexOf('<think>') > cleanedAnswer.lastIndexOf('</think>'));
cleanedAnswer.indexOf('<think>') >
cleanedAnswer.lastIndexOf('</think>'));
if (cleanedAnswer && !hasUnclosedThink) {
// 计算新增的内容部分
const newContent = cleanedAnswer;
const incrementalContent = newContent.substring(lastContent.length);
const incrementalContent = newContent.substring(
lastContent.length,
);
// 只有当有新增内容时才更新编辑器
if (incrementalContent) {
@ -444,7 +489,8 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
setContent(beforeCursor + newContent + afterCursor);
// 更新光标位置到插入内容之后
const newPosition = initialCursorPos + newContent.length;
const newPosition =
initialCursorPos + newContent.length;
setCursorPosition(newPosition);
} else {
// 如果没有光标位置,则追加到末尾
@ -460,7 +506,7 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
// 添加一个小延迟让UI有时间更新
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 {
await axios.post('/v1/conversation/rm', {
await axios.post(
'/v1/conversation/rm',
{
conversation_ids: [conversationId],
dialog_id: dialogId
}, {
dialog_id: dialogId,
},
{
headers: {
'authorization': authorization
}
});
authorization: authorization,
},
},
);
console.log('临时会话已删除:', conversationId);
// 处理完成后,重新设置光标焦点
@ -506,13 +556,9 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
setAiQuestion('');
}
}
};
// ... existing code ...
};
return (
<Layout style={{ height: 'auto', padding: 24, overflow: 'hidden'}}>
<Layout style={{ height: 'auto', padding: 24, overflow: 'hidden' }}>
<Sider
width={280}
theme="light"
@ -520,20 +566,29 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
borderRight: '1px solid #f0f0f0',
padding: '0 16px',
overflow: 'auto',
height: '100%'
height: '100%',
}}
>
<List
header={<div className="templateHeader" style={{ textAlign: 'center'}}>{t('templateList')}</div>}
header={
<div className="templateHeader" style={{ textAlign: 'center' }}>
{t('templateList')}
</div>
}
dataSource={templates}
renderItem={(item) => (
<List.Item
actions={[<a key="select" onClick={() => handleTemplateSelect(item.id)}></a>]}
actions={[
<a
key="select"
onClick={() => handleTemplateSelect(item.id)}
></a>,
]}
style={{
cursor: 'pointer',
background: selectedTemplate === item.id ? '#f0f7ff' : 'none',
borderRadius: 8,
paddingLeft: 12
paddingLeft: 12,
}}
onClick={() => handleTemplateSelect(item.id)}
>
@ -544,7 +599,11 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
</Sider>
<Content style={{ paddingLeft: 24 }}>
<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>
<Space>
<Button.Group>
@ -572,12 +631,14 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
</Button>
</Space>
</Flex>
<Card bodyStyle={{
<Card
bodyStyle={{
padding: 0,
height: 'calc(70vh - 100px)',
overflow: 'hidden',
position: 'relative'
}}>
position: 'relative',
}}
>
{renderContent()}
</Card>
<Card
@ -587,16 +648,15 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
height: 'auto',
display: 'flex',
flexDirection: 'column',
gap: 10
gap: 10,
}}
>
<div className="dialogContainer">
{isAiLoading && (
<div style={{ textAlign: 'center'}}>
<div style={{ textAlign: 'center' }}>
<div>AI ...</div>
</div>
)}
<Input.TextArea
className="inputArea"
placeholder={t('askAI')}

File diff suppressed because it is too large Load Diff