| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- 'use client'
- import { useEffect, useState } from 'react'
- import { RiCloseLine, RiHistoryLine } from '@remixicon/react'
- import Modal from '@/app/components/base/modal'
- import Button from '@/app/components/base/button'
- import Loading from '@/app/components/base/loading'
- import { fetchSegmentVersions, type SegmentVersion } from '@/service/datasets'
- import cn from '@/utils/classnames'
- type VersionHistoryModalProps = {
- datasetId: string
- documentId: string
- segmentId: string
- isOpen: boolean
- onClose: () => void
- }
- export default function VersionHistoryModal({
- datasetId,
- documentId,
- segmentId,
- isOpen,
- onClose,
- }: VersionHistoryModalProps) {
- const [loading, setLoading] = useState(true)
- const [versions, setVersions] = useState<SegmentVersion[]>([])
- const [total, setTotal] = useState(0)
- const [selectedVersion, setSelectedVersion] = useState<SegmentVersion | null>(null)
- useEffect(() => {
- if (isOpen)
- loadVersions()
- }, [isOpen])
- const loadVersions = async () => {
- setLoading(true)
- try {
- const result = await fetchSegmentVersions(datasetId, documentId, segmentId, {
- page: 1,
- per_page: 20,
- include_content: false,
- })
- setVersions(result.versions)
- setTotal(result.total)
- }
- catch (error) {
- console.error('Failed to load versions:', error)
- }
- finally {
- setLoading(false)
- }
- }
- const loadVersionDetail = async (version: SegmentVersion) => {
- try {
- const detail = await fetchSegmentVersions(datasetId, documentId, segmentId, {
- page: 1,
- per_page: 1,
- include_content: true,
- })
- const fullVersion = detail.versions.find(v => v.version_number === version.version_number)
- if (fullVersion)
- setSelectedVersion(fullVersion)
- }
- catch (error) {
- console.error('Failed to load version detail:', error)
- }
- }
- const getChangeTypeColor = (changeType: string) => {
- switch (changeType) {
- case 'create':
- return 'bg-green-100 text-green-700 border-green-200'
- case 'update':
- return 'bg-blue-100 text-blue-700 border-blue-200'
- case 'delete':
- return 'bg-red-100 text-red-700 border-red-200'
- default:
- return 'bg-gray-100 text-gray-700 border-gray-200'
- }
- }
- const formatDateTime = (dateStr: string) => {
- const date = new Date(dateStr)
- return date.toLocaleString(undefined, {
- month: 'short',
- day: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- })
- }
- return (
- <>
- <Modal
- isShow={isOpen}
- onClose={onClose}
- title="版本历史"
- className="!max-w-3xl"
- >
- <div className="h-[500px] flex flex-col">
- {total > 0 && (
- <div className="px-6 py-3 bg-gray-50 border-b text-sm text-gray-600">
- 共 {total} 个版本
- </div>
- )}
- <div className="flex-1 overflow-y-auto px-6 py-4">
- {loading
- ? (
- <div className="flex items-center justify-center h-40">
- <Loading />
- </div>
- )
- : versions.length === 0
- ? (
- <div className="flex flex-col items-center justify-center h-40 text-gray-500">
- <RiHistoryLine className="w-12 h-12 mb-2 text-gray-400" />
- <p>暂无版本历史</p>
- </div>
- )
- : (
- <div className="space-y-3">
- {versions.map((version, index) => {
- const isLatest = index === 0
- return (
- <div
- key={version.version_number}
- className="bg-white border rounded-lg p-4 hover:shadow-md transition-shadow"
- >
- <div className="flex items-start justify-between gap-4">
- <div className="flex-1 min-w-0">
- <div className="flex items-center gap-2 mb-2">
- <span className="font-semibold text-gray-900">
- 版本 {version.version_number}
- </span>
- <span className={cn(
- 'px-2 py-0.5 text-xs font-medium rounded-full border',
- getChangeTypeColor(version.change_type),
- )}>
- {version.change_type === 'create' && '创建'}
- {version.change_type === 'update' && '更新'}
- {version.change_type === 'delete' && '删除'}
- {version.change_type === 'rollback' && '回滚'}
- </span>
- {isLatest && (
- <span className="px-2 py-0.5 text-xs font-medium rounded-full bg-primary-50 text-primary-700 border border-primary-200">
- 当前版本
- </span>
- )}
- </div>
- <p className="text-sm text-gray-700 mb-2">
- {version.change_summary}
- </p>
- {version.changed_fields && version.changed_fields.length > 0 && (
- <div className="flex items-center gap-2 mb-2">
- <span className="text-xs text-gray-500">
- 修改字段:
- </span>
- <div className="flex flex-wrap gap-1">
- {version.changed_fields.map(field => (
- <span
- key={field}
- className="px-1.5 py-0.5 text-xs bg-gray-100 text-gray-600 rounded"
- >
- {field}
- </span>
- ))}
- </div>
- </div>
- )}
- <div className="flex items-center gap-4 text-xs text-gray-500">
- <span>
- 创建者: {version.created_by_name}
- </span>
- <span>•</span>
- <span>{formatDateTime(version.created_at)}</span>
- </div>
- </div>
- <div className="flex-shrink-0">
- <Button
- variant="ghost"
- size="small"
- onClick={() => loadVersionDetail(version)}
- >
- 查看
- </Button>
- </div>
- </div>
- </div>
- )
- })}
- </div>
- )}
- </div>
- </div>
- </Modal>
- {selectedVersion && (
- <Modal
- isShow={!!selectedVersion}
- onClose={() => setSelectedVersion(null)}
- title={`版本 ${selectedVersion.version_number}`}
- className="!max-w-2xl"
- >
- <div className="max-h-[60vh] overflow-y-auto space-y-4">
- <div>
- <label className="block text-sm font-medium text-gray-700 mb-2">
- 内容
- </label>
- <div className="p-4 bg-gray-50 rounded-lg border border-gray-200 max-h-60 overflow-y-auto">
- <pre className="whitespace-pre-wrap text-sm text-gray-900 font-sans">
- {selectedVersion.content}
- </pre>
- </div>
- </div>
- {selectedVersion.answer && (
- <div>
- <label className="block text-sm font-medium text-gray-700 mb-2">
- 答案
- </label>
- <div className="p-4 bg-blue-50 rounded-lg border border-blue-200 max-h-40 overflow-y-auto">
- <pre className="whitespace-pre-wrap text-sm text-gray-900 font-sans">
- {selectedVersion.answer}
- </pre>
- </div>
- </div>
- )}
- </div>
- </Modal>
- )}
- </>
- )
- }
|