feat(deploy): Complete PostgreSQL migration and Docker image build

Summary:
- PostgreSQL database migration to RDS completed (90MB SQL, 11 schemas)
- Frontend Nginx Docker image built and pushed to ACR (v1.0, ~50MB)
- Python microservice Docker image built and pushed to ACR (v1.0, 1.12GB)
- Created 3 deployment documentation files

Docker Configuration Files:
- frontend-v2/Dockerfile: Multi-stage build with nginx:alpine
- frontend-v2/.dockerignore: Optimize build context
- frontend-v2/nginx.conf: SPA routing and API proxy
- frontend-v2/docker-entrypoint.sh: Dynamic env injection
- extraction_service/Dockerfile: Multi-stage build with Aliyun Debian mirror
- extraction_service/.dockerignore: Optimize build context
- extraction_service/requirements-prod.txt: Production dependencies (removed Nougat)

Deployment Documentation:
- docs/05-部署文档/00-部署进度总览.md: One-stop deployment status overview
- docs/05-部署文档/07-前端Nginx-SAE部署操作手册.md: Frontend deployment guide
- docs/05-部署文档/08-PostgreSQL数据库部署操作手册.md: Database deployment guide
- docs/00-系统总体设计/00-系统当前状态与开发指南.md: Updated with deployment status

Database Migration:
- RDS instance: pgm-2zex1m2y3r23hdn5 (2C4G, PostgreSQL 15.0)
- Database: ai_clinical_research
- Schemas: 11 business schemas migrated successfully
- Data: 3 users, 2 projects, 1204 literatures verified
- Backup: rds_init_20251224_154529.sql (90MB)

Docker Images:
- Frontend: crpi-cd5ij4pjt65mweeo.cn-beijing.personal.cr.aliyuncs.com/ai-clinical/ai-clinical_frontend-nginx:v1.0
- Python: crpi-cd5ij4pjt65mweeo.cn-beijing.personal.cr.aliyuncs.com/ai-clinical/python-extraction:v1.0

Key Achievements:
- Resolved Docker Hub network issues (using generic tags)
- Fixed 30 TypeScript compilation errors
- Removed Nougat OCR to reduce image size by 1.5GB
- Used Aliyun Debian mirror to resolve apt-get network issues
- Implemented multi-stage builds for optimization

Next Steps:
- Deploy Python microservice to SAE
- Build Node.js backend Docker image
- Deploy Node.js backend to SAE
- Deploy frontend Nginx to SAE
- End-to-end verification testing

Status: Docker images ready, SAE deployment pending
This commit is contained in:
2025-12-24 18:21:55 +08:00
parent 5fa7b0bbe1
commit b64896a307
134 changed files with 4185 additions and 53 deletions

68
frontend-v2/.dockerignore Normal file
View File

@@ -0,0 +1,68 @@
# Node.js
node_modules
npm-debug.log
yarn-error.log
.npm
.yarn
# 开发文件
.env
.env.*
*.local
# 构建产物Dockerfile 中会重新生成)
dist
# 测试文件
test
tests
*.test.ts
*.test.tsx
*.spec.ts
*.spec.tsx
coverage
.nyc_output
# 文档和临时文件
docs
*.md
!README.md
.vscode
.idea
.DS_Store
Thumbs.db
# Git
.git
.gitignore
.gitattributes
# CI/CD
.github
.gitlab-ci.yml
.travis.yml
# 日志
*.log
logs
# 临时文件
temp
tmp
*.swp
*.swo
*~
# 编辑器配置
.editorconfig
.prettierrc
.eslintrc*
# TypeScript 配置(保留 tsconfig.json其他忽略
tsconfig.tsbuildinfo
# Vite
.vite
vite.config.*.timestamp-*

64
frontend-v2/Dockerfile Normal file
View File

@@ -0,0 +1,64 @@
# ==================== 阶段 1: 构建阶段 ====================
# ⚠️ 使用 Node Alpine 最新版(包含 Node 22+
FROM node:alpine AS builder
# 设置工作目录
WORKDIR /app
# 1. 复制依赖文件
COPY package*.json ./
# 2. 安装依赖
# 使用国内镜像加速(可选,如果网络慢可以取消注释)
# RUN npm config set registry https://registry.npmmirror.com
RUN npm ci --only=production=false
# 3. 复制源代码
COPY . .
# 4. 构建生产版本
# ⚠️ 注意:如果需要在构建时注入环境变量,在这里设置 ARG
# ARG VITE_API_BASE_URL
# ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
RUN npm run build
# 验证构建产物
RUN ls -la /app/dist/
# ==================== 阶段 2: 运行阶段 ====================
FROM nginx:alpine
# 安装必要工具(包括时区数据)
RUN apk add --no-cache \
bash \
gettext \
curl \
tzdata
# 设置容器时区为上海(否则日志时间会比北京时间慢 8 小时)
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
# 创建 Nginx 配置目录
RUN mkdir -p /etc/nginx/templates
# 1. 复制 Nginx 配置模板(支持环境变量替换)
COPY nginx.conf /etc/nginx/templates/nginx.conf.template
# 2. 复制构建产物到 Nginx 默认目录
COPY --from=builder /app/dist /usr/share/nginx/html
# 3. 复制启动脚本
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
# 健康检查
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -f http://localhost/health || exit 1
# 暴露端口
EXPOSE 80
# 启动命令
ENTRYPOINT ["/docker-entrypoint.sh"]

View File

@@ -0,0 +1,35 @@
#!/bin/bash
set -e
# ⚠️ 关键:不给默认值,强制在 SAE 控制台配置
# 如果未配置,报错退出(避免使用错误的后端地址)
if [ -z "$BACKEND_SERVICE_HOST" ]; then
echo "❌ ERROR: BACKEND_SERVICE_HOST environment variable is required!"
echo "Please configure it in SAE console with backend internal IP (e.g., 172.17.x.x)"
exit 1
fi
if [ -z "$BACKEND_SERVICE_PORT" ]; then
echo "⚠️ WARNING: BACKEND_SERVICE_PORT not set, using default: 3001"
export BACKEND_SERVICE_PORT=3001
fi
echo "============================================"
echo "Starting Frontend Nginx Service"
echo "Backend Service: ${BACKEND_SERVICE_HOST}:${BACKEND_SERVICE_PORT}"
echo "Container Timezone: $(cat /etc/timezone)"
echo "Current Time: $(date)"
echo "============================================"
# 使用 envsubst 替换 Nginx 配置中的环境变量
envsubst '${BACKEND_SERVICE_HOST} ${BACKEND_SERVICE_PORT}' \
< /etc/nginx/templates/nginx.conf.template \
> /etc/nginx/nginx.conf
# 验证 Nginx 配置
nginx -t
# 启动 Nginx
exec nginx -g 'daemon off;'

191
frontend-v2/nginx.conf Normal file
View File

@@ -0,0 +1,191 @@
# Nginx 配置文件 - AI临床研究平台前端服务
# 用途:托管 React SPA + 反向代理后端 API
user nginx;
worker_processes auto; # 自动根据 CPU 核心数调整
# ⚠️ 日志输出到 stderrSAE 会自动收集)
error_log /dev/stderr warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024; # 每个 worker 进程的最大连接数
use epoll; # Linux 下使用 epoll高性能
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# ⚠️ 日志输出到 stdoutSAE 会自动收集,避免磁盘写满)
access_log /dev/stdout main;
# 性能优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip 压缩(减少传输大小)
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml font/truetype font/opentype
application/vnd.ms-fontobject image/svg+xml;
gzip_disable "msie6";
# 上游后端服务Backend Service
upstream backend {
# ⚠️ 重要:这里的地址在部署时需要替换为真实的后端内网地址
# SAE 部署时,通过环境变量注入,详见 Dockerfile
server ${BACKEND_SERVICE_HOST}:${BACKEND_SERVICE_PORT} fail_timeout=30s max_fails=3;
# 如果有多个后端实例(负载均衡)
# server 172.17.x.x:3001 weight=1;
# server 172.17.x.y:3001 weight=1;
keepalive 32; # 保持连接池
}
server {
listen 80;
server_name _; # 接受所有域名
# 根目录React 构建产物)
root /usr/share/nginx/html;
index index.html;
# 字符集
charset utf-8;
# ==================== 静态资源处理 ====================
# 主页面index.html- 不缓存
location = / {
try_files /index.html =404;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
# JS/CSS 文件 - 强缓存(文件名带 hash
location ~* \.(js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# 图片/字体文件 - 强缓存
location ~* \.(png|jpg|jpeg|gif|ico|svg|webp|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# ==================== API 反向代理 ====================
# 后端 API 代理(关键配置)
location /api/ {
# 代理到后端服务
proxy_pass http://backend;
# 保留原始请求信息
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时配置AI 对话、文件处理可能耗时较长)
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
# 缓冲配置
proxy_buffering off; # 关闭缓冲(实时流式响应)
proxy_request_buffering off; # 支持大文件上传
# WebSocket 支持(如果后续需要)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 错误处理
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
proxy_next_upstream_tries 2;
}
# ==================== SPA 路由支持 ====================
# React Router 路由回退
# 所有非文件请求都返回 index.htmlSPA 的核心)
location / {
try_files $uri $uri/ /index.html;
# 禁用缓存(用户刷新时总是获取最新页面)
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# ==================== 安全加固 ====================
# 隐藏 Nginx 版本号
server_tokens off;
# 禁止访问隐藏文件
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# 禁止访问特定文件
location ~* \.(bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)$ {
deny all;
}
# ==================== 健康检查 ====================
# SAE 健康检查端点
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# Nginx 状态页(用于监控)
location /nginx_status {
stub_status on;
access_log off;
# 仅允许内网访问
allow 10.0.0.0/8;
allow 172.17.0.0/16;
allow 192.168.0.0/16;
deny all;
}
# ==================== 错误页面 ====================
error_page 404 /index.html; # SPA 路由回退
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}

View File

@@ -35,7 +35,7 @@ const ModuleErrorFallback = ({
const navigate = useNavigate()
// 是否显示详细错误信息(开发环境显示,生产环境隐藏)
const isDevelopment = import.meta.env?.DEV ?? false
const isDevelopment = import.meta.env.DEV
/**
* 处理重试

View File

@@ -10,7 +10,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
<ConfigProvider
locale={zhCN}
theme={{
cssVar: true, // ⭐ 启用 CSS 变量Ant Design 6.0 新特性)
cssVar: { prefix: 'ant' }, // ⭐ 启用 CSS 变量Ant Design 6.0 新特性)
token: {
colorPrimary: '#10b981', // emerald-500工具C主题色
borderRadius: 8,

View File

@@ -12,7 +12,7 @@ import {
import type { ConclusionType } from '../types';
interface ConclusionTagProps {
conclusion: ConclusionType;
conclusion: ConclusionType | 'pending';
showIcon?: boolean;
size?: 'small' | 'middle' | 'large';
}
@@ -42,6 +42,12 @@ const ConclusionTag: React.FC<ConclusionTagProps> = ({
icon: <QuestionCircleOutlined />,
text: '不确定',
};
case 'pending':
return {
color: 'processing',
icon: <QuestionCircleOutlined />,
text: '待处理',
};
default:
return {
color: 'default',

View File

@@ -531,6 +531,8 @@ export default FulltextDetailDrawer;

View File

@@ -8,7 +8,6 @@
* 4. 人工复核
*/
import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { message } from 'antd';
import { aslApi } from '../api';
@@ -131,5 +130,6 @@ export function useFulltextResults({

View File

@@ -35,10 +35,10 @@ export function useFulltextTask({
enabled: enabled && !!taskId,
refetchInterval: refetchInterval !== undefined
? refetchInterval
: ((data) => {
: ((data: any) => {
// 默认行为任务进行中时每2秒轮询否则停止
if (!data?.data) return false;
const status = (data.data as any).status;
const status = data.data.status;
return status === 'processing' || status === 'pending' ? 2000 : false;
}),
retry: 1,
@@ -94,5 +94,6 @@ export function useFulltextTask({

View File

@@ -33,12 +33,11 @@ export function useScreeningResults({
queryFn: () => aslApi.getScreeningResultsList(projectId, { page, pageSize, filter }),
enabled: enabled && !!projectId,
staleTime: 1000 * 30, // 30秒内认为数据是新鲜的
keepPreviousData: true, // 切换页面时保留上一页数据,避免闪烁
});
const results = data?.data?.items || [];
const total = data?.data?.total || 0;
const totalPages = data?.data?.totalPages || 0;
const results = (data as any)?.data?.items || [];
const total = (data as any)?.data?.total || 0;
const totalPages = (data as any)?.data?.totalPages || 0;
// 人工复核Mutation
const reviewMutation = useMutation({

View File

@@ -9,7 +9,7 @@
*/
import { useState } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import {
Card,
Statistic,
@@ -59,8 +59,6 @@ interface FulltextResultItem {
const FulltextResults = () => {
const { taskId } = useParams<{ taskId: string }>();
const [searchParams] = useSearchParams();
const projectId = searchParams.get('projectId') || '';
const [activeTab, setActiveTab] = useState<'all' | 'included' | 'excluded' | 'pending'>('all');
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
@@ -108,7 +106,7 @@ const FulltextResults = () => {
const results = resultsData?.items || [];
// 导出Excel
const handleExport = async (filter: 'all' | 'included' | 'excluded' | 'pending') => {
const handleExport = async (_filter: 'all' | 'included' | 'excluded' | 'pending') => {
try {
message.loading({ content: '正在生成Excel...', key: 'export' });
@@ -432,7 +430,7 @@ const FulltextResults = () => {
expandable={{
expandedRowRender,
expandedRowKeys,
onExpand: (expanded, record) => toggleRowExpanded(record.resultId),
onExpand: (_expanded, record) => toggleRowExpanded(record.resultId),
expandIcon: () => null,
}}
pagination={{
@@ -485,5 +483,6 @@ export default FulltextResults;

View File

@@ -124,6 +124,8 @@ export const useAssets = (activeTab: AssetTabType) => {

View File

@@ -114,6 +114,8 @@ export const useRecentTasks = () => {

View File

@@ -22,7 +22,7 @@ interface VerifyRow {
status: 'clean' | 'conflict' | 'pending' | 'failed'; // 行状态
}
const Step4Verify: React.FC<Step4VerifyProps> = ({ state, updateState, onComplete }) => {
const Step4Verify: React.FC<Step4VerifyProps> = ({ state, onComplete }) => {
const [rows, setRows] = useState<VerifyRow[]>([]);
const [selectedRowId, setSelectedRowId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);

View File

@@ -9,7 +9,7 @@
import React, { useState } from 'react';
import { Modal, Select, Input, Button, Radio, Space, Tag, App, Alert } from 'antd';
import { Plus, X, Info } from 'lucide-react';
import { Info } from 'lucide-react';
interface BinningDialogProps {
visible: boolean;
@@ -353,3 +353,4 @@ export default BinningDialog;

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Modal, Input, Button, Select, Space, Alert, App, Card, Tag } from 'antd';
import { Modal, Input, Button, Select, Alert, App, Card, Tag } from 'antd';
import { PlusCircle, Trash2, AlertCircle } from 'lucide-react';
interface Condition {
@@ -28,7 +28,6 @@ const ConditionalDialog: React.FC<Props> = ({
onClose,
onApply,
columns,
data,
sessionId,
}) => {
const { message } = App.useApp();

View File

@@ -315,4 +315,6 @@ export default DropnaDialog;

View File

@@ -401,3 +401,5 @@ export default MetricTimePanel;

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Modal, Select, Button, Alert, Checkbox, Radio, App, Tag } from 'antd';
import { Modal, Select, Button, Alert, Checkbox, Radio, App } from 'antd';
import { ArrowLeftRight, Info } from 'lucide-react';
interface Props {

View File

@@ -287,3 +287,5 @@ export default PivotPanel;

View File

@@ -27,14 +27,13 @@ interface MappingRow {
const RecodeDialog: React.FC<RecodeDialogProps> = ({
visible,
columns,
data,
sessionId,
onClose,
onApply,
}) => {
const { message } = App.useApp();
const [selectedColumn, setSelectedColumn] = useState<string>('');
const [uniqueValues, setUniqueValues] = useState<any[]>([]);
const [_uniqueValues, setUniqueValues] = useState<any[]>([]);
const [mappingTable, setMappingTable] = useState<MappingRow[]>([]);
const [createNewColumn, setCreateNewColumn] = useState(true);
const [newColumnName, setNewColumnName] = useState('');

View File

@@ -6,9 +6,7 @@
import {
Calculator,
CalendarClock,
ArrowLeftRight,
FileSearch,
Wand2,
Filter,
Search,
@@ -71,9 +69,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
onConditionalClick,
onDropnaClick,
onComputeClick,
onDedupClick,
onPivotClick,
onMiceClick,
sessionId,
}) => {
return (

View File

@@ -9,7 +9,7 @@
import React, { useState } from 'react';
import { Modal, Tabs } from 'antd';
import { ArrowLeftRight, Sparkles, BarChart3 } from 'lucide-react';
import { ArrowLeftRight, BarChart3 } from 'lucide-react';
import PivotPanel from './PivotPanel';
import UnpivotPanel from './UnpivotPanel';
import { MultiMetricPanel } from './MultiMetricPanel';

View File

@@ -4,7 +4,7 @@
*/
import React, { useState } from 'react';
import { Select, Button, Alert, Checkbox, App, Input, Collapse, Radio } from 'antd';
import { Button, Alert, Checkbox, App, Input, Collapse, Radio } from 'antd';
import { ArrowLeftRight, Info } from 'lucide-react';
interface Props {
@@ -392,3 +392,4 @@ export default UnpivotPanel;

View File

@@ -87,3 +87,5 @@ export function useSessionStatus({
};
}

View File

@@ -460,7 +460,6 @@ const ToolC = () => {
<MissingValueDialog
visible={state.dropnaDialogVisible}
columns={state.columns}
data={state.data}
sessionId={state.sessionId}
onClose={() => updateState({ dropnaDialogVisible: false })}
onApply={handleQuickActionDataUpdate}

View File

@@ -76,6 +76,8 @@ export interface DataStats {

View File

@@ -72,6 +72,8 @@ export type AssetTabType = 'all' | 'processed' | 'raw';

View File

@@ -5,14 +5,18 @@ import { RocketOutlined } from '@ant-design/icons'
interface PlaceholderProps {
title?: string
description?: string
message?: string // 别名等同于description
moduleName?: string
}
const Placeholder = ({
title = '功能开发中',
description = '该模块正在规划和开发中,敬请期待',
message, // message是description的别名
moduleName
}: PlaceholderProps) => {
// 优先使用message如果没有则使用description
const displayDescription = message || description;
const navigate = useNavigate()
return (
@@ -22,7 +26,7 @@ const Placeholder = ({
title={<span className="text-2xl">{title}</span>}
subTitle={
<div className="space-y-2">
<p className="text-gray-600">{description}</p>
<p className="text-gray-600">{displayDescription}</p>
{moduleName && (
<p className="text-sm text-gray-400">
{moduleName}

View File

@@ -27,6 +27,8 @@ export { default as Placeholder } from './Placeholder';

14
frontend-v2/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly DEV: boolean
readonly PROD: boolean
readonly SSR: boolean
// 更多环境变量...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

View File

@@ -0,0 +1 @@
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/framework/layout/mainlayout.tsx","./src/framework/layout/topnavigation.tsx","./src/framework/modules/errorboundary.tsx","./src/framework/modules/moduleerrorfallback.tsx","./src/framework/modules/moduleregistry.ts","./src/framework/modules/types.ts","./src/framework/permission/permissioncontext.tsx","./src/framework/permission/index.ts","./src/framework/permission/types.ts","./src/framework/permission/usepermission.ts","./src/framework/router/permissiondenied.tsx","./src/framework/router/routeguard.tsx","./src/framework/router/index.ts","./src/modules/aia/index.tsx","./src/modules/asl/index.tsx","./src/modules/asl/api/index.ts","./src/modules/asl/components/asllayout.tsx","./src/modules/asl/components/conclusiontag.tsx","./src/modules/asl/components/detailreviewdrawer.tsx","./src/modules/asl/components/fulltextdetaildrawer.tsx","./src/modules/asl/components/judgmentbadge.tsx","./src/modules/asl/hooks/usefulltextresults.ts","./src/modules/asl/hooks/usefulltexttask.ts","./src/modules/asl/hooks/usescreeningresults.ts","./src/modules/asl/hooks/usescreeningtask.ts","./src/modules/asl/pages/fulltextprogress.tsx","./src/modules/asl/pages/fulltextresults.tsx","./src/modules/asl/pages/fulltextsettings.tsx","./src/modules/asl/pages/fulltextworkbench.tsx","./src/modules/asl/pages/screeningresults.tsx","./src/modules/asl/pages/screeningworkbench.tsx","./src/modules/asl/pages/titlescreeningsettings.tsx","./src/modules/asl/types/index.ts","./src/modules/asl/utils/excelexport.ts","./src/modules/asl/utils/excelutils.ts","./src/modules/asl/utils/tabletransform.ts","./src/modules/dc/index.tsx","./src/modules/dc/api/toolb.ts","./src/modules/dc/api/toolc.ts","./src/modules/dc/components/assetlibrary.tsx","./src/modules/dc/components/tasklist.tsx","./src/modules/dc/components/toolcard.tsx","./src/modules/dc/hooks/useassets.ts","./src/modules/dc/hooks/userecenttasks.ts","./src/modules/dc/pages/portal.tsx","./src/modules/dc/pages/tool-b/step1upload.tsx","./src/modules/dc/pages/tool-b/step2schema.tsx","./src/modules/dc/pages/tool-b/step3processing.tsx","./src/modules/dc/pages/tool-b/step4verify.tsx","./src/modules/dc/pages/tool-b/step5result.tsx","./src/modules/dc/pages/tool-b/index.tsx","./src/modules/dc/pages/tool-b/components/stepindicator.tsx","./src/modules/dc/pages/tool-c/index.tsx","./src/modules/dc/pages/tool-c/components/binningdialog.tsx","./src/modules/dc/pages/tool-c/components/binningdialog_improved.tsx","./src/modules/dc/pages/tool-c/components/computedialog.tsx","./src/modules/dc/pages/tool-c/components/conditionaldialog.tsx","./src/modules/dc/pages/tool-c/components/datagrid.tsx","./src/modules/dc/pages/tool-c/components/dropnadialog.tsx","./src/modules/dc/pages/tool-c/components/filterdialog.tsx","./src/modules/dc/pages/tool-c/components/header.tsx","./src/modules/dc/pages/tool-c/components/metrictimepanel.tsx","./src/modules/dc/pages/tool-c/components/missingvaluedialog.tsx","./src/modules/dc/pages/tool-c/components/multimetricpanel.tsx","./src/modules/dc/pages/tool-c/components/pivotdialog.tsx","./src/modules/dc/pages/tool-c/components/pivotpanel.tsx","./src/modules/dc/pages/tool-c/components/recodedialog.tsx","./src/modules/dc/pages/tool-c/components/sidebar.tsx","./src/modules/dc/pages/tool-c/components/streamingsteps.tsx","./src/modules/dc/pages/tool-c/components/toolbar.tsx","./src/modules/dc/pages/tool-c/components/transformdialog.tsx","./src/modules/dc/pages/tool-c/components/unpivotpanel.tsx","./src/modules/dc/pages/tool-c/hooks/usesessionstatus.ts","./src/modules/dc/pages/tool-c/types/index.ts","./src/modules/dc/types/portal.ts","./src/modules/pkb/index.tsx","./src/modules/ssa/index.tsx","./src/modules/st/index.tsx","./src/pages/homepage.tsx","./src/shared/components/placeholder.tsx","./src/shared/components/index.ts","./src/shared/components/chat/chatcontainer.tsx","./src/shared/components/chat/codeblockrenderer.tsx","./src/shared/components/chat/messagerenderer.tsx","./src/shared/components/chat/index.ts","./src/shared/components/chat/types.ts"],"version":"5.9.3"}