fix: harden file download flow
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,425 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SVN 日志工作台</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/styles-redesign.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- 主题切换按钮 -->
|
||||
<button class="theme-toggle" id="theme-toggle" aria-label="切换主题">
|
||||
<svg class="icon-sun" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="5"/>
|
||||
<line x1="12" y1="1" x2="12" y2="3"/>
|
||||
<line x1="12" y1="21" x2="12" y2="23"/>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
|
||||
<line x1="1" y1="12" x2="3" y2="12"/>
|
||||
<line x1="21" y1="12" x2="23" y2="12"/>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
||||
</svg>
|
||||
<svg class="icon-moon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="app-container">
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar" role="navigation" aria-label="主导航">
|
||||
<div class="sidebar-header">
|
||||
<svg class="logo-icon" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
|
||||
</svg>
|
||||
<h1 class="logo-text">SVN 工作台</h1>
|
||||
</div>
|
||||
|
||||
<nav class="nav-menu">
|
||||
<button class="nav-item active" data-view="dashboard">
|
||||
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="7" height="7"/>
|
||||
<rect x="14" y="3" width="7" height="7"/>
|
||||
<rect x="14" y="14" width="7" height="7"/>
|
||||
<rect x="3" y="14" width="7" height="7"/>
|
||||
</svg>
|
||||
<span>工作台</span>
|
||||
</button>
|
||||
<button class="nav-item" data-view="svn">
|
||||
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
|
||||
<line x1="12" y1="22.08" x2="12" y2="12"/>
|
||||
</svg>
|
||||
<span>SVN 日志</span>
|
||||
</button>
|
||||
<button class="nav-item" data-view="ai">
|
||||
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
||||
<path d="M2 17l10 5 10-5"/>
|
||||
<path d="M2 12l10 5 10-5"/>
|
||||
</svg>
|
||||
<span>AI 分析</span>
|
||||
</button>
|
||||
<button class="nav-item" data-view="history">
|
||||
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<polyline points="12 6 12 12 16 14"/>
|
||||
</svg>
|
||||
<span>任务历史</span>
|
||||
</button>
|
||||
<button class="nav-item" data-view="settings">
|
||||
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M12 1v6m0 6v6m5.2-13.2l-4.2 4.2m-2 2l-4.2 4.2M23 12h-6m-6 0H5m13.2 5.2l-4.2-4.2m-2-2l-4.2-4.2"/>
|
||||
</svg>
|
||||
<span>系统设置</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div class="status-indicator">
|
||||
<span class="status-dot"></span>
|
||||
<span class="status-text">系统正常</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main class="main-content" id="main-content">
|
||||
<!-- 页面头部 -->
|
||||
<header class="page-header">
|
||||
<div class="header-content">
|
||||
<h2 class="page-title" id="page-title">工作台</h2>
|
||||
<p class="page-description" id="page-description">查看系统状态与最近产物</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 工作台视图 -->
|
||||
<section class="view active" id="view-dashboard" aria-live="polite">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-grid">
|
||||
<article class="stat-card">
|
||||
<div class="stat-icon stat-icon-primary">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<p class="stat-label">任务总数</p>
|
||||
<p class="stat-value" id="stat-total">0</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="stat-card">
|
||||
<div class="stat-icon stat-icon-warning">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<polyline points="12 6 12 12 16 14"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<p class="stat-label">执行中</p>
|
||||
<p class="stat-value" id="stat-running">0</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="stat-card">
|
||||
<div class="stat-icon stat-icon-danger">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<line x1="15" y1="9" x2="9" y2="15"/>
|
||||
<line x1="9" y1="9" x2="15" y2="15"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<p class="stat-label">失败任务</p>
|
||||
<p class="stat-value" id="stat-failed">0</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="stat-card">
|
||||
<div class="stat-icon stat-icon-success">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
||||
<polyline points="22 4 12 14.01 9 11.01"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<p class="stat-label">系统状态</p>
|
||||
<p class="stat-value stat-value-small" id="stat-health">-</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- 健康检查卡片 -->
|
||||
<article class="glass-card" id="health-card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">健康检查</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted" id="health-details">加载中...</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- 最近任务和文件 -->
|
||||
<div class="grid-2">
|
||||
<article class="glass-card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">最近任务</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul id="recent-tasks" class="item-list"></ul>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="glass-card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">最近文件</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul id="recent-files" class="item-list"></ul>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SVN 日志抓取视图 -->
|
||||
<section class="view" id="view-svn">
|
||||
<article class="glass-card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">SVN 抓取参数</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="svn-form" class="form-layout">
|
||||
<div class="form-group">
|
||||
<label for="svn-preset-select" class="form-label">预置项目</label>
|
||||
<select name="presetId" id="svn-preset-select" class="form-select" aria-label="预置 SVN 项目"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="project-name" class="form-label">项目名</label>
|
||||
<input type="text" name="projectName" id="project-name" class="form-input" placeholder="如:PRS-7050">
|
||||
</div>
|
||||
|
||||
<div class="form-group form-group-full">
|
||||
<label for="svn-url" class="form-label">SVN 地址 <span class="required">*</span></label>
|
||||
<input type="url" name="url" id="svn-url" class="form-input" placeholder="https://..." required aria-label="SVN 地址">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="svn-username" class="form-label">账号 <span class="required">*</span></label>
|
||||
<input type="text" name="username" id="svn-username" class="form-input" placeholder="请输入账号" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="svn-password" class="form-label">密码 <span class="required">*</span></label>
|
||||
<input type="password" name="password" id="svn-password" class="form-input" placeholder="请输入密码" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="start-revision" class="form-label">开始版本号</label>
|
||||
<input type="text" name="startRevision" id="start-revision" class="form-input" inputmode="numeric" placeholder="默认最新">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="end-revision" class="form-label">结束版本号</label>
|
||||
<input type="text" name="endRevision" id="end-revision" class="form-input" inputmode="numeric" placeholder="默认最新">
|
||||
</div>
|
||||
|
||||
<div class="form-group form-group-full">
|
||||
<label for="filter-user" class="form-label">过滤用户名</label>
|
||||
<input type="text" name="filterUser" id="filter-user" class="form-input" placeholder="包含匹配,留空不过滤">
|
||||
</div>
|
||||
|
||||
<div class="form-actions form-group-full">
|
||||
<button type="button" id="btn-test-connection" class="btn btn-secondary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
||||
<polyline points="22 4 12 14.01 9 11.01"/>
|
||||
</svg>
|
||||
测试连接
|
||||
</button>
|
||||
<button type="submit" id="btn-svn-run" class="btn btn-primary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
|
||||
</svg>
|
||||
开始抓取并导出
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<!-- AI 工作量分析视图 -->
|
||||
<section class="view" id="view-ai">
|
||||
<article class="glass-card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">AI 分析参数</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="ai-form" class="form-layout">
|
||||
<div class="form-group form-group-full">
|
||||
<label class="form-label">选择 Markdown 输入文件</label>
|
||||
<div class="file-picker" id="md-file-picker" role="group" aria-label="Markdown 文件选择"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="work-period" class="form-label">工作周期</label>
|
||||
<input type="text" name="period" id="work-period" class="form-input" placeholder="例如 2026年03月">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="output-filename" class="form-label">输出文件名</label>
|
||||
<input type="text" name="outputFileName" id="output-filename" class="form-input" placeholder="例如 202603工作量统计.xlsx">
|
||||
</div>
|
||||
|
||||
<div class="form-group form-group-full">
|
||||
<label for="temp-api-key" class="form-label">临时 API Key(可选)</label>
|
||||
<input type="password" name="apiKey" id="temp-api-key" class="form-input" placeholder="优先使用设置页或环境变量">
|
||||
</div>
|
||||
|
||||
<div class="form-actions form-group-full">
|
||||
<button type="submit" id="btn-ai-run" class="btn btn-primary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
||||
<path d="M2 17l10 5 10-5"/>
|
||||
<path d="M2 12l10 5 10-5"/>
|
||||
</svg>
|
||||
开始 AI 分析并导出 Excel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<!-- 任务历史视图 -->
|
||||
<section class="view" id="view-history">
|
||||
<article class="glass-card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">任务列表</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="filter-toolbar" id="history-toolbar">
|
||||
<select id="task-filter-status" class="form-select" aria-label="状态筛选">
|
||||
<option value="">全部状态</option>
|
||||
<option value="PENDING">PENDING</option>
|
||||
<option value="RUNNING">RUNNING</option>
|
||||
<option value="SUCCESS">SUCCESS</option>
|
||||
<option value="FAILED">FAILED</option>
|
||||
<option value="CANCELLED">CANCELLED</option>
|
||||
</select>
|
||||
<select id="task-filter-type" class="form-select" aria-label="类型筛选">
|
||||
<option value="">全部类型</option>
|
||||
<option value="SVN_FETCH">SVN_FETCH</option>
|
||||
<option value="AI_ANALYZE">AI_ANALYZE</option>
|
||||
</select>
|
||||
<input id="task-filter-keyword" class="form-input" placeholder="搜索任务ID/信息" aria-label="关键词搜索">
|
||||
<button id="btn-task-filter" type="button" class="btn btn-secondary">查询</button>
|
||||
</div>
|
||||
<div id="task-table" class="table-container"></div>
|
||||
<div class="pagination" id="task-pager"></div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="glass-card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">输出文件</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="file-table" class="table-container"></div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<!-- 系统设置视图 -->
|
||||
<section class="view" id="view-settings">
|
||||
<article class="glass-card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">系统设置</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="settings-form" class="form-layout">
|
||||
<div class="form-group form-group-full">
|
||||
<label for="settings-provider" class="form-label">AI 提供商</label>
|
||||
<select name="provider" id="settings-provider" class="form-select">
|
||||
<option value="deepseek">DeepSeek</option>
|
||||
<option value="openai-compatible">OpenAI兼容</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group form-group-full">
|
||||
<label for="api-key-input" class="form-label">DeepSeek API Key</label>
|
||||
<input type="password" name="apiKey" id="api-key-input" class="form-input" placeholder="设置后将保存在当前进程内存">
|
||||
</div>
|
||||
|
||||
<div class="form-group form-group-full" id="openai-settings-group" hidden>
|
||||
<label for="openai-base-url" class="form-label">OpenAI兼容 Base URL</label>
|
||||
<input type="text" name="openaiBaseUrl" id="openai-base-url" class="form-input" placeholder="例如 http://127.0.0.1:5001/v1">
|
||||
</div>
|
||||
|
||||
<div class="form-group form-group-full" id="openai-api-key-group" hidden>
|
||||
<label for="openai-api-key" class="form-label">OpenAI兼容 API Key</label>
|
||||
<input type="password" name="openaiApiKey" id="openai-api-key" class="form-input" placeholder="设置后将保存在当前进程内存">
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="openai-stage-one-group" hidden>
|
||||
<label for="openai-stage-one-model" class="form-label">第一阶段模型</label>
|
||||
<select name="openaiStageOneModel" id="openai-stage-one-model" class="form-select">
|
||||
<option value="deepseek-v4-flash">deepseek-v4-flash</option>
|
||||
<option value="deepseek-v4-pro">deepseek-v4-pro</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="openai-stage-two-group" hidden>
|
||||
<label for="openai-stage-two-model" class="form-label">第二阶段模型</label>
|
||||
<select name="openaiStageTwoModel" id="openai-stage-two-model" class="form-select">
|
||||
<option value="deepseek-v4-pro">deepseek-v4-pro</option>
|
||||
<option value="deepseek-v4-flash">deepseek-v4-flash</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group form-group-full">
|
||||
<label for="default-preset" class="form-label">默认 SVN 项目</label>
|
||||
<select name="defaultSvnPresetId" id="default-preset" class="form-select"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group form-group-full">
|
||||
<label for="output-dir" class="form-label">输出目录</label>
|
||||
<input type="text" name="outputDir" id="output-dir" class="form-input" placeholder="默认 outputs">
|
||||
</div>
|
||||
|
||||
<div class="form-actions form-group-full">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
|
||||
<polyline points="17 21 17 13 7 13 7 21"/>
|
||||
<polyline points="7 3 7 8 15 8"/>
|
||||
</svg>
|
||||
保存设置
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<p id="settings-state" class="text-muted"></p>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Toast 通知 -->
|
||||
<div class="toast-container" id="toast-container" aria-live="assertive" aria-atomic="true"></div>
|
||||
|
||||
<!-- 加载指示器 -->
|
||||
<div class="loading-overlay" id="loading-overlay">
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
|
||||
<script src="/app-redesign.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,746 +0,0 @@
|
||||
:root {
|
||||
--bg-0: #0b1020;
|
||||
--bg-1: #121a2f;
|
||||
--bg-2: #1a2744;
|
||||
|
||||
--surface-0: rgba(19, 30, 53, 0.62);
|
||||
--surface-1: rgba(24, 37, 64, 0.84);
|
||||
--surface-2: #1f3158;
|
||||
|
||||
--text-0: #e8efff;
|
||||
--text-1: #c4d2f0;
|
||||
--text-2: #91a3cc;
|
||||
|
||||
--accent-0: #6ba6ff;
|
||||
--accent-1: #8fc0ff;
|
||||
--accent-soft: rgba(107, 166, 255, 0.16);
|
||||
|
||||
--success: #49c28a;
|
||||
--warning: #f0b85d;
|
||||
--danger: #ff7f87;
|
||||
|
||||
--border-0: rgba(150, 180, 230, 0.24);
|
||||
--border-1: rgba(150, 180, 230, 0.4);
|
||||
|
||||
--shadow-0: 0 22px 54px rgba(4, 8, 20, 0.45);
|
||||
--shadow-1: 0 10px 30px rgba(8, 14, 32, 0.36);
|
||||
|
||||
--radius-sm: 10px;
|
||||
--radius-md: 14px;
|
||||
--radius-lg: 20px;
|
||||
|
||||
--space-1: 8px;
|
||||
--space-2: 12px;
|
||||
--space-3: 16px;
|
||||
--space-4: 20px;
|
||||
--space-5: 24px;
|
||||
|
||||
--z-bg: 0;
|
||||
--z-layout: 5;
|
||||
--z-toast: 50;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
color: var(--text-0);
|
||||
background:
|
||||
radial-gradient(1200px 800px at -15% 130%, #1d2749 0%, transparent 60%),
|
||||
radial-gradient(900px 700px at 110% -10%, #1f3b67 0%, transparent 62%),
|
||||
linear-gradient(160deg, var(--bg-0) 0%, var(--bg-1) 52%, #131f38 100%);
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.bg-grid {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: var(--z-bg);
|
||||
background-image:
|
||||
linear-gradient(rgba(116, 153, 211, 0.08) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(116, 153, 211, 0.08) 1px, transparent 1px);
|
||||
background-size: 36px 36px;
|
||||
mask-image: radial-gradient(circle at 35% 15%, black 0%, transparent 78%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bg-glow {
|
||||
position: fixed;
|
||||
border-radius: 999px;
|
||||
pointer-events: none;
|
||||
z-index: var(--z-bg);
|
||||
}
|
||||
|
||||
.bg-glow-1 {
|
||||
width: 420px;
|
||||
height: 420px;
|
||||
left: -120px;
|
||||
top: 30%;
|
||||
background: rgba(86, 135, 214, 0.18);
|
||||
filter: blur(60px);
|
||||
}
|
||||
|
||||
.bg-glow-2 {
|
||||
width: 360px;
|
||||
height: 360px;
|
||||
right: -90px;
|
||||
top: 80px;
|
||||
background: rgba(115, 168, 255, 0.12);
|
||||
filter: blur(58px);
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
position: relative;
|
||||
z-index: var(--z-layout);
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
gap: var(--space-4);
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background: linear-gradient(165deg, rgba(30, 47, 80, 0.86) 0%, rgba(17, 27, 48, 0.95) 100%);
|
||||
border: 1px solid var(--border-0);
|
||||
box-shadow: var(--shadow-0);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-4);
|
||||
position: sticky;
|
||||
top: var(--space-4);
|
||||
height: calc(100vh - 40px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.brand-dot {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, var(--accent-0), #80dbff);
|
||||
box-shadow: 0 0 0 6px rgba(107, 166, 255, 0.22);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.brand h1 {
|
||||
margin: 0;
|
||||
font-size: 21px;
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
|
||||
.brand p {
|
||||
margin: 6px 0 0;
|
||||
color: var(--text-2);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.nav-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
color: var(--text-1);
|
||||
text-align: left;
|
||||
font-size: 15px;
|
||||
line-height: 1.45;
|
||||
padding: 12px 14px;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-item:hover,
|
||||
.nav-item:focus-visible {
|
||||
color: var(--text-0);
|
||||
border-color: var(--border-1);
|
||||
background: rgba(139, 177, 240, 0.14);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: #031126;
|
||||
background: linear-gradient(135deg, #8ab6ff 0%, #9ed4ff 100%);
|
||||
border-color: #acd2ff;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 10px 24px rgba(99, 159, 243, 0.34);
|
||||
}
|
||||
|
||||
.main {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
padding-right: var(--space-2);
|
||||
}
|
||||
|
||||
.main-header {
|
||||
background: var(--surface-0);
|
||||
border: 1px solid var(--border-0);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-4);
|
||||
box-shadow: var(--shadow-1);
|
||||
backdrop-filter: blur(7px);
|
||||
}
|
||||
|
||||
.main-header h2 {
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.main-header p {
|
||||
margin: 8px 0 0;
|
||||
color: var(--text-1);
|
||||
}
|
||||
|
||||
.view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.view.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.grid.cols-4 {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.grid.cols-3 {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.grid.cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--surface-1);
|
||||
border: 1px solid var(--border-0);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-1);
|
||||
padding: var(--space-4);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.card + .card {
|
||||
margin-top: var(--space-3);
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
margin: 0 0 var(--space-3);
|
||||
font-size: 18px;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.stat {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stat::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -32px;
|
||||
bottom: -38px;
|
||||
width: 112px;
|
||||
height: 112px;
|
||||
border-radius: 999px;
|
||||
background: radial-gradient(circle at center, rgba(118, 173, 255, 0.36) 0%, rgba(118, 173, 255, 0) 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.stat p {
|
||||
margin: 0;
|
||||
font-size: 38px;
|
||||
font-weight: 700;
|
||||
color: var(--accent-1);
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.list li {
|
||||
border-bottom: 1px solid rgba(157, 185, 229, 0.2);
|
||||
padding: 12px 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.list a {
|
||||
color: #afd0ff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.list a:hover,
|
||||
.list a:focus-visible {
|
||||
color: #cde4ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 1.6;
|
||||
color: var(--text-1);
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
button {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
width: 100%;
|
||||
margin-top: 6px;
|
||||
min-height: 44px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border-0);
|
||||
color: var(--text-0);
|
||||
background: rgba(13, 21, 39, 0.64);
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: rgba(173, 192, 228, 0.66);
|
||||
}
|
||||
|
||||
input:focus-visible,
|
||||
select:focus-visible,
|
||||
button:focus-visible {
|
||||
outline: 2px solid rgba(136, 189, 255, 0.92);
|
||||
outline-offset: 2px;
|
||||
border-color: rgba(136, 189, 255, 0.92);
|
||||
}
|
||||
|
||||
select {
|
||||
appearance: none;
|
||||
background-image:
|
||||
linear-gradient(45deg, transparent 50%, #aacfff 50%),
|
||||
linear-gradient(135deg, #aacfff 50%, transparent 50%);
|
||||
background-position:
|
||||
calc(100% - 16px) 17px,
|
||||
calc(100% - 11px) 17px;
|
||||
background-size: 5px 5px, 5px 5px;
|
||||
background-repeat: no-repeat;
|
||||
padding-right: 34px;
|
||||
}
|
||||
|
||||
.span-2 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.alert.info {
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid rgba(130, 174, 255, 0.38);
|
||||
background: rgba(96, 150, 235, 0.15);
|
||||
color: #c5dcff;
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.month-panel {
|
||||
border: 1px solid var(--border-0);
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(21, 34, 61, 0.6);
|
||||
padding: var(--space-3);
|
||||
}
|
||||
|
||||
.month-grid {
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.month-action {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.month-action button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.project-item {
|
||||
border: 1px solid var(--border-0);
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(20, 31, 55, 0.62);
|
||||
padding: var(--space-3);
|
||||
}
|
||||
|
||||
.project-item h4 {
|
||||
margin: 0 0 10px;
|
||||
color: var(--text-0);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
button {
|
||||
min-height: 44px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border-0);
|
||||
background: rgba(88, 120, 170, 0.22);
|
||||
color: var(--text-0);
|
||||
padding: 0 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
button:focus-visible {
|
||||
background: rgba(122, 160, 221, 0.35);
|
||||
border-color: rgba(157, 194, 253, 0.72);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
button.primary {
|
||||
background: linear-gradient(135deg, #6fafff 0%, #8fcbff 100%);
|
||||
border-color: #9cd0ff;
|
||||
color: #04162d;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
button.primary:hover,
|
||||
button.primary:focus-visible {
|
||||
background: linear-gradient(135deg, #85bcff 0%, #a1d7ff 100%);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#log-panel {
|
||||
display: none;
|
||||
margin-top: var(--space-3);
|
||||
}
|
||||
|
||||
.live-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.live-column header,
|
||||
.system-log-wrap > header {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--text-1);
|
||||
margin-bottom: 7px;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.live-output,
|
||||
.system-output {
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 12px;
|
||||
font-family: "JetBrains Mono", "Consolas", monospace;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.62;
|
||||
border: 1px solid var(--border-0);
|
||||
background: rgba(6, 12, 25, 0.72);
|
||||
}
|
||||
|
||||
.live-output {
|
||||
color: #d6e5ff;
|
||||
}
|
||||
|
||||
.live-column.reasoning .live-output {
|
||||
background: rgba(9, 18, 39, 0.82);
|
||||
}
|
||||
|
||||
.live-column.answer .live-output {
|
||||
background: rgba(12, 24, 37, 0.78);
|
||||
}
|
||||
|
||||
.system-output {
|
||||
color: #e8f1ff;
|
||||
background: rgba(6, 12, 24, 0.9);
|
||||
border-color: rgba(150, 180, 230, 0.34);
|
||||
}
|
||||
|
||||
.live-output .muted,
|
||||
.system-output .muted {
|
||||
color: #a9bde3;
|
||||
}
|
||||
|
||||
.log-line {
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.system-output .log-line.is-info {
|
||||
color: #dbe8ff;
|
||||
}
|
||||
|
||||
.system-output .log-line.is-error {
|
||||
color: #ffb6bc;
|
||||
}
|
||||
|
||||
.live-output .log-line.is-reasoning {
|
||||
color: #ccdeff;
|
||||
}
|
||||
|
||||
.live-output .log-line.is-answer {
|
||||
color: #baf2da;
|
||||
}
|
||||
|
||||
.system-log-wrap {
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.history-toolbar {
|
||||
display: grid;
|
||||
grid-template-columns: 180px 180px minmax(220px, 1fr) 120px;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.table-wrap {
|
||||
overflow-x: auto;
|
||||
border: 1px solid rgba(157, 185, 229, 0.2);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: 720px;
|
||||
background: rgba(8, 15, 30, 0.35);
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 10px 8px;
|
||||
border-bottom: 1px solid rgba(157, 185, 229, 0.16);
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
th {
|
||||
color: #d5e4ff;
|
||||
background: rgba(129, 167, 229, 0.12);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.pager {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
color: var(--text-2);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.pager .pager-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-cancel-task {
|
||||
min-height: 32px;
|
||||
padding: 0 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
border-radius: 999px;
|
||||
padding: 2px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.tag.SUCCESS {
|
||||
background: rgba(73, 194, 138, 0.16);
|
||||
border-color: rgba(73, 194, 138, 0.5);
|
||||
color: #95edc2;
|
||||
}
|
||||
|
||||
.tag.RUNNING,
|
||||
.tag.PENDING {
|
||||
background: rgba(240, 184, 93, 0.16);
|
||||
border-color: rgba(240, 184, 93, 0.45);
|
||||
color: #ffd99d;
|
||||
}
|
||||
|
||||
.tag.FAILED {
|
||||
background: rgba(255, 127, 135, 0.16);
|
||||
border-color: rgba(255, 127, 135, 0.5);
|
||||
color: #ffb5bc;
|
||||
}
|
||||
|
||||
.tag.CANCELLED {
|
||||
background: rgba(148, 166, 196, 0.16);
|
||||
border-color: rgba(148, 166, 196, 0.45);
|
||||
color: #ced9f1;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
right: 24px;
|
||||
bottom: 24px;
|
||||
z-index: var(--z-toast);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 12px 14px;
|
||||
background: rgba(8, 16, 33, 0.96);
|
||||
border: 1px solid rgba(132, 171, 235, 0.46);
|
||||
color: #eef4ff;
|
||||
min-width: 240px;
|
||||
max-width: 400px;
|
||||
display: none;
|
||||
box-shadow: var(--shadow-0);
|
||||
}
|
||||
|
||||
.toast.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.grid.cols-4 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.app-shell {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: auto;
|
||||
padding: 14px;
|
||||
gap: 12px;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.brand p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-list {
|
||||
flex-direction: row;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 2px;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
flex: 0 0 auto;
|
||||
white-space: nowrap;
|
||||
min-height: 44px;
|
||||
padding: 10px 13px;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.grid.cols-2,
|
||||
.grid.cols-3,
|
||||
.form-grid,
|
||||
.history-toolbar {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.span-2 {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.month-action {
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.grid.cols-4 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.main-header h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: var(--space-3);
|
||||
}
|
||||
|
||||
.live-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.toast {
|
||||
right: 12px;
|
||||
left: 12px;
|
||||
bottom: 12px;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user