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

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