chore(web): 前端格式 - 统一十个文件的 Prettier 格式 (#561)
Original-Commit: ade0049c0b80733dfea563bac2aae9260e373cf0 style(web): 前端格式 - 补 Prettier 格式(10 个文件) 这 10 个文件(3 个组件/样式与设置页、4 个 admin 主题与控件、1 个 lib、2 个测试) 用仓库自带 Prettier 检查会 warn,`bun scripts/check-changed-formatting.mjs` 在改动到它们时 会直接判红。本次只做格式化,不改任何行为:合并的 CSS 声明、去掉多余换行与括号、 统一链式调用换行。 验证:prettier --write 后 `check-changed-formatting.mjs` 通过、typecheck 通过、 1940 条前端测试全过、eslint 通过。
This commit is contained in:
@@ -24,25 +24,13 @@ const movingMap: Record<Direction, string> = {
|
||||
const highlight = "radial-gradient(75% 181% at 50% 50%, color-mix(in srgb, var(--user-ink) 22%, transparent) 0%, transparent 100%)";
|
||||
|
||||
function rotateDirection(current: Direction, clockwise: boolean): Direction {
|
||||
const directions: Direction[] = clockwise
|
||||
? ["TOP", "LEFT", "BOTTOM", "RIGHT"]
|
||||
: ["TOP", "RIGHT", "BOTTOM", "LEFT"];
|
||||
const directions: Direction[] = clockwise ? ["TOP", "LEFT", "BOTTOM", "RIGHT"] : ["TOP", "RIGHT", "BOTTOM", "LEFT"];
|
||||
const index = directions.indexOf(current);
|
||||
return directions[(index - 1 + directions.length) % directions.length];
|
||||
}
|
||||
|
||||
// Aceternity Hover Border Gradient:中性墨色描边,不用彩色高光。
|
||||
export function HoverBorderGradient<T extends ElementType = "button">({
|
||||
children,
|
||||
containerClassName,
|
||||
className,
|
||||
as,
|
||||
duration = 1,
|
||||
clockwise = true,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
...props
|
||||
}: HoverBorderGradientProps<T>) {
|
||||
export function HoverBorderGradient<T extends ElementType = "button">({ children, containerClassName, className, as, duration = 1, clockwise = true, onMouseEnter, onMouseLeave, ...props }: HoverBorderGradientProps<T>) {
|
||||
const Tag = (as || "button") as any;
|
||||
const reducedMotion = useReducedMotion();
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
export function shouldShowSidebarCheckin(input: {
|
||||
creditsEnabled?: boolean;
|
||||
checkinBonusMicrocredits?: number | null;
|
||||
checkedInToday?: boolean;
|
||||
}) {
|
||||
export function shouldShowSidebarCheckin(input: { creditsEnabled?: boolean; checkinBonusMicrocredits?: number | null; checkedInToday?: boolean }) {
|
||||
return Boolean(input.creditsEnabled && (input.checkinBonusMicrocredits || 0) > 0 && !input.checkedInToday);
|
||||
}
|
||||
|
||||
|
||||
@@ -179,9 +179,7 @@ export default function AgentLessonsPanel() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="text-xs text-foreground/60">
|
||||
记忆归用户自己批准和管理。这里只做巡查:可按用户、状态、关键词筛选,必要时删除违规内容。批准入口在用户的「设置 → Agent 记忆」。
|
||||
</p>
|
||||
<p className="text-xs text-foreground/60">记忆归用户自己批准和管理。这里只做巡查:可按用户、状态、关键词筛选,必要时删除违规内容。批准入口在用户的「设置 → Agent 记忆」。</p>
|
||||
<AdminDataTable
|
||||
toolbar={
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
@@ -211,13 +209,7 @@ export default function AgentLessonsPanel() {
|
||||
label: user.displayName ? `${user.displayName}(${user.username})` : user.username,
|
||||
}))}
|
||||
/>
|
||||
<Input.Search
|
||||
allowClear
|
||||
placeholder="主题、内容或用户名"
|
||||
className="w-[220px]"
|
||||
value={keyword}
|
||||
onChange={(event) => setKeyword(event.target.value)}
|
||||
/>
|
||||
<Input.Search allowClear placeholder="主题、内容或用户名" className="w-[220px]" value={keyword} onChange={(event) => setKeyword(event.target.value)} />
|
||||
<Button icon={<RefreshCw className="size-4" />} onClick={() => void load()}>
|
||||
刷新
|
||||
</Button>
|
||||
|
||||
@@ -142,7 +142,9 @@
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
text-decoration: none;
|
||||
transition: background-color var(--admin-motion), color var(--admin-motion);
|
||||
transition:
|
||||
background-color var(--admin-motion),
|
||||
color var(--admin-motion);
|
||||
}
|
||||
|
||||
[data-admin-root] .admin-nav-link.is-collapsed {
|
||||
@@ -651,18 +653,19 @@
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
[data-admin-root] :is(
|
||||
.admin-feature-command-bar,
|
||||
.admin-appearance-command-bar,
|
||||
.admin-access-command-bar,
|
||||
.admin-email-command-bar,
|
||||
.admin-storage-command-bar,
|
||||
.admin-ark-command-bar,
|
||||
.admin-intercept-command-bar,
|
||||
.admin-drawing-command-bar,
|
||||
.admin-runtime-policy-command-bar,
|
||||
.admin-third-party-command-bar
|
||||
) {
|
||||
[data-admin-root]
|
||||
:is(
|
||||
.admin-feature-command-bar,
|
||||
.admin-appearance-command-bar,
|
||||
.admin-access-command-bar,
|
||||
.admin-email-command-bar,
|
||||
.admin-storage-command-bar,
|
||||
.admin-ark-command-bar,
|
||||
.admin-intercept-command-bar,
|
||||
.admin-drawing-command-bar,
|
||||
.admin-runtime-policy-command-bar,
|
||||
.admin-third-party-command-bar
|
||||
) {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 8;
|
||||
@@ -681,26 +684,24 @@
|
||||
backdrop-filter: none !important;
|
||||
}
|
||||
|
||||
[data-admin-root] :is(
|
||||
.admin-feature-command-bar,
|
||||
.admin-appearance-command-bar,
|
||||
.admin-access-command-bar,
|
||||
.admin-email-command-bar,
|
||||
.admin-storage-command-bar,
|
||||
.admin-ark-command-bar,
|
||||
.admin-intercept-command-bar,
|
||||
.admin-drawing-command-bar,
|
||||
.admin-runtime-policy-command-bar,
|
||||
.admin-third-party-command-bar
|
||||
).is-dirty {
|
||||
[data-admin-root]
|
||||
:is(
|
||||
.admin-feature-command-bar,
|
||||
.admin-appearance-command-bar,
|
||||
.admin-access-command-bar,
|
||||
.admin-email-command-bar,
|
||||
.admin-storage-command-bar,
|
||||
.admin-ark-command-bar,
|
||||
.admin-intercept-command-bar,
|
||||
.admin-drawing-command-bar,
|
||||
.admin-runtime-policy-command-bar,
|
||||
.admin-third-party-command-bar
|
||||
).is-dirty {
|
||||
border-bottom-color: color-mix(in srgb, var(--admin-status-warning) 55%, var(--admin-border)) !important;
|
||||
background: color-mix(in srgb, var(--admin-status-warning) 8%, var(--admin-canvas)) !important;
|
||||
}
|
||||
|
||||
[data-admin-root] :is(
|
||||
.admin-feature-command-copy strong,
|
||||
.admin-appearance-command-copy strong
|
||||
) {
|
||||
[data-admin-root] :is(.admin-feature-command-copy strong, .admin-appearance-command-copy strong) {
|
||||
color: var(--admin-text);
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
|
||||
@@ -2,15 +2,7 @@
|
||||
* Never alias --workspace-*, --user-*, or --skin-* from the product UI.
|
||||
* Light/dark follow the page theme class; product skins are ignored. */
|
||||
|
||||
:is(
|
||||
[data-admin-root],
|
||||
body.admin-overlays,
|
||||
.admin-drawer,
|
||||
.admin-secondary-drawer,
|
||||
.admin-modal-root,
|
||||
.admin-channel-modal-root,
|
||||
.admin-mobile-navigation-menu
|
||||
) {
|
||||
:is([data-admin-root], body.admin-overlays, .admin-drawer, .admin-secondary-drawer, .admin-modal-root, .admin-channel-modal-root, .admin-mobile-navigation-menu) {
|
||||
--admin-font: ui-sans-serif, "SF Pro Text", "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", system-ui, sans-serif;
|
||||
--admin-font-mono: ui-monospace, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
--admin-space: 8px;
|
||||
@@ -100,17 +92,12 @@
|
||||
font-family: var(--admin-font);
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
font-feature-settings: "ss01" 0, "cv11" 0;
|
||||
font-feature-settings:
|
||||
"ss01" 0,
|
||||
"cv11" 0;
|
||||
}
|
||||
|
||||
.dark :is(
|
||||
[data-admin-root],
|
||||
.admin-drawer,
|
||||
.admin-secondary-drawer,
|
||||
.admin-modal-root,
|
||||
.admin-channel-modal-root,
|
||||
.admin-mobile-navigation-menu
|
||||
),
|
||||
.dark :is([data-admin-root], .admin-drawer, .admin-secondary-drawer, .admin-modal-root, .admin-channel-modal-root, .admin-mobile-navigation-menu),
|
||||
.dark body.admin-overlays {
|
||||
--admin-canvas: #0f0f0f;
|
||||
--admin-sidebar: #0f0f0f;
|
||||
|
||||
@@ -22,14 +22,7 @@ export function AdminSwitch({
|
||||
className?: string;
|
||||
"aria-label"?: string;
|
||||
}) {
|
||||
return (
|
||||
<AntSwitch
|
||||
{...props}
|
||||
size={size === "sm" ? "small" : "default"}
|
||||
className={cn("admin-switch", className)}
|
||||
onChange={(checked) => props.onChange?.(checked)}
|
||||
/>
|
||||
);
|
||||
return <AntSwitch {...props} size={size === "sm" ? "small" : "default"} className={cn("admin-switch", className)} onChange={(checked) => props.onChange?.(checked)} />;
|
||||
}
|
||||
|
||||
export function Switch(props: Parameters<typeof AdminSwitch>[0]) {
|
||||
@@ -53,15 +46,7 @@ export function AdminSelect<V extends string = string>({
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<AntSelect
|
||||
{...props}
|
||||
size={size === "sm" ? "small" : "middle"}
|
||||
className={cn("admin-select", className)}
|
||||
aria-label={ariaLabel}
|
||||
onChange={(value) => onChange?.(value as V)}
|
||||
/>
|
||||
);
|
||||
return <AntSelect {...props} size={size === "sm" ? "small" : "middle"} className={cn("admin-select", className)} aria-label={ariaLabel} onChange={(value) => onChange?.(value as V)} />;
|
||||
}
|
||||
|
||||
export function Select<V extends string = string>(props: Parameters<typeof AdminSelect<V>>[0]) {
|
||||
@@ -143,15 +128,7 @@ export function AdminSegmented<V extends string = string>({
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<Segmented
|
||||
className={cn("admin-segmented", className)}
|
||||
value={value}
|
||||
options={options.map((option) => ({ ...option, label: option.label ?? option.value }))}
|
||||
onChange={(next) => onChange?.(String(next) as V)}
|
||||
aria-label={ariaLabel}
|
||||
/>
|
||||
);
|
||||
return <Segmented className={cn("admin-segmented", className)} value={value} options={options.map((option) => ({ ...option, label: option.label ?? option.value }))} onChange={(next) => onChange?.(String(next) as V)} aria-label={ariaLabel} />;
|
||||
}
|
||||
|
||||
export function SegmentedControl<V extends string = string>(props: Parameters<typeof AdminSegmented<V>>[0]) {
|
||||
|
||||
@@ -30,19 +30,13 @@ function mergeFlushStyles(resolved: unknown, flush: boolean, kind: "drawer" | "m
|
||||
}
|
||||
|
||||
export function AdminDrawer({ flush = false, destroyOnHidden = true, styles, className, rootClassName, ...props }: AdminDrawerProps) {
|
||||
const mergedStyles =
|
||||
typeof styles === "function"
|
||||
? (info: { props: DrawerProps }) => mergeFlushStyles(styles(info), flush, "drawer")
|
||||
: mergeFlushStyles(styles, flush, "drawer");
|
||||
const mergedStyles = typeof styles === "function" ? (info: { props: DrawerProps }) => mergeFlushStyles(styles(info), flush, "drawer") : mergeFlushStyles(styles, flush, "drawer");
|
||||
|
||||
return <Drawer destroyOnHidden={destroyOnHidden} className={cn("admin-drawer-panel", className)} rootClassName={cn("admin-drawer", rootClassName)} {...props} styles={mergedStyles as DrawerProps["styles"]} />;
|
||||
}
|
||||
|
||||
export function AdminModal({ flush = false, destroyOnHidden = true, styles, className, rootClassName, ...props }: AdminModalProps) {
|
||||
const mergedStyles =
|
||||
typeof styles === "function"
|
||||
? (info: { props: ModalProps }) => mergeFlushStyles(styles(info), flush, "modal")
|
||||
: mergeFlushStyles(styles, flush, "modal");
|
||||
const mergedStyles = typeof styles === "function" ? (info: { props: ModalProps }) => mergeFlushStyles(styles(info), flush, "modal") : mergeFlushStyles(styles, flush, "modal");
|
||||
|
||||
return <Modal destroyOnHidden={destroyOnHidden} className={cn("admin-modal-panel", className)} rootClassName={cn("admin-modal-root", rootClassName)} {...props} styles={mergedStyles as ModalProps["styles"]} />;
|
||||
}
|
||||
|
||||
@@ -85,11 +85,7 @@ function compactStatusLabel(settings: AgentMemoryCompactView | null) {
|
||||
if (settings.lastStatus === "failed") return settings.lastError || "上次压缩失败";
|
||||
if (settings.lastStatus === "succeeded") {
|
||||
const summary = settings.summary;
|
||||
const parts = [
|
||||
summary?.merged ? `合并 ${summary.merged}` : "",
|
||||
summary?.rewritten ? `改写 ${summary.rewritten}` : "",
|
||||
summary?.removed ? `删除 ${summary.removed}` : "",
|
||||
].filter(Boolean);
|
||||
const parts = [summary?.merged ? `合并 ${summary.merged}` : "", summary?.rewritten ? `改写 ${summary.rewritten}` : "", summary?.removed ? `删除 ${summary.removed}` : ""].filter(Boolean);
|
||||
const when = settings.lastCompactAt ? new Date(settings.lastCompactAt).toLocaleString() : "";
|
||||
return [when && `上次 ${when}`, parts.join(" · ") || "模型认为没有需要改动的条目"].filter(Boolean).join(" · ");
|
||||
}
|
||||
@@ -204,14 +200,7 @@ function AgentMemoryCompactCard({ compact = false, onApplied }: { compact?: bool
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
<SegmentedControl
|
||||
size="sm"
|
||||
ariaLabel="压缩周期"
|
||||
value={settings?.compactInterval || "off"}
|
||||
disabled={saving || busy}
|
||||
options={COMPACT_INTERVALS}
|
||||
onChange={(value) => void persist(value, model)}
|
||||
/>
|
||||
<SegmentedControl size="sm" ariaLabel="压缩周期" value={settings?.compactInterval || "off"} disabled={saving || busy} options={COMPACT_INTERVALS} onChange={(value) => void persist(value, model)} />
|
||||
<div className="min-w-0 overflow-hidden rounded-xl bg-surface-tertiary">
|
||||
<ModelPicker
|
||||
config={config}
|
||||
@@ -231,7 +220,9 @@ function AgentMemoryCompactCard({ compact = false, onApplied }: { compact?: bool
|
||||
</div>
|
||||
</div>
|
||||
{failed && settings?.lastError ? (
|
||||
<Callout tone="warning" title="上次压缩未完成">{settings.lastError}</Callout>
|
||||
<Callout tone="warning" title="上次压缩未完成">
|
||||
{settings.lastError}
|
||||
</Callout>
|
||||
) : statusText ? (
|
||||
<p className="text-caption leading-5 text-muted-foreground">{statusText}</p>
|
||||
) : null}
|
||||
@@ -378,13 +369,7 @@ export default function AgentMemoryPane({ compact = false }: { compact?: boolean
|
||||
return (
|
||||
<div className={compact ? "flex min-h-0 flex-1 flex-col gap-3 overflow-hidden" : "flex flex-col gap-5"}>
|
||||
<div className="flex shrink-0 flex-wrap items-center gap-2">
|
||||
<SegmentedControl
|
||||
size="sm"
|
||||
ariaLabel="记忆状态"
|
||||
value={status}
|
||||
options={statusFilters}
|
||||
onChange={setStatus}
|
||||
/>
|
||||
<SegmentedControl size="sm" ariaLabel="记忆状态" value={status} options={statusFilters} onChange={setStatus} />
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
<IconButton variant="ghost" size="sm" icon={Download} aria-label="导出记忆" onClick={() => void onExport()} />
|
||||
<IconButton variant="ghost" size="sm" icon={Upload} aria-label="导入记忆" onClick={() => fileRef.current?.click()} />
|
||||
@@ -422,7 +407,13 @@ export default function AgentMemoryPane({ compact = false }: { compact?: boolean
|
||||
className={compact ? "min-h-0 flex-1 py-8" : "min-h-[220px] py-10"}
|
||||
title={status === "pending" ? "没有待批准的记忆" : "还没有个人记忆"}
|
||||
description={status === "pending" ? "Agent 跑通任务后会把可复用做法记到这里,等你点头。" : "手动添加立刻生效,也可以等 Agent 记下后再批准。"}
|
||||
action={status === "pending" ? undefined : <Button type="primary" icon={<Plus className="size-4" />} onClick={openCreate}>添加记忆</Button>}
|
||||
action={
|
||||
status === "pending" ? undefined : (
|
||||
<Button type="primary" icon={<Plus className="size-4" />} onClick={openCreate}>
|
||||
添加记忆
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
{!loading && items.length ? (
|
||||
@@ -478,14 +469,7 @@ export default function AgentMemoryPane({ compact = false }: { compact?: boolean
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<AppModal
|
||||
open={editorOpen}
|
||||
title={editing ? "编辑记忆" : "添加记忆"}
|
||||
okText={editing ? "保存" : "添加"}
|
||||
confirmLoading={saving}
|
||||
onOk={() => void save()}
|
||||
onCancel={() => setEditorOpen(false)}
|
||||
>
|
||||
<AppModal open={editorOpen} title={editing ? "编辑记忆" : "添加记忆"} okText={editing ? "保存" : "添加"} confirmLoading={saving} onOk={() => void save()} onCancel={() => setEditorOpen(false)}>
|
||||
<Form form={form} layout="vertical" className="pt-2">
|
||||
<Form.Item name="topic" label="主题" rules={[{ required: true, message: "请填写主题" }]}>
|
||||
<Input maxLength={120} placeholder="例如 canvas.snapshot-hash" />
|
||||
|
||||
@@ -49,29 +49,18 @@ describe("pendingAgentQuestion", () => {
|
||||
const question = { question: "选哪个模型?", options: [{ label: "H3文生视频" }, { label: "Seedance 2.5" }] };
|
||||
|
||||
test("有未作答的提问 → 亮面板", () => {
|
||||
const messages = [
|
||||
{ role: "user" },
|
||||
{ role: "assistant", question },
|
||||
];
|
||||
const messages = [{ role: "user" }, { role: "assistant", question }];
|
||||
expect(pendingAgentQuestion(messages)?.question).toBe("选哪个模型?");
|
||||
});
|
||||
|
||||
test("提问之后用户已回复 → 收起面板", () => {
|
||||
const messages = [
|
||||
{ role: "assistant", question },
|
||||
{ role: "user" },
|
||||
];
|
||||
const messages = [{ role: "assistant", question }, { role: "user" }];
|
||||
expect(pendingAgentQuestion(messages)).toBeUndefined();
|
||||
});
|
||||
|
||||
test("多轮提问取最新那道;旧的那道不算数", () => {
|
||||
const later = { question: "确认开始吗?", options: [{ label: "开始" }, { label: "再改改" }] };
|
||||
const messages = [
|
||||
{ role: "assistant", question },
|
||||
{ role: "user" },
|
||||
{ role: "assistant", question },
|
||||
{ role: "assistant", question: later },
|
||||
];
|
||||
const messages = [{ role: "assistant", question }, { role: "user" }, { role: "assistant", question }, { role: "assistant", question: later }];
|
||||
expect(pendingAgentQuestion(messages)?.question).toBe("确认开始吗?");
|
||||
});
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ describe("creation library button", () => {
|
||||
const source = readCreateWorkspaceSource();
|
||||
const styles = readFileSync(resolve(import.meta.dir, "../src/styles/globals.css"), "utf8");
|
||||
|
||||
expect(source).toContain("import { Reorder, LayoutGroup, motion, useReducedMotion } from \"motion/react\"");
|
||||
expect(source).toContain('import { Reorder, LayoutGroup, motion, useReducedMotion } from "motion/react"');
|
||||
expect(source).toContain("<Reorder.Group");
|
||||
expect(source).toContain('axis="x"');
|
||||
expect(source).toContain("values={visibleAttachments}");
|
||||
|
||||
Reference in New Issue
Block a user