Update from Vibe Studio

This commit is contained in:
Vibe Studio
2026-01-23 02:29:22 +00:00
parent 976704f9ec
commit d9cfd126eb
7 changed files with 2372 additions and 598 deletions

2602
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,14 +18,16 @@
"less": "^4.2.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.28.1"
"react-markdown": "^10.1.0",
"react-router-dom": "^6.28.1",
"remark-gfm": "^4.0.1"
},
"devDependencies": {
"@tailwindcss/vite": "4.0.12",
"@types/node": "^22.10.5",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"@vitejs/plugin-react": "^4.7.0",
"path": "^0.12.7",
"prettier-plugin-tailwindcss": "^0.1.7",
"tailwindcss": "4.0.12",

View File

@@ -0,0 +1,38 @@
export interface ContractReviewRequest {
contract_text: string
knowledge_base: string
}
export interface DifyRequest {
inputs: {
contract_text: string
knowledge_base: string
}
query: string
response_mode: string
user: string
}
/**
* 发送合同审核请求到Dify平台
*/
export function submitContractReview(contract_text: string, knowledge_base: string) {
const requestBody: DifyRequest = {
inputs: {
contract_text,
knowledge_base
},
query: '1',
response_mode: 'streaming',
user: 'contract-review-user'
}
return fetch('https://copilot.sino-bridge.com/v1/chat-messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer app-rzQ05wbVNUcNaoE7nnzmBueE'
},
body: JSON.stringify(requestBody)
})
}

View File

@@ -0,0 +1,292 @@
import { useState } from 'react'
import { Button, Card, Form, Input, message, Row, Col, Divider, Typography, Space } from 'antd'
import { FileTextOutlined, DownloadOutlined } from '@ant-design/icons'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { submitContractReview } from '@/api/contract-review'
const { Title, Paragraph } = Typography
const { TextArea } = Input
const ContractReview = () => {
const [form] = Form.useForm()
const [loading, setLoading] = useState(false)
const [reviewResult, setReviewResult] = useState('')
const [currentContract, setCurrentContract] = useState('')
const maxChars = 10000
const handleReview = async () => {
try {
const contract_text = form.getFieldValue('contract_fragment') || ''
const knowledge_base = '通用合同审核规则' // 固定使用通用知识库
// 验证输入
if (!contract_text.trim()) {
message.warning('请输入需要审核的合同片段')
return
}
setLoading(true)
setReviewResult('')
const response = await submitContractReview(contract_text, knowledge_base)
if (!response.ok) {
const errorText = await response.text()
throw new Error(`审核请求失败: ${response.status} ${errorText}`)
}
if (!response.body) {
throw new Error('响应体为空')
}
const reader = response.body.getReader()
const decoder = new TextDecoder('utf-8')
let buffer = ''
let fullContent = ''
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop() || ''
for (const line of lines) {
const trimmedLine = line.trim()
if (!trimmedLine || trimmedLine === 'data: [DONE]') {
if (trimmedLine === 'data: [DONE]') {
message.success('审核完成')
setLoading(false)
return
}
continue
}
if (trimmedLine.startsWith('data: ')) {
try {
const data = trimmedLine.slice(6)
const parsed = JSON.parse(data)
if (parsed.event === 'message' && parsed.answer) {
fullContent += parsed.answer
setReviewResult(fullContent)
} else if (parsed.event === 'error') {
throw new Error(parsed.message || 'Dify API 返回错误')
}
} catch (parseError) {
console.warn('跳过无法解析的行:', trimmedLine)
}
}
}
}
} finally {
reader.releaseLock()
}
if (fullContent) {
message.success('审核完成')
} else {
throw new Error('未收到审核结果')
}
} catch (error) {
console.error('审核错误:', error)
message.error(error instanceof Error ? error.message : '审核失败,请稍后重试')
} finally {
setLoading(false)
}
}
const handleClear = () => {
form.resetFields()
setReviewResult('')
setCurrentContract('')
message.info('已清空内容')
}
const handleExport = () => {
if (!reviewResult) {
message.warning('没有可导出的内容')
return
}
// 创建下载链接
const element = document.createElement('a')
const file = new Blob([reviewResult], { type: 'text/plain;charset=utf-8' })
element.href = URL.createObjectURL(file)
element.download = `合同审核报告_${new Date().toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
}).replace(/\//g, '-')}.txt`
document.body.appendChild(element)
element.click()
document.body.removeChild(element)
message.success('导出成功')
}
return (
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '20px' }}>
<Space direction="vertical" size="small" style={{ marginBottom: '30px', textAlign: 'center' }}>
<Title level={2} style={{ color: '#1890ff', marginBottom: '8px' }}>
</Title>
<Paragraph style={{ color: '#666', fontSize: '14px', marginBottom: 0 }}>
AI驱动的合同智能审核工具
</Paragraph>
</Space>
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
{/* 参数输入区 */}
<Card title="参数输入区">
<Form form={form} layout="vertical">
<Form.Item
name="contract_fragment"
label={
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
<span></span>
<span style={{ color: currentContract.length > maxChars ? '#ff4d4f' : '#999' }}>
{currentContract.length} / {maxChars}
</span>
</div>
}
rules={[
{ required: true, message: '请输入需要审核的合同片段' },
{ max: maxChars, message: `输入内容不能超过${maxChars}个字符` }
]}
>
<TextArea
placeholder="请输入需要审核的合同片段内容最多10000个字符"
rows={10}
value={currentContract}
onChange={(e) => setCurrentContract(e.target.value)}
showCount
maxLength={maxChars}
style={{ fontSize: '14px' }}
/>
</Form.Item>
{/* 知识库选择已隐藏,默认使用通用合同审核规则 */}
<input type="hidden" name="knowledge_base" value="通用合同审核规则" />
</Form>
</Card>
{/* 操作按钮区 */}
<div style={{ display: 'flex', justifyContent: 'center', gap: '16px' }}>
<Button
type="primary"
size="large"
onClick={handleReview}
loading={loading}
icon={<FileTextOutlined />}
style={{ minWidth: '140px', height: '44px', fontSize: '16px' }}
>
</Button>
<Button
size="large"
onClick={handleClear}
disabled={loading}
style={{ minWidth: '120px', height: '44px', fontSize: '16px' }}
>
</Button>
<Button
size="large"
onClick={handleExport}
disabled={!reviewResult || loading}
icon={<DownloadOutlined />}
style={{ minWidth: '120px', height: '44px', fontSize: '16px' }}
>
</Button>
</div>
{/* 内容展示区 */}
<Card
title="审核结果"
extra={
reviewResult && (
<span style={{ color: '#52c41a', fontWeight: 'bold' }}>
</span>
)
}
>
<div
style={{
minHeight: '300px',
maxHeight: '600px',
overflowY: 'auto',
padding: '20px',
backgroundColor: '#fafafa',
border: '1px solid #d9d9d9',
borderRadius: '6px'
}}
>
{loading ? (
<div style={{ textAlign: 'center', color: '#999', padding: '40px' }}>
<div style={{ fontSize: '16px', marginBottom: '8px' }}>🤖 AI ...</div>
<div style={{ fontSize: '12px' }}></div>
</div>
) : reviewResult ? (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
// 自定义样式
h1: ({ children }) => <h1 style={{ color: '#1890ff', fontSize: '20px', marginBottom: '16px', borderBottom: '2px solid #1890ff', paddingBottom: '8px' }}>{children}</h1>,
h2: ({ children }) => <h2 style={{ color: '#1890ff', fontSize: '18px', marginBottom: '12px', marginTop: '20px' }}>{children}</h2>,
h3: ({ children }) => <h3 style={{ color: '#333', fontSize: '16px', marginBottom: '10px', marginTop: '16px' }}>{children}</h3>,
p: ({ children }) => <p style={{ marginBottom: '12px', lineHeight: '1.6', color: '#333' }}>{children}</p>,
table: ({ children }) => (
<div style={{ overflowX: 'auto', marginBottom: '16px' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '14px' }}>{children}</table>
</div>
),
th: ({ children }) => <th style={{ border: '1px solid #d9d9d9', padding: '8px 12px', backgroundColor: '#f5f5f5', fontWeight: 'bold', textAlign: 'left' }}>{children}</th>,
td: ({ children }) => <td style={{ border: '1px solid #d9d9d9', padding: '8px 12px' }}>{children}</td>,
ul: ({ children }) => <ul style={{ marginBottom: '12px', paddingLeft: '20px' }}>{children}</ul>,
ol: ({ children }) => <ol style={{ marginBottom: '12px', paddingLeft: '20px' }}>{children}</ol>,
li: ({ children }) => <li style={{ marginBottom: '4px', lineHeight: '1.6' }}>{children}</li>,
blockquote: ({ children }) => (
<blockquote style={{ borderLeft: '4px solid #1890ff', paddingLeft: '16px', marginLeft: 0, marginBottom: '12px', fontStyle: 'italic', backgroundColor: '#f0f9ff', padding: '12px 16px' }}>
{children}
</blockquote>
),
code: ({ children }) => (
<code style={{ backgroundColor: '#f5f5f5', padding: '2px 4px', borderRadius: '3px', fontSize: '13px', fontFamily: 'Monaco, Consolas, monospace' }}>
{children}
</code>
),
pre: ({ children }) => (
<pre style={{ backgroundColor: '#f5f5f5', padding: '12px', borderRadius: '6px', overflowX: 'auto', marginBottom: '12px' }}>
{children}
</pre>
),
strong: ({ children }) => <strong style={{ fontWeight: 'bold', color: '#1890ff' }}>{children}</strong>
}}
>
{reviewResult}
</ReactMarkdown>
) : (
<div style={{ textAlign: 'center', color: '#999', padding: '60px' }}>
<FileTextOutlined style={{ fontSize: '48px', marginBottom: '16px', display: 'block' }} />
<div></div>
<div style={{ fontSize: '12px', marginTop: '8px' }}>
"开始审核"
</div>
</div>
)}
</div>
</Card>
</div>
</div>
)
}
export default ContractReview

View File

@@ -1,6 +1,6 @@
import { Card, Row, Col, Typography, Space } from 'antd'
import { Link } from 'react-router-dom'
import { RobotOutlined, TranslationOutlined, FileTextOutlined, EditOutlined } from '@ant-design/icons'
import { RobotOutlined, TranslationOutlined, FileTextOutlined, EditOutlined, CheckCircleOutlined } from '@ant-design/icons'
const { Title, Paragraph } = Typography
@@ -34,6 +34,12 @@ const cards = [
description: '更智能的错别字检测与修正系统,支持分类和建议',
icon: <EditOutlined style={{ fontSize: 32, color: '#13c2c2' }} />,
link: '/spell-check-system'
},
{
title: '合同智能审核',
description: 'AI驱动的合同智能审核工具快速识别风险点并提供专业建议',
icon: <CheckCircleOutlined style={{ fontSize: 32, color: '#1890ff' }} />,
link: '/contract-review'
}
]

View File

@@ -1,8 +1,9 @@
import { useState } from 'react'
import { Button, Card, Form, Input, message, Space } from 'antd'
import { PageHeader } from '@ant-design/pro-components'
import { Button, Card, Form, Input, message, Space, Typography } from 'antd'
import { translateChineseToEnglish } from '@/api/zh-en-translator'
const { Title, Paragraph } = Typography
const ZhEnTranslator = () => {
const [form] = Form.useForm()
const [loading, setLoading] = useState(false)
@@ -115,11 +116,14 @@ const ZhEnTranslator = () => {
return (
<div style={{ maxWidth: '900px', margin: '0 auto', padding: '20px' }}>
<PageHeader
title="中文转英文翻译助手"
subTitle="专业的中文到英文文本翻译工具,支持流式输出,翻译结果地道自然"
style={{ marginBottom: '30px' }}
/>
<Space direction="vertical" size="small" style={{ marginBottom: '30px', textAlign: 'center' }}>
<Title level={2} style={{ color: '#1890ff', marginBottom: '8px' }}>
</Title>
<Paragraph style={{ color: '#666', fontSize: '14px', marginBottom: 0 }}>
</Paragraph>
</Space>
<Space direction="vertical" style={{ width: '100%' }} size="large">
<Card title="参数输入区">

View File

@@ -52,6 +52,10 @@ const router: RouteObject[] = [
path: '/spell-check-system',
element: LazyLoad(lazy(() => import('@/pages/spell-check-system')))
},
{
path: '/contract-review',
element: LazyLoad(lazy(() => import('@/pages/contract-review')))
},
{
path: '/404',
element: <>404</>