feat: implement new layout components and routing structure
- Added HeaderLayout component for the application header. - Introduced KeepAliveViewport for managing tab states and rendering. - Created TabNav for tab navigation with animated indicator. - Removed old Tabs component in favor of new layout structure. - Updated routing with AppRouter and defined appTabs for navigation. - Enhanced theme context to manage dark mode styles. - Added new UI components: Badge, Button, Separator, and Tabs. - Refactored pages to utilize new layout components and improve responsiveness. - Updated global styles for better theming and layout consistency. - Introduced TypeScript path aliases for cleaner imports.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useTheme } from '../../contexts';
|
||||
import { Content } from '../../components/layout/Content';
|
||||
import { TPattern } from '../../components/common/TPattern';
|
||||
import { getDocumentList, getDocumentStatus, searchRegulations, uploadDocument, deleteDocument, retryDocument, type RegulationSearchItem } from '../../api/docs';
|
||||
import type { Doc } from '../../types';
|
||||
@@ -40,6 +39,7 @@ export const DocsPage: React.FC = () => {
|
||||
const [searchResults, setSearchResults] = useState<RegulationSearchItem[]>([]);
|
||||
const [searchLoading, setSearchLoading] = useState(false);
|
||||
const [searchError, setSearchError] = useState('');
|
||||
const [batchQueueLength, setBatchQueueLength] = useState(0);
|
||||
|
||||
// Upload metadata
|
||||
const [regulationType, setRegulationType] = useState('');
|
||||
@@ -48,12 +48,17 @@ export const DocsPage: React.FC = () => {
|
||||
// Batch queue: files waiting to be uploaded after the current one finishes
|
||||
const batchQueueRef = useRef<File[]>([]);
|
||||
|
||||
const setBatchQueue = (files: File[]) => {
|
||||
batchQueueRef.current = files;
|
||||
setBatchQueueLength(files.length);
|
||||
};
|
||||
|
||||
async function loadDocuments() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await getDocumentList();
|
||||
const apiDocs: Doc[] = response.docs.map((doc) => ({
|
||||
id: parseInt(String(doc.id).replace('doc-', ''), 10) || Math.floor(Math.random() * 10000),
|
||||
const apiDocs: Doc[] = response.docs.map((doc, index) => ({
|
||||
id: Number.parseInt(String(doc.id).replace('doc-', ''), 10) || -(index + 1),
|
||||
name: doc.name,
|
||||
chunks: doc.chunks,
|
||||
size: doc.updated_at ? new Date(doc.updated_at).toLocaleString() : 'Indexed document',
|
||||
@@ -209,6 +214,7 @@ export const DocsPage: React.FC = () => {
|
||||
|
||||
// Process next file in batch queue
|
||||
const next = batchQueueRef.current.shift();
|
||||
setBatchQueueLength(batchQueueRef.current.length);
|
||||
if (next) {
|
||||
const nextRunId = pipelineRunIdRef.current + 1;
|
||||
pipelineRunIdRef.current = nextRunId;
|
||||
@@ -222,7 +228,7 @@ export const DocsPage: React.FC = () => {
|
||||
if (files.length === 0 || uploading) return;
|
||||
|
||||
const [first, ...rest] = files;
|
||||
batchQueueRef.current = rest;
|
||||
setBatchQueue(rest);
|
||||
|
||||
const runId = pipelineRunIdRef.current + 1;
|
||||
pipelineRunIdRef.current = runId;
|
||||
@@ -262,7 +268,7 @@ export const DocsPage: React.FC = () => {
|
||||
if (files.length === 0 || uploading) return;
|
||||
|
||||
const [first, ...rest] = files;
|
||||
batchQueueRef.current = rest;
|
||||
setBatchQueue(rest);
|
||||
const runId = pipelineRunIdRef.current + 1;
|
||||
pipelineRunIdRef.current = runId;
|
||||
void uploadSingleFile(first, runId);
|
||||
@@ -282,8 +288,7 @@ export const DocsPage: React.FC = () => {
|
||||
|
||||
const getPipelineHint = () => {
|
||||
if (pipelineStatus === 'running') {
|
||||
const queueLen = batchQueueRef.current.length;
|
||||
const suffix = queueLen > 0 ? ` (+${queueLen} 待上传)` : '';
|
||||
const suffix = batchQueueLength > 0 ? ` (+${batchQueueLength} 待上传)` : '';
|
||||
return `${activeStep >= 0 ? PIPELINE_STEPS[activeStep].name : 'LOAD'} · ${uploadFileName}${suffix}`;
|
||||
}
|
||||
if (pipelineStatus === 'completed') return 'PIPELINE COMPLETE';
|
||||
@@ -291,6 +296,11 @@ export const DocsPage: React.FC = () => {
|
||||
return 'WAITING FOR UPLOAD';
|
||||
};
|
||||
|
||||
const getDocKey = (doc: Doc) => {
|
||||
// Prefer the backend document identifier because the numeric display id is not guaranteed unique.
|
||||
return doc.docId ?? `local-${doc.id}-${doc.name}`;
|
||||
};
|
||||
|
||||
const inputStyle: React.CSSProperties = {
|
||||
padding: '8px 12px',
|
||||
fontSize: 13,
|
||||
@@ -302,7 +312,7 @@ export const DocsPage: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<div className="relative w-full">
|
||||
<TPattern />
|
||||
|
||||
<section style={{ marginBottom: 56 }}>
|
||||
@@ -432,7 +442,7 @@ export const DocsPage: React.FC = () => {
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
{docs.map((doc) => (
|
||||
<div
|
||||
key={doc.id}
|
||||
key={getDocKey(doc)}
|
||||
style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: 20, background: theme.bgCard, borderRadius: 12, border: `1px solid ${doc.status === 'parsing' ? theme.accent : theme.border}`, transition: 'all 0.2s ease', boxShadow: !isDark ? '0 2px 8px rgba(226,0,116,0.04)' : 'none' }}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 16 }}>
|
||||
@@ -543,6 +553,6 @@ export const DocsPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Content>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user