Merge pull request 'Feat/commercial saas polish' (#12) from feat/commercial-saas-polish into master
Reviewed-on: #12
This commit was merged in pull request #12.
This commit is contained in:
@@ -26,7 +26,6 @@
|
|||||||
VideoCameraOutlined,
|
VideoCameraOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import {
|
import {
|
||||||
Background,
|
|
||||||
ReactFlow,
|
ReactFlow,
|
||||||
} from "@xyflow/react";
|
} from "@xyflow/react";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent, type CSSProperties, type MouseEvent, type WheelEvent } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent, type CSSProperties, type MouseEvent, type WheelEvent } from "react";
|
||||||
@@ -3560,7 +3559,8 @@ function CanvasPage({
|
|||||||
onMouseMove={(shouldShowEmptyProjectState || isWaitingForProjects) ? undefined : handleCanvasMouseMove}
|
onMouseMove={(shouldShowEmptyProjectState || isWaitingForProjects) ? undefined : handleCanvasMouseMove}
|
||||||
onWheel={(shouldShowEmptyProjectState || isWaitingForProjects) ? undefined : handleCanvasWheel}
|
onWheel={(shouldShowEmptyProjectState || isWaitingForProjects) ? undefined : handleCanvasWheel}
|
||||||
style={{
|
style={{
|
||||||
"--canvas-bg-size": `${24 * canvasViewport.zoom}px`,
|
"--canvas-bg-size": `${34 * canvasViewport.zoom}px`,
|
||||||
|
"--canvas-bg-dot": `${1.35 * canvasViewport.zoom}px`,
|
||||||
"--canvas-bg-x": `${canvasViewport.x}px`,
|
"--canvas-bg-x": `${canvasViewport.x}px`,
|
||||||
"--canvas-bg-y": `${canvasViewport.y}px`,
|
"--canvas-bg-y": `${canvasViewport.y}px`,
|
||||||
cursor: canvasPanDrag ? "grabbing" : spacePanning ? "grab" : undefined,
|
cursor: canvasPanDrag ? "grabbing" : spacePanning ? "grab" : undefined,
|
||||||
@@ -3748,9 +3748,7 @@ function CanvasPage({
|
|||||||
proOptions={{ hideAttribution: true }}
|
proOptions={{ hideAttribution: true }}
|
||||||
onPaneClick={(shouldShowEmptyProjectState || isWaitingForProjects) ? undefined : handlePaneClick}
|
onPaneClick={(shouldShowEmptyProjectState || isWaitingForProjects) ? undefined : handlePaneClick}
|
||||||
onPaneContextMenu={(shouldShowEmptyProjectState || isWaitingForProjects) ? undefined : handlePaneContextMenu}
|
onPaneContextMenu={(shouldShowEmptyProjectState || isWaitingForProjects) ? undefined : handlePaneContextMenu}
|
||||||
>
|
/>
|
||||||
<Background gap={24} color="transparent" className="studio-canvas__background" />
|
|
||||||
</ReactFlow>
|
|
||||||
<div className="studio-canvas-zoom-controls" onMouseDown={(e) => e.stopPropagation()}>
|
<div className="studio-canvas-zoom-controls" onMouseDown={(e) => e.stopPropagation()}>
|
||||||
<button type="button" title="缩小" onClick={zoomCanvasOut}>−</button>
|
<button type="button" title="缩小" onClick={zoomCanvasOut}>−</button>
|
||||||
<button type="button" className="studio-canvas-zoom-controls__pct" title="重置缩放" onClick={resetCanvasZoom}>
|
<button type="button" className="studio-canvas-zoom-controls__pct" title="重置缩放" onClick={resetCanvasZoom}>
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import {
|
|||||||
CloseOutlined,
|
CloseOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
|
FileImageOutlined,
|
||||||
|
FolderOpenOutlined,
|
||||||
LockOutlined,
|
LockOutlined,
|
||||||
MailOutlined,
|
MailOutlined,
|
||||||
MobileOutlined,
|
MobileOutlined,
|
||||||
PhoneOutlined,
|
PhoneOutlined,
|
||||||
|
PlayCircleOutlined,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
SafetyOutlined,
|
SafetyOutlined,
|
||||||
ShareAltOutlined,
|
ShareAltOutlined,
|
||||||
@@ -180,6 +183,19 @@ function formatAssetStatus(status: string | undefined): string {
|
|||||||
return status || "资产";
|
return status || "资产";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatAssetType(type: SavedAssetItem["type"]): string {
|
||||||
|
const labels: Record<string, string> = {
|
||||||
|
character: "角色",
|
||||||
|
scene: "场景",
|
||||||
|
prop: "道具",
|
||||||
|
video: "视频",
|
||||||
|
image: "图像",
|
||||||
|
asset: "资产",
|
||||||
|
other: "素材",
|
||||||
|
};
|
||||||
|
return labels[type] || "素材";
|
||||||
|
}
|
||||||
|
|
||||||
function ProfilePage({
|
function ProfilePage({
|
||||||
session,
|
session,
|
||||||
usage,
|
usage,
|
||||||
@@ -608,21 +624,49 @@ function ProfilePage({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderCardPreview = (
|
||||||
|
url: string | null | undefined,
|
||||||
|
type: "image" | "video" | "project" | "asset",
|
||||||
|
label: string,
|
||||||
|
) => {
|
||||||
|
const mediaUrl = typeof url === "string" ? url.trim() : "";
|
||||||
|
const isVideoPreview = type === "video" || /\.(mp4|webm|mov)(\?|#|$)/i.test(mediaUrl);
|
||||||
|
const placeholderIcon =
|
||||||
|
type === "video" ? <PlayCircleOutlined /> : type === "project" ? <FolderOpenOutlined /> : <FileImageOutlined />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`profile-page__list-card-preview${mediaUrl ? " has-media" : ""}`} aria-hidden="true">
|
||||||
|
{mediaUrl ? (
|
||||||
|
isVideoPreview ? (
|
||||||
|
<video src={mediaUrl} muted playsInline preload="metadata" />
|
||||||
|
) : (
|
||||||
|
<img src={mediaUrl} alt="" loading="lazy" />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<span className="profile-page__list-card-placeholder">{placeholderIcon}</span>
|
||||||
|
)}
|
||||||
|
<span className="profile-page__media-badge">{label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const renderActivePanel = () => {
|
const renderActivePanel = () => {
|
||||||
if (activePanel === "works") {
|
if (activePanel === "works") {
|
||||||
return visibleWorks.length ? (
|
return visibleWorks.length ? (
|
||||||
<div className="profile-page__works-scroll">
|
<div className="profile-page__works-scroll">
|
||||||
<div className="profile-page__list-grid motion-stagger">
|
<div className="profile-page__list-grid motion-stagger">
|
||||||
{visibleWorks.map((task) => (
|
{visibleWorks.map((task) => (
|
||||||
<article key={task.id} className="profile-page__list-card">
|
<article key={task.id} className="profile-page__list-card profile-page__media-card">
|
||||||
<div className="profile-page__list-card-head">
|
{renderCardPreview(task.outputUrl, task.type === "video" ? "video" : "image", formatTaskType(task.type))}
|
||||||
<strong>{task.title}</strong>
|
<div className="profile-page__list-card-body">
|
||||||
<span>{formatTaskType(task.type)}</span>
|
<div className="profile-page__list-card-head">
|
||||||
</div>
|
<strong>{task.title}</strong>
|
||||||
<p>{task.prompt}</p>
|
</div>
|
||||||
<div className="profile-page__list-card-meta">
|
<p>{task.prompt}</p>
|
||||||
<span>{formatTaskStatus(task.status)}</span>
|
<div className="profile-page__list-card-meta">
|
||||||
<span>{formatProfileDate(task.createdAt)}</span>
|
<span>{formatTaskStatus(task.status)}</span>
|
||||||
|
<span>{formatProfileDate(task.createdAt)}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
@@ -637,25 +681,27 @@ function ProfilePage({
|
|||||||
return projects.length ? (
|
return projects.length ? (
|
||||||
<div className="profile-page__list-grid motion-stagger">
|
<div className="profile-page__list-grid motion-stagger">
|
||||||
{projects.map((project) => (
|
{projects.map((project) => (
|
||||||
<article key={project.id} className="profile-page__list-card">
|
<article key={project.id} className="profile-page__list-card profile-page__media-card">
|
||||||
<div className="profile-page__list-card-head">
|
{renderCardPreview(project.thumbnailUrl, "project", "项目")}
|
||||||
<strong>{project.name}</strong>
|
<div className="profile-page__list-card-body">
|
||||||
<span>{formatProfileDate(project.updatedAt)}</span>
|
<div className="profile-page__list-card-head">
|
||||||
{onDeleteProject ? (
|
<strong>{project.name}</strong>
|
||||||
<button
|
{onDeleteProject ? (
|
||||||
type="button"
|
<button
|
||||||
className="profile-page__delete-project"
|
type="button"
|
||||||
aria-label={`删除项目 ${project.name}`}
|
className="profile-page__delete-project"
|
||||||
onClick={() => onDeleteProject(project)}
|
aria-label={`删除项目 ${project.name}`}
|
||||||
>
|
onClick={() => onDeleteProject(project)}
|
||||||
<DeleteOutlined />
|
>
|
||||||
</button>
|
<DeleteOutlined />
|
||||||
) : null}
|
</button>
|
||||||
</div>
|
) : null}
|
||||||
<p>{project.description || "最近更新的项目"}</p>
|
</div>
|
||||||
<div className="profile-page__list-card-meta">
|
<p>{project.description || "最近更新的项目"}</p>
|
||||||
<span>{project.storyboardCount} 节点</span>
|
<div className="profile-page__list-card-meta">
|
||||||
<span>{project.imageCount} 图 / {project.videoCount} 视频</span>
|
<span>{project.storyboardCount} 节点</span>
|
||||||
|
<span>{formatProfileDate(project.updatedAt)}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
@@ -669,15 +715,18 @@ function ProfilePage({
|
|||||||
return savedAssets.length ? (
|
return savedAssets.length ? (
|
||||||
<div className="profile-page__list-grid">
|
<div className="profile-page__list-grid">
|
||||||
{savedAssets.map((asset) => (
|
{savedAssets.map((asset) => (
|
||||||
<article key={asset.id} className="profile-page__list-card">
|
<article key={asset.id} className="profile-page__list-card profile-page__media-card">
|
||||||
<div className="profile-page__list-card-head">
|
{renderCardPreview(asset.imageUrl || asset.url, asset.type === "video" ? "video" : "asset", formatAssetType(asset.type))}
|
||||||
<strong>{asset.name}</strong>
|
<div className="profile-page__list-card-body">
|
||||||
<span>{formatAssetStatus(asset.status)}</span>
|
<div className="profile-page__list-card-head">
|
||||||
</div>
|
<strong>{asset.name}</strong>
|
||||||
<p>{asset.description}</p>
|
<span>{formatAssetStatus(asset.status)}</span>
|
||||||
<div className="profile-page__list-card-meta">
|
</div>
|
||||||
<span>{asset.type}</span>
|
<p>{asset.description}</p>
|
||||||
<span>{formatProfileDate(asset.updatedAt)}</span>
|
<div className="profile-page__list-card-meta">
|
||||||
|
<span>{formatAssetType(asset.type)}</span>
|
||||||
|
<span>{formatProfileDate(asset.updatedAt)}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
@@ -791,6 +840,50 @@ function ProfilePage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="profile-page__account-card">
|
||||||
|
<div className="profile-page__list-tabs">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={accountPanel === "credits" ? "is-active" : ""}
|
||||||
|
onClick={() => setAccountPanel("credits")}
|
||||||
|
>
|
||||||
|
积分 {(totalBalance / 100).toFixed(2)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={accountPanel === "tasks" ? "is-active" : ""}
|
||||||
|
onClick={() => setAccountPanel("tasks")}
|
||||||
|
>
|
||||||
|
任务 {tasks.length}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="profile-page__upload-card profile-page__upload-card--meta">
|
||||||
|
{accountPanel === "credits" ? (
|
||||||
|
<>
|
||||||
|
<span className="profile-page__meta-item">
|
||||||
|
<small>当前账号</small>
|
||||||
|
<strong>{displayName}</strong>
|
||||||
|
</span>
|
||||||
|
<span className="profile-page__meta-item">
|
||||||
|
<small>积分剩余</small>
|
||||||
|
<strong>{(usage.balanceCents / 100).toFixed(2)}</strong>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="profile-page__meta-item">
|
||||||
|
<small>任务总数</small>
|
||||||
|
<strong>{tasks.length}</strong>
|
||||||
|
</span>
|
||||||
|
<span className="profile-page__meta-item">
|
||||||
|
<small>已完成</small>
|
||||||
|
<strong>{completedTasks.length}</strong>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="button" className="profile-page__share-btn profile-page__share-btn--plan">
|
<button type="button" className="profile-page__share-btn profile-page__share-btn--plan">
|
||||||
<ShareAltOutlined />
|
<ShareAltOutlined />
|
||||||
{packageLabel}
|
{packageLabel}
|
||||||
@@ -838,52 +931,6 @@ function ProfilePage({
|
|||||||
</span>
|
</span>
|
||||||
{renderActivePanel()}
|
{renderActivePanel()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="profile-page__section">
|
|
||||||
<div className="profile-page__list-bar">
|
|
||||||
<div className="profile-page__list-tabs">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={accountPanel === "credits" ? "is-active" : ""}
|
|
||||||
onClick={() => setAccountPanel("credits")}
|
|
||||||
>
|
|
||||||
积分 {(totalBalance / 100).toFixed(2)}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={accountPanel === "tasks" ? "is-active" : ""}
|
|
||||||
onClick={() => setAccountPanel("tasks")}
|
|
||||||
>
|
|
||||||
任务 {tasks.length}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="profile-page__upload-card profile-page__upload-card--meta">
|
|
||||||
{accountPanel === "credits" ? (
|
|
||||||
<>
|
|
||||||
<span className="profile-page__meta-item">
|
|
||||||
<small>当前账号</small>
|
|
||||||
<strong>{displayName}</strong>
|
|
||||||
</span>
|
|
||||||
<span className="profile-page__meta-item">
|
|
||||||
<small>积分剩余</small>
|
|
||||||
<strong>{(usage.balanceCents / 100).toFixed(2)}</strong>
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className="profile-page__meta-item">
|
|
||||||
<small>任务总数</small>
|
|
||||||
<strong>{tasks.length}</strong>
|
|
||||||
</span>
|
|
||||||
<span className="profile-page__meta-item">
|
|
||||||
<small>已完成</small>
|
|
||||||
<strong>{completedTasks.length}</strong>
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -405,111 +405,113 @@ function ScriptTokensPage() {
|
|||||||
<div className="script-eval-v5-page">
|
<div className="script-eval-v5-page">
|
||||||
{/* Left Panel */}
|
{/* Left Panel */}
|
||||||
<aside className="script-eval-v5-left">
|
<aside className="script-eval-v5-left">
|
||||||
<div className="script-eval-v5-lp-section">
|
<div className="script-eval-v5-left-main">
|
||||||
<div className="script-eval-v5-lp-label">上传剧本</div>
|
<div className="script-eval-v5-lp-section">
|
||||||
<div
|
<div className="script-eval-v5-lp-label">上传剧本</div>
|
||||||
className="script-eval-v5-upload-zone"
|
<div
|
||||||
role="button"
|
className="script-eval-v5-upload-zone"
|
||||||
tabIndex={0}
|
role="button"
|
||||||
onClick={() => fileInputRef.current?.click()}
|
tabIndex={0}
|
||||||
onKeyDown={uploadKeyDown}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
>
|
onKeyDown={uploadKeyDown}
|
||||||
{uploadedFile ? (
|
>
|
||||||
<div className="script-eval-v5-upload-done is-show">
|
{uploadedFile ? (
|
||||||
<CheckCircleFilled />
|
<div className="script-eval-v5-upload-done is-show">
|
||||||
<span className="script-eval-v5-uf-meta">
|
<CheckCircleFilled />
|
||||||
<span className="script-eval-v5-uf-name">{uploadedFile.name}</span>
|
<span className="script-eval-v5-uf-meta">
|
||||||
<span className="script-eval-v5-uf-size">{formatFileSize(uploadedFile.size)}</span>
|
<span className="script-eval-v5-uf-name">{uploadedFile.name}</span>
|
||||||
</span>
|
<span className="script-eval-v5-uf-size">{formatFileSize(uploadedFile.size)}</span>
|
||||||
<span className="script-eval-v5-uf-re" onClick={(e) => { e.stopPropagation(); handleReset(); }}>
|
</span>
|
||||||
重新上传
|
<span className="script-eval-v5-uf-re" onClick={(e) => { e.stopPropagation(); handleReset(); }}>
|
||||||
</span>
|
重新上传
|
||||||
</div>
|
</span>
|
||||||
) : (
|
</div>
|
||||||
<>
|
) : (
|
||||||
<div className="script-eval-v5-upload-icon"><UploadOutlined /></div>
|
<>
|
||||||
<div className="script-eval-v5-upload-text">拖拽或点击上传</div>
|
<div className="script-eval-v5-upload-icon"><UploadOutlined /></div>
|
||||||
<button type="button" className="script-eval-v5-upload-btn" onClick={(e) => { e.stopPropagation(); fileInputRef.current?.click(); }}>
|
<div className="script-eval-v5-upload-text">拖拽或点击上传</div>
|
||||||
<UploadOutlined /> 选择剧本
|
<button type="button" className="script-eval-v5-upload-btn" onClick={(e) => { e.stopPropagation(); fileInputRef.current?.click(); }}>
|
||||||
</button>
|
<UploadOutlined /> 选择剧本
|
||||||
<div className="script-eval-v5-upload-hint">{TEXT_FILE_HINT}</div>
|
</button>
|
||||||
</>
|
<div className="script-eval-v5-upload-hint">{TEXT_FILE_HINT}</div>
|
||||||
)}
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<input ref={fileInputRef} type="file" accept={TEXT_FILE_ACCEPT} style={{ display: "none" }} onChange={handleFileUpload} />
|
||||||
</div>
|
</div>
|
||||||
<input ref={fileInputRef} type="file" accept={TEXT_FILE_ACCEPT} style={{ display: "none" }} onChange={handleFileUpload} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="script-eval-v5-lp-section">
|
<div className="script-eval-v5-lp-section">
|
||||||
<div className="script-eval-v5-lp-label">AI 识别信息</div>
|
<div className="script-eval-v5-lp-label">AI 识别信息</div>
|
||||||
<div className="script-eval-v5-info-grid">
|
<div className="script-eval-v5-info-grid">
|
||||||
{!result ? (
|
{!result ? (
|
||||||
<div className="script-eval-v5-info-empty">上传剧本并点击评测后识别</div>
|
<div className="script-eval-v5-info-empty">上传剧本并点击评测后识别</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="script-eval-v5-info-item">
|
<div className="script-eval-v5-info-item">
|
||||||
<span className="script-eval-v5-info-key">综合评分</span>
|
<span className="script-eval-v5-info-key">综合评分</span>
|
||||||
<span className="script-eval-v5-info-val"><span className="script-eval-v5-info-tag">{result.totalScore}分 · {grade}级</span></span>
|
<span className="script-eval-v5-info-val"><span className="script-eval-v5-info-tag">{result.totalScore}分 · {grade}级</span></span>
|
||||||
</div>
|
</div>
|
||||||
<div className="script-eval-v5-info-item">
|
<div className="script-eval-v5-info-item">
|
||||||
<span className="script-eval-v5-info-key">文本长度</span>
|
<span className="script-eval-v5-info-key">文本长度</span>
|
||||||
<span className="script-eval-v5-info-val">{script.length} 字</span>
|
<span className="script-eval-v5-info-val">{script.length} 字</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="script-eval-v5-info-item">
|
<div className="script-eval-v5-info-item">
|
||||||
<span className="script-eval-v5-info-key">评测时间</span>
|
<span className="script-eval-v5-info-key">评测时间</span>
|
||||||
<span className="script-eval-v5-info-val">{new Date().toLocaleDateString("zh-CN")}</span>
|
<span className="script-eval-v5-info-val">{new Date().toLocaleDateString("zh-CN")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="script-eval-v5-info-item">
|
<div className="script-eval-v5-info-item">
|
||||||
<span className="script-eval-v5-info-key">击败比例</span>
|
<span className="script-eval-v5-info-key">击败比例</span>
|
||||||
<span className="script-eval-v5-info-val">{beatPct}%</span>
|
<span className="script-eval-v5-info-val">{beatPct}%</span>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="script-eval-v5-lp-section is-fill">
|
<div className="script-eval-v5-lp-section is-fill">
|
||||||
<div className="script-eval-v5-lp-label">历史评测</div>
|
<div className="script-eval-v5-lp-label">历史评测</div>
|
||||||
<div className="script-eval-v5-history-list">
|
<div className="script-eval-v5-history-list">
|
||||||
{!session ? (
|
{!session ? (
|
||||||
<div className="script-eval-v5-history-empty">登录后查看云端评测记录</div>
|
<div className="script-eval-v5-history-empty">登录后查看云端评测记录</div>
|
||||||
) : history.length === 0 ? (
|
) : history.length === 0 ? (
|
||||||
<div className="script-eval-v5-history-empty">暂无评测记录</div>
|
<div className="script-eval-v5-history-empty">暂无评测记录</div>
|
||||||
) : (
|
) : (
|
||||||
history.map((item, i) => (
|
history.map((item, i) => (
|
||||||
<div key={i} className={`script-eval-v5-history-item${i === activeHistoryIndex ? " is-active" : ""}`}
|
<div key={i} className={`script-eval-v5-history-item${i === activeHistoryIndex ? " is-active" : ""}`}
|
||||||
onClick={() => handleHistoryClick(item, i)} role="button" tabIndex={0}
|
onClick={() => handleHistoryClick(item, i)} role="button" tabIndex={0}
|
||||||
onKeyDown={(e) => { if ((e as React.KeyboardEvent).key === "Enter") handleHistoryClick(item, i); }}>
|
onKeyDown={(e) => { if ((e as React.KeyboardEvent).key === "Enter") handleHistoryClick(item, i); }}>
|
||||||
<div className="script-eval-v5-hi-left">
|
<div className="script-eval-v5-hi-left">
|
||||||
<div className="script-eval-v5-hi-name">{item.name}</div>
|
<div className="script-eval-v5-hi-name">{item.name}</div>
|
||||||
<div className="script-eval-v5-hi-date">{item.date}</div>
|
<div className="script-eval-v5-hi-date">{item.date}</div>
|
||||||
<div className="script-eval-v5-hi-bar">
|
<div className="script-eval-v5-hi-bar">
|
||||||
<div className="script-eval-v5-hi-bar-fill" style={{ width: `${Math.min(92, (item.score / 100) * 100)}%` }} />
|
<div className="script-eval-v5-hi-bar-fill" style={{ width: `${Math.min(92, (item.score / 100) * 100)}%` }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="script-eval-v5-hi-right">
|
||||||
|
<div className={`script-eval-v5-hi-score${item.score >= 90 ? " is-green" : ""}`}>{item.score}</div>
|
||||||
|
<div className="script-eval-v5-hi-grade">{item.grade}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="script-eval-v5-hi-right">
|
))
|
||||||
<div className={`script-eval-v5-hi-score${item.score >= 90 ? " is-green" : ""}`}>{item.score}</div>
|
)}
|
||||||
<div className="script-eval-v5-hi-grade">{item.grade}</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="script-eval-v5-lp-bottom">
|
<div className="script-eval-v5-lp-bottom">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="script-eval-v5-eval-btn"
|
className="script-eval-v5-eval-btn"
|
||||||
disabled={loading || !hasContent}
|
disabled={loading || !hasContent}
|
||||||
onClick={() => void handleEvaluate()}
|
onClick={() => void handleEvaluate()}
|
||||||
>
|
>
|
||||||
{loading ? <LoadingOutlined /> : <ThunderboltOutlined />}
|
{loading ? <LoadingOutlined /> : <ThunderboltOutlined />}
|
||||||
<span>{loading ? "评测中..." : "开始评测"}</span>
|
<span>{loading ? "评测中..." : "开始评测"}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" className="script-eval-v5-export-btn" disabled={!result} onClick={handleExportMarkdown}>
|
<button type="button" className="script-eval-v5-export-btn" disabled={!result} onClick={handleExportMarkdown}>
|
||||||
<DownloadOutlined />
|
<DownloadOutlined />
|
||||||
<span>导出评测报告</span>
|
<span>导出评测报告</span>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
|||||||
@@ -271,9 +271,8 @@ function TokenUsagePage({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<section className="management-metric-cards" aria-label="关键指标">
|
<section className="management-metric-cards" aria-label="关键指标">
|
||||||
{metricCards.map((card, index) => (
|
{metricCards.map((card) => (
|
||||||
<article key={card.key} className={`management-metric-card is-${card.tone}`}>
|
<article key={card.key} className={`management-metric-card is-${card.tone}`}>
|
||||||
<span className="management-metric-card__index">{String(index + 1).padStart(2, "0")}</span>
|
|
||||||
<span className="management-metric-card__label">{card.label}</span>
|
<span className="management-metric-card__label">{card.label}</span>
|
||||||
<strong className="management-metric-card__value">{card.value}</strong>
|
<strong className="management-metric-card__value">{card.value}</strong>
|
||||||
<span className="management-metric-card__hint">{card.hint}</span>
|
<span className="management-metric-card__hint">{card.hint}</span>
|
||||||
|
|||||||
@@ -8598,9 +8598,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.product-clone-page[data-tool="clone"] .clone-ai-logo {
|
.product-clone-page[data-tool="clone"] .clone-ai-logo {
|
||||||
position: sticky;
|
position: static;
|
||||||
top: 0;
|
z-index: auto;
|
||||||
z-index: 3;
|
|
||||||
margin: -18px -18px 2px;
|
margin: -18px -18px 2px;
|
||||||
padding: 16px 18px 14px;
|
padding: 16px 18px 14px;
|
||||||
border-bottom-color: var(--ecm-line);
|
border-bottom-color: var(--ecm-line);
|
||||||
@@ -9103,7 +9102,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.product-clone-page[data-tool="clone"] .clone-ai-logo {
|
.product-clone-page[data-tool="clone"] .clone-ai-logo {
|
||||||
margin: -14px -14px 0;
|
margin: 0;
|
||||||
padding: 14px 54px 12px 14px;
|
padding: 14px 54px 12px 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9382,3 +9381,42 @@
|
|||||||
padding-top: 14px;
|
padding-top: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile clone header alignment: keep the tool title in normal flow, but attach it to the top nav rhythm. */
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.product-clone-page[data-tool="clone"] {
|
||||||
|
padding-top: 59px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-clone-page[data-tool="clone"] > .product-clone-shell {
|
||||||
|
min-height: calc(100% - 59px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-clone-page[data-tool="clone"] .clone-ai-panel {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-clone-page[data-tool="clone"] .clone-ai-logo {
|
||||||
|
margin: 0 -18px 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 620px) {
|
||||||
|
.product-clone-page[data-tool="clone"] .clone-ai-panel {
|
||||||
|
padding: 0 14px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-clone-page[data-tool="clone"] .clone-ai-logo {
|
||||||
|
margin: 0 -14px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.product-clone-page[data-tool="clone"] {
|
||||||
|
padding-top: 59px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-clone-page[data-tool="clone"] > .product-clone-shell {
|
||||||
|
min-height: calc(100% - 59px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user