Files
copilot-toolbox-template-sh…/src/pages/home/index.tsx
2026-01-20 09:19:36 +00:00

374 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import { Button, Card, Form, Input, message, Space, Typography } from 'antd'
import { reviewContract } from '@/api/contract-review'
const { Title, Paragraph, Text } = Typography
const { TextArea } = Input
const HomePage: React.FC = () => {
const [form] = Form.useForm()
const [loading, setLoading] = useState(false)
const [reviewResult, setReviewResult] = useState('')
const [currentInput, setCurrentInput] = useState('')
const maxChars = 5000
// 简单的markdown处理函数
const formatMarkdown = (text: string) => {
if (!text) return []
const lines = text.split('\n')
const elements: React.ReactNode[] = []
let currentList: string[] = []
let inTable = false
let tableHeaders: string[] = []
let tableRows: string[][] = []
const flushList = () => {
if (currentList.length > 0) {
elements.push(
<ul key={elements.length} style={{ paddingLeft: '20px', margin: '12px 0' }}>
{currentList.map((item, index) => (
<li key={index} style={{ margin: '4px 0' }}>{item}</li>
))}
</ul>
)
currentList = []
}
}
const flushTable = () => {
if (tableHeaders.length > 0) {
elements.push(
<div key={elements.length} style={{ margin: '16px 0', overflowX: 'auto' }}>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr>
{tableHeaders.map((header, index) => (
<th key={index} style={{
border: '1px solid #d9d9d9',
padding: '8px 12px',
backgroundColor: '#f5f5f5',
fontWeight: 'bold',
textAlign: 'left'
}}>
{header}
</th>
))}
</tr>
</thead>
<tbody>
{tableRows.map((row, rowIndex) => (
<tr key={rowIndex}>
{row.map((cell, cellIndex) => (
<td key={cellIndex} style={{
border: '1px solid #d9d9d9',
padding: '8px 12px'
}}>
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
tableHeaders = []
tableRows = []
inTable = false
}
}
lines.forEach((line, index) => {
const trimmedLine = line.trim()
// 处理标题
if (trimmedLine.startsWith('# ')) {
flushList()
flushTable()
elements.push(
<Title key={elements.length} level={1} style={{ color: '#1890ff', borderBottom: '1px solid #d9d9d9', paddingBottom: '8px', marginBottom: '16px' }}>
{trimmedLine.substring(2)}
</Title>
)
} else if (trimmedLine.startsWith('## ')) {
flushList()
flushTable()
elements.push(
<Title key={elements.length} level={2} style={{ color: '#1890ff', marginTop: '24px', marginBottom: '12px' }}>
{trimmedLine.substring(3)}
</Title>
)
} else if (trimmedLine.startsWith('### ')) {
flushList()
flushTable()
elements.push(
<Title key={elements.length} level={3} style={{ color: '#1890ff', marginTop: '20px', marginBottom: '10px' }}>
{trimmedLine.substring(4)}
</Title>
)
}
// 处理分割线
else if (trimmedLine === '---') {
flushList()
flushTable()
elements.push(
<hr key={elements.length} style={{ border: 'none', borderTop: '1px solid #d9d9d9', margin: '24px 0' }} />
)
}
// 处理列表项
else if (trimmedLine.startsWith('- ') || trimmedLine.startsWith('* ')) {
flushTable()
currentList.push(trimmedLine.substring(2))
}
// 处理表格
else if (trimmedLine.includes('|') && !inTable) {
flushList()
inTable = true
tableHeaders = trimmedLine.split('|').map(h => h.trim()).filter(h => h)
} else if (trimmedLine.includes('|') && inTable && !trimmedLine.includes('---')) {
const row = trimmedLine.split('|').map(c => c.trim()).filter(c => c)
if (row.length > 0) {
tableRows.push(row)
}
} else if (trimmedLine.includes('|') && inTable && trimmedLine.includes('---')) {
// 跳过分隔行
}
// 处理普通段落
else if (trimmedLine) {
flushList()
flushTable()
// 处理加粗文本
const boldRegex = /\*\*(.*?)\*\*/g
const parts = trimmedLine.split(boldRegex)
if (parts.length > 1) {
elements.push(
<Paragraph key={elements.length} style={{ margin: '12px 0' }}>
{parts.map((part, partIndex) =>
partIndex % 2 === 1 ?
<Text key={partIndex} strong style={{ color: '#262626' }}>{part}</Text> :
part
)}
</Paragraph>
)
} else {
elements.push(
<Paragraph key={elements.length} style={{ margin: '12px 0', lineHeight: '1.6' }}>
{trimmedLine}
</Paragraph>
)
}
}
})
// 清理剩余的内容
flushList()
flushTable()
return elements
}
const handleReview = async () => {
try {
const contract_text = form.getFieldValue('contract_text') || ''
if (!contract_text.trim()) {
message.warning('请输入合同片段内容')
return
}
setLoading(true)
setReviewResult('')
const response = await reviewContract(contract_text)
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 handleReset = () => {
form.resetFields()
setReviewResult('')
setCurrentInput('')
message.info('已重置内容')
}
const handleReReview = () => {
handleReview()
}
return (
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '24px' }}>
<Title level={2} style={{ textAlign: 'center', marginBottom: '30px' }}></Title>
<Paragraph style={{ textAlign: 'center', marginBottom: '40px', fontSize: '16px' }}>
AI技术对合同片段进行智能审核
</Paragraph>
<Space direction="vertical" style={{ width: '100%' }} size="large">
{/* 参数输入区 */}
<Card title="参数输入区">
<Form form={form} layout="vertical">
<Form.Item
name="contract_text"
label={
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
<span></span>
<span style={{ color: currentInput.length > maxChars ? '#ff4d4f' : '#999' }}>
{currentInput.length} / {maxChars}
</span>
</div>
}
rules={[
{ required: true, message: '请输入合同片段内容' },
{ max: maxChars, message: `输入内容不能超过${maxChars}个字符` }
]}
>
<TextArea
placeholder="请输入需要审核的合同片段内容最多5000个字符"
rows={8}
value={currentInput}
onChange={(e) => setCurrentInput(e.target.value)}
showCount
maxLength={maxChars}
style={{ fontSize: '14px', fontFamily: 'monospace' }}
/>
</Form.Item>
</Form>
</Card>
{/* 操作按钮区 */}
<Space style={{ width: '100%', justifyContent: 'center' }} size="middle">
<Button
type="primary"
size="large"
onClick={handleReview}
loading={loading}
style={{ minWidth: '120px', height: '40px', fontSize: '16px' }}
>
</Button>
{reviewResult && (
<Button
size="large"
onClick={handleReReview}
disabled={loading}
style={{ minWidth: '120px', height: '40px', fontSize: '16px' }}
>
</Button>
)}
<Button
size="large"
onClick={handleReset}
disabled={loading}
style={{ minWidth: '120px', height: '40px', fontSize: '16px' }}
>
</Button>
</Space>
{/* 内容展示区 */}
<Card title="审核结果">
<div
style={{
backgroundColor: '#fafafa',
border: '1px solid #d9d9d9',
borderRadius: '4px',
padding: '15px',
minHeight: '300px',
maxHeight: '500px',
overflowY: 'auto',
fontSize: '14px',
lineHeight: '1.6'
}}
>
{loading ? (
<div style={{ textAlign: 'center', color: '#999', padding: '20px' }}>
...
</div>
) : reviewResult ? (
<div>
{formatMarkdown(reviewResult)}
</div>
) : (
<div style={{ textAlign: 'center', color: '#999' }}>
</div>
)}
</div>
</Card>
</Space>
</div>
)
}
export default HomePage