fix(web): 写作台流式/保存 live region + 图标按钮换 lucide 无障碍 + 三栏改 xl 断点 + 手稿字号统一

This commit is contained in:
Yaojia Wang
2026-06-30 08:56:23 +02:00
parent e82aff7067
commit 69030eacc3
4 changed files with 137 additions and 78 deletions

View File

@@ -1,8 +1,11 @@
"use client";
import Link from "next/link";
import { RotateCcw, X } from "lucide-react";
import { Minus, Pin, PinOff, Plus, RotateCcw, Undo2, X } from "lucide-react";
import { ThinkingIndicator } from "@/components/ThinkingIndicator";
import { Button } from "@/components/ui/Button";
import { SectionHeader } from "@/components/ui/SectionHeader";
import { StatusNote } from "@/components/ui/StatusNote";
import { buttonClass } from "@/lib/ui/variants";
import {
@@ -27,7 +30,7 @@ interface ChapterAssistantProps {
// 桌面 aside 包裹;移动端经 Workbench 抽屉复用 AssistantContent。
export function ChapterAssistant({ projectId, chapterNo }: ChapterAssistantProps) {
return (
<aside className="hidden border-l border-line bg-panel py-4 lg:block">
<aside className="hidden border-l border-line bg-panel py-4 xl:block">
<AssistantContent projectId={projectId} chapterNo={chapterNo} />
</aside>
);
@@ -41,26 +44,31 @@ export function AssistantContent({ projectId, chapterNo }: ChapterAssistantProps
return (
<div className="px-4">
<div className="flex items-center justify-between">
<h2 className="text-sm font-semibold text-ink"></h2>
{injection.saving ? (
<span className="text-[10px] text-ink-soft"></span>
) : null}
</div>
<SectionHeader
title="本章助手"
action={
injection.saving ? (
<ThinkingIndicator
label="助手保存中"
className="text-xs text-ink-soft"
/>
) : null
}
/>
<section className="mt-4">
<div className="flex items-center justify-between">
<h3 className="text-xs font-semibold uppercase tracking-wide text-ink-soft">
</h3>
{data ? (
<RecentStepper
value={data.recent_n}
disabled={injection.saving}
onChange={injection.setRecentN}
/>
) : null}
</div>
<SectionHeader
title="本章注入(透明)"
action={
data ? (
<RecentStepper
value={data.recent_n}
disabled={injection.saving}
onChange={injection.setRecentN}
/>
) : null
}
/>
{injection.loading ? (
<p className="mt-2 text-xs text-ink-soft"></p>
@@ -131,9 +139,7 @@ export function AssistantContent({ projectId, chapterNo }: ChapterAssistantProps
{excluded.length > 0 ? (
<div className="mt-3">
<h4 className="text-[10px] uppercase tracking-wide text-ink-soft">
</h4>
<SectionHeader title="已排除(不注入)" />
<ul className="mt-1 space-y-1">
{excluded.map((ref) => (
<ExcludedRow
@@ -149,12 +155,10 @@ export function AssistantContent({ projectId, chapterNo }: ChapterAssistantProps
</section>
<section className="mt-6">
<h3 className="text-xs font-semibold uppercase tracking-wide text-ink-soft">
</h3>
<p className="mt-2 text-xs text-ink-soft">
/ / / 稿
</p>
<SectionHeader
title="四审"
description="一致性 / 伏笔 / 文风 / 节奏,在审稿页逐条裁决。"
/>
<Link
href={`/projects/${projectId}/review?chapter=${chapterNo}`}
className={buttonClass({ variant: "outline", size: "sm", className: "mt-2" })}
@@ -179,42 +183,44 @@ function EntityRow({ entity, pinned, disabled, onTogglePin, onExclude }: EntityR
return (
<li className="rounded border border-line bg-bg p-2">
<div className="flex items-center gap-2">
<span className="rounded bg-panel px-1.5 py-0.5 text-[10px] text-ink-soft">
<span className="rounded bg-panel px-1.5 py-0.5 text-2xs text-ink-soft">
{kindLabel(entity.kind)}
</span>
<span className="text-sm text-ink">{entity.name}</span>
<div className="ml-auto flex items-center gap-1">
<button
type="button"
<Button
variant="ghost"
size="icon"
disabled={disabled}
aria-pressed={pinned}
title={pinned ? "取消置顶" : "置顶(强制注入)"}
aria-label={
pinned ? `取消置顶 ${entity.name}` : `置顶 ${entity.name}(强制注入)`
}
onClick={() => onTogglePin(ref)}
className={`rounded px-1.5 py-0.5 text-[11px] disabled:opacity-50 ${
pinned
? "bg-[var(--color-cinnabar-wash)] text-cinnabar"
: "text-ink-soft hover:bg-panel"
}`}
className={pinned ? "text-cinnabar" : undefined}
>
📌
</button>
<button
type="button"
{pinned ? (
<Pin className="h-4 w-4" aria-hidden="true" />
) : (
<PinOff className="h-4 w-4" aria-hidden="true" />
)}
</Button>
<Button
variant="ghost"
size="icon"
disabled={disabled}
title="排除(不注入本章)"
aria-label={`排除 ${entity.name}`}
aria-label={`排除 ${entity.name}(不注入本章)`}
onClick={() => onExclude(ref)}
className="rounded px-1.5 py-0.5 text-[11px] text-ink-soft hover:bg-panel disabled:opacity-50"
>
</button>
<X className="h-4 w-4" aria-hidden="true" />
</Button>
</div>
</div>
<div className="mt-1.5 flex flex-wrap gap-1">
{(entity.reasons ?? []).map((reason) => (
<span
key={reason}
className="rounded bg-[var(--color-cinnabar-wash)] px-1.5 py-0.5 text-[10px] text-cinnabar"
className="rounded bg-[var(--color-cinnabar-wash)] px-1.5 py-0.5 text-2xs text-cinnabar"
>
{reasonLabel(reason)}
</span>
@@ -233,17 +239,18 @@ interface ExcludedRowProps {
function ExcludedRow({ refItem, disabled, onRestore }: ExcludedRowProps) {
return (
<li className="flex items-center gap-2 rounded border border-dashed border-line px-2 py-1">
<span className="text-[10px] text-ink-soft">{kindLabel(refItem.kind)}</span>
<span className="text-2xs text-ink-soft">{kindLabel(refItem.kind)}</span>
<span className="text-xs text-ink-soft line-through">{refItem.name}</span>
<button
type="button"
<Button
variant="ghost"
size="icon"
disabled={disabled}
title="恢复(回到自动判定)"
aria-label={`恢复 ${refItem.name}(回到自动判定)`}
onClick={() => onRestore(refItem)}
className="ml-auto rounded px-1.5 py-0.5 text-[11px] text-cinnabar hover:bg-[var(--color-cinnabar-wash)] disabled:opacity-50"
className="ml-auto text-cinnabar"
>
</button>
<Undo2 className="h-4 w-4" aria-hidden="true" />
</Button>
</li>
);
}
@@ -257,27 +264,27 @@ interface RecentStepperProps {
// 「关联近 N 章」步进器:覆盖近况摘要回看章数(钳在 [MIN, MAX])。
function RecentStepper({ value, disabled, onChange }: RecentStepperProps) {
return (
<div className="flex items-center gap-1 text-[11px] text-ink-soft">
<div className="flex items-center gap-1 text-2xs text-ink-soft">
<span></span>
<button
type="button"
<Button
variant="ghost"
size="icon"
disabled={disabled || value <= RECENT_N_MIN}
aria-label="减少关联章数"
onClick={() => onChange(value - 1)}
className="rounded border border-line px-1.5 leading-tight hover:bg-panel disabled:opacity-40"
>
</button>
<Minus className="h-4 w-4" aria-hidden="true" />
</Button>
<span className="w-4 text-center tabular-nums text-ink">{value}</span>
<button
type="button"
<Button
variant="ghost"
size="icon"
disabled={disabled || value >= RECENT_N_MAX}
aria-label="增加关联章数"
onClick={() => onChange(value + 1)}
className="rounded border border-line px-1.5 leading-tight hover:bg-panel disabled:opacity-40"
>
+
</button>
<Plus className="h-4 w-4" aria-hidden="true" />
</Button>
<span></span>
</div>
);