Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9030aac03d | ||
|
|
1b43c53806 |
No files matched your search
@@ -266,93 +266,9 @@ export function FirstImportReview({
|
||||
step={2}
|
||||
title={text(title)}
|
||||
footer={
|
||||
<div className="first-import-r6-footer-actions">
|
||||
{changing ? (
|
||||
<R6Button
|
||||
onClick={() => {
|
||||
stopBatch.current = true;
|
||||
}}
|
||||
>
|
||||
{text("停止后续操作")}
|
||||
</R6Button>
|
||||
) : onChangeJob && (canPause || canResume) ? (
|
||||
<R6Button
|
||||
disabled={busy || (stale && !canPause)}
|
||||
onClick={() => void changeBatch(canPause ? "pause" : "resume")}
|
||||
>
|
||||
{canPause ? (
|
||||
<Pause size={16} aria-hidden="true" />
|
||||
) : (
|
||||
<Play size={16} aria-hidden="true" />
|
||||
)}
|
||||
{text(canPause ? "暂停导入" : "继续导入")}
|
||||
</R6Button>
|
||||
) : null}
|
||||
{recovery ? (
|
||||
<>
|
||||
<R6Button
|
||||
disabled={busy}
|
||||
onClick={() => {
|
||||
stopBatch.current = true;
|
||||
recovery.onSkip();
|
||||
}}
|
||||
>
|
||||
{text(
|
||||
recovery.destination === "sources"
|
||||
? "仍进入应用页"
|
||||
: recovery.destination === "settings"
|
||||
? "仍进入设置"
|
||||
: "仍进入工作台",
|
||||
)}
|
||||
</R6Button>
|
||||
<R6Button
|
||||
variant="primary"
|
||||
disabled={busy}
|
||||
onClick={() => leave(recovery.destination)}
|
||||
>
|
||||
{text("重试保存并进入")}
|
||||
</R6Button>
|
||||
</>
|
||||
) : (
|
||||
<R6Button
|
||||
variant="primary"
|
||||
disabled={busy}
|
||||
onClick={() => leave("settings")}
|
||||
>
|
||||
{text("选择记忆模式")}
|
||||
</R6Button>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="first-import-r6-layout">
|
||||
<section
|
||||
className="first-import-r6-main"
|
||||
aria-label={text("导入与原文")}
|
||||
>
|
||||
<div className="first-import-r6-intro">
|
||||
<h2>{text("正在导入你的会话")}</h2>
|
||||
{importSubmissionState === "checking" ? (
|
||||
<p className="first-import-r6-submission-status" role="status">
|
||||
{t("onboardingImportChecking")}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
{text(
|
||||
allCompleted
|
||||
? "本次导入已结束。已保存的对话无需重复导入。"
|
||||
: "后台将持续为你导入,可以放心进入下一步",
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
{ids.length > 3 && (
|
||||
<p className="first-import-r6-scroll-hint">
|
||||
{t("onboardingMoreImportApps", { count: ids.length - 3 })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{(error || actionMessage) && (
|
||||
<div className="first-import-r6-feedback">
|
||||
<>
|
||||
<div className="first-import-r6-footer-note">
|
||||
{(error || actionMessage) && (
|
||||
<p
|
||||
role={
|
||||
error?.includes("应用连接") ||
|
||||
@@ -371,8 +287,87 @@ export function FirstImportReview({
|
||||
>
|
||||
{error ? text(error) : actionMessage}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
<div className="first-import-r6-footer-actions">
|
||||
{changing ? (
|
||||
<R6Button
|
||||
onClick={() => {
|
||||
stopBatch.current = true;
|
||||
}}
|
||||
>
|
||||
{text("停止后续操作")}
|
||||
</R6Button>
|
||||
) : onChangeJob && (canPause || canResume) ? (
|
||||
<R6Button
|
||||
disabled={busy || (stale && !canPause)}
|
||||
onClick={() => void changeBatch(canPause ? "pause" : "resume")}
|
||||
>
|
||||
{canPause ? (
|
||||
<Pause size={16} aria-hidden="true" />
|
||||
) : (
|
||||
<Play size={16} aria-hidden="true" />
|
||||
)}
|
||||
{text(canPause ? "暂停导入" : "继续导入")}
|
||||
</R6Button>
|
||||
) : null}
|
||||
{recovery ? (
|
||||
<>
|
||||
<R6Button
|
||||
disabled={busy}
|
||||
onClick={() => {
|
||||
stopBatch.current = true;
|
||||
recovery.onSkip();
|
||||
}}
|
||||
>
|
||||
{text(
|
||||
recovery.destination === "sources"
|
||||
? "仍进入应用页"
|
||||
: recovery.destination === "settings"
|
||||
? "仍进入设置"
|
||||
: "仍进入工作台",
|
||||
)}
|
||||
</R6Button>
|
||||
<R6Button
|
||||
variant="primary"
|
||||
disabled={busy}
|
||||
onClick={() => leave(recovery.destination)}
|
||||
>
|
||||
{text("重试保存并进入")}
|
||||
</R6Button>
|
||||
</>
|
||||
) : (
|
||||
<R6Button
|
||||
variant="primary"
|
||||
disabled={busy}
|
||||
onClick={() => leave("settings")}
|
||||
>
|
||||
{text("选择记忆模式")}
|
||||
</R6Button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="first-import-r6-layout">
|
||||
<section
|
||||
className="first-import-r6-main"
|
||||
aria-label={text("导入与原文")}
|
||||
>
|
||||
<p className="first-import-r6-intro">
|
||||
{importSubmissionState === "checking"
|
||||
? t("onboardingImportChecking")
|
||||
: text(
|
||||
allCompleted
|
||||
? "本次导入已结束。已保存的对话无需重复导入。"
|
||||
: "对话先保存在本机。你可以同时设置后续连接。",
|
||||
)}
|
||||
</p>
|
||||
<p className="first-import-r6-subintro">
|
||||
{text(
|
||||
"导入不会自动开放应用读取权限。配置模型后,才会开始生成记忆。",
|
||||
)}
|
||||
</p>
|
||||
{historyWindowLimited && (
|
||||
<p className="first-import-r6-history-window" role="status">
|
||||
{t("onboardingHistoryWindow")}
|
||||
@@ -384,39 +379,46 @@ export function FirstImportReview({
|
||||
jobs={batch}
|
||||
stale={progressStale}
|
||||
/>
|
||||
{Object.keys(failedSources).length > 0 && (
|
||||
<section
|
||||
className="first-import-r6-failures"
|
||||
aria-label={text("未开始的导入")}
|
||||
>
|
||||
{Object.keys(failedSources).map((id) => (
|
||||
<p key={id}>
|
||||
{sources.find((source) => source.source_id === id)
|
||||
?.display_name ?? id}{" "}
|
||||
{text("未能开始导入,其他已接受任务不受影响。")}
|
||||
</p>
|
||||
))}
|
||||
<R6Button
|
||||
variant="quiet"
|
||||
disabled={busy}
|
||||
onClick={() => leave("sources")}
|
||||
<section
|
||||
className="first-import-r6-feed"
|
||||
aria-label={text("导入说明")}
|
||||
>
|
||||
<h2>{text("正在导入你的会话")}</h2>
|
||||
<p className="first-import-r6-feed-status">
|
||||
{text("后台将持续为你导入,可以放心进入下一步")}
|
||||
</p>
|
||||
{Object.keys(failedSources).length > 0 && (
|
||||
<section
|
||||
className="first-import-r6-failures"
|
||||
aria-label={text("未开始的导入")}
|
||||
>
|
||||
{text("查看未导入的应用")}
|
||||
</R6Button>
|
||||
</section>
|
||||
)}
|
||||
{Object.keys(failedSources).map((id) => (
|
||||
<p key={id}>
|
||||
{sources.find((source) => source.source_id === id)
|
||||
?.display_name ?? id}{" "}
|
||||
{text("未能开始导入,其他已接受任务不受影响。")}
|
||||
</p>
|
||||
))}
|
||||
<R6Button
|
||||
variant="quiet"
|
||||
disabled={busy}
|
||||
onClick={() => leave("sources")}
|
||||
>
|
||||
{text("查看未导入的应用")}
|
||||
</R6Button>
|
||||
</section>
|
||||
)}
|
||||
</section>
|
||||
</section>
|
||||
<aside
|
||||
className="first-import-r6-options"
|
||||
aria-label={text("连接应用")}
|
||||
>
|
||||
<div className="first-import-r6-options-head">
|
||||
<p className="first-import-r6-eyebrow">{text("让记忆继续生长")}</p>
|
||||
<h2>{text("连接你的应用")}</h2>
|
||||
<p className="first-import-r6-options-intro">
|
||||
{text("适用于本次选择且支持的应用。")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="first-import-r6-eyebrow">{text("让记忆继续生长")}</p>
|
||||
<h2>{text("连接你的应用")}</h2>
|
||||
<p className="first-import-r6-options-intro">
|
||||
{text("适用于本次选择且支持的应用。")}
|
||||
</p>
|
||||
<SelectionTile
|
||||
title={text(
|
||||
hostConnectionAvailable ? "一键接入应用" : "测试实例不接入应用",
|
||||
|
||||
@@ -33,6 +33,7 @@ export function MemoryModeCards({
|
||||
currentBadge,
|
||||
disabled = false,
|
||||
layout = "grid",
|
||||
describedBy,
|
||||
onChange,
|
||||
ultraDetail,
|
||||
"aria-label": ariaLabel,
|
||||
@@ -43,6 +44,7 @@ export function MemoryModeCards({
|
||||
currentBadge?: ReactNode;
|
||||
disabled?: boolean | ((mode: MemoryPipelineMode) => boolean);
|
||||
layout?: "grid" | "rows";
|
||||
describedBy?: Partial<Record<MemoryPipelineMode, string | undefined>>;
|
||||
onChange(mode: MemoryPipelineMode, control: HTMLInputElement): void;
|
||||
ultraDetail?: ReactNode;
|
||||
"aria-label": string;
|
||||
@@ -87,6 +89,7 @@ export function MemoryModeCards({
|
||||
name={name}
|
||||
value={mode.id}
|
||||
aria-label={localizedTitle}
|
||||
aria-describedby={describedBy?.[mode.id]}
|
||||
checked={selected}
|
||||
disabled={modeDisabled}
|
||||
onChange={(event) => onChange(mode.id, event.currentTarget)}
|
||||
|
||||
@@ -156,7 +156,7 @@ it("moves from import to the actual third step without completing onboarding", a
|
||||
);
|
||||
await userEvent.click(screen.getByRole("button", { name: "进入第三步" }));
|
||||
await userEvent.click(screen.getByRole("radio", { name: /pro/i }));
|
||||
await userEvent.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await userEvent.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
expect(screen.getByRole("heading", { name: "第三步模型页面" })).toBeTruthy();
|
||||
expect(completeOnboarding).not.toHaveBeenCalled();
|
||||
expect(finish).not.toHaveBeenCalled();
|
||||
|
||||
@@ -240,7 +240,7 @@ async function continueImport(user: ReturnType<typeof userEvent.setup>) {
|
||||
async function openModelStep(user: ReturnType<typeof userEvent.setup>) {
|
||||
await user.click(screen.getByRole("button", { name: "选择记忆模式" }));
|
||||
await selectMode(user, /pro/i);
|
||||
await user.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
}
|
||||
async function chooseLiteAndContinue(user: ReturnType<typeof userEvent.setup>) {
|
||||
await selectMode(user, /lite/i);
|
||||
@@ -688,7 +688,7 @@ describe("OnboardingFlow S1.3", () => {
|
||||
await openApps();
|
||||
await user.click(screen.getByRole("button", { name: SKIP }));
|
||||
await selectMode(user, /pro/i);
|
||||
await user.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
const receipt = screen.getByRole("button", {
|
||||
name: "模型启用回执已确认",
|
||||
});
|
||||
@@ -1031,6 +1031,12 @@ describe("OnboardingFlow S1.3", () => {
|
||||
await user.click(screen.getByRole("button", { name: SKIP }));
|
||||
|
||||
expect(screen.getByRole("heading", { name: "选择记忆模式" })).toBeTruthy();
|
||||
expect(
|
||||
screen.getByRole("heading", {
|
||||
name: "你希望 MemHub 如何形成记忆?",
|
||||
}),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByText("Choose how MemHub remembers")).toBeNull();
|
||||
expect(screen.getByText("之后可在设置中更改。")).toBeTruthy();
|
||||
expect(screen.getAllByRole("radio")).toHaveLength(3);
|
||||
expect(screen.getByRole("radio", { name: /ultra/i })).toBeTruthy();
|
||||
@@ -1043,7 +1049,7 @@ describe("OnboardingFlow S1.3", () => {
|
||||
await selectMode(user, /lite/i);
|
||||
expect(screen.getByRole("button", { name: "使用 Lite 进入" })).toBeTruthy();
|
||||
await selectMode(user, /pro/i);
|
||||
expect(screen.getByRole("button", { name: "继续配置模型" })).toBeTruthy();
|
||||
expect(screen.getByRole("button", { name: "配置模型" })).toBeTruthy();
|
||||
});
|
||||
|
||||
it("configures Ultra scheduling before model setup and completes after activation", async () => {
|
||||
@@ -1083,10 +1089,18 @@ describe("OnboardingFlow S1.3", () => {
|
||||
|
||||
await openApps();
|
||||
await user.click(screen.getByRole("button", { name: SKIP }));
|
||||
await user.click(screen.getByRole("radio", { name: /ultra/i }));
|
||||
const ultra = screen.getByRole("radio", { name: /ultra/i });
|
||||
await user.click(ultra);
|
||||
const schedule = screen.getByRole("complementary", {
|
||||
name: "每天自动整合",
|
||||
});
|
||||
expect(schedule.closest(".memory-mode-card")).toBeNull();
|
||||
expect(ultra.getAttribute("aria-describedby")).toBe(
|
||||
"onboarding-ultra-schedule-description",
|
||||
);
|
||||
expect(screen.getByText("每天自动整合")).toBeTruthy();
|
||||
expect(screen.getByText(/每天 03:00 左右自动整合/)).toBeTruthy();
|
||||
await user.click(screen.getByRole("button", { name: "继续" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
expect(screen.getByRole("region", { name: "模型步骤边界" })).toBeTruthy();
|
||||
expect(putOnboardingProgress).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -1149,14 +1163,14 @@ describe("OnboardingFlow S1.3", () => {
|
||||
}),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole("button", { name: "继续" })).toHaveProperty(
|
||||
expect(screen.getByRole("button", { name: "配置模型" })).toHaveProperty(
|
||||
"disabled",
|
||||
false,
|
||||
),
|
||||
);
|
||||
expect(screen.getByText(/重新开启后仍按 05:30 运行/)).toBeTruthy();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "继续" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
await waitFor(() =>
|
||||
expect(finish).toHaveBeenCalledExactlyOnceWith("sources"),
|
||||
);
|
||||
@@ -1190,7 +1204,7 @@ describe("OnboardingFlow S1.3", () => {
|
||||
await openApps();
|
||||
await user.click(screen.getByRole("button", { name: SKIP }));
|
||||
await selectMode(user, /ultra/i);
|
||||
await user.click(screen.getByRole("button", { name: "继续" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
expect((await screen.findByRole("alert")).textContent).toContain(
|
||||
"自动整合设置未能保存",
|
||||
);
|
||||
@@ -1199,7 +1213,7 @@ describe("OnboardingFlow S1.3", () => {
|
||||
screen.getByRole<HTMLInputElement>("radio", { name: /ultra/i }).checked,
|
||||
).toBe(true);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "继续" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
await waitFor(() =>
|
||||
expect(finish).toHaveBeenCalledExactlyOnceWith("sources"),
|
||||
);
|
||||
@@ -1207,9 +1221,14 @@ describe("OnboardingFlow S1.3", () => {
|
||||
});
|
||||
|
||||
it("retries a failed Ultra schedule read without losing the selection", async () => {
|
||||
let resolveRetry!: (value: { enabled: boolean; time: string }) => void;
|
||||
vi.mocked(api.getEvolveSchedule)
|
||||
.mockRejectedValueOnce(new Error("offline"))
|
||||
.mockResolvedValueOnce({ enabled: true, time: "04:15" });
|
||||
.mockReturnValueOnce(
|
||||
new Promise((resolve) => {
|
||||
resolveRetry = resolve;
|
||||
}),
|
||||
);
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<OnboardingFlow
|
||||
@@ -1225,19 +1244,28 @@ describe("OnboardingFlow S1.3", () => {
|
||||
expect(screen.getByRole("alert").textContent).toContain(
|
||||
"记忆模式状态暂时无法读取",
|
||||
);
|
||||
expect(screen.getByRole("button", { name: "继续" })).toHaveProperty(
|
||||
expect(screen.getByRole("button", { name: "配置模型" })).toHaveProperty(
|
||||
"disabled",
|
||||
true,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "重试" }));
|
||||
const retry = screen.getByRole("button", { name: "重试" });
|
||||
retry.focus();
|
||||
await user.click(retry);
|
||||
expect(screen.getByRole("button", { name: "重试" })).toHaveProperty(
|
||||
"disabled",
|
||||
true,
|
||||
);
|
||||
expect(document.activeElement).toBe(retry);
|
||||
expect(api.getEvolveSchedule).toHaveBeenCalledTimes(2);
|
||||
await act(async () => resolveRetry({ enabled: true, time: "04:15" }));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText(/每天 04:15 左右自动整合/)).toBeTruthy(),
|
||||
);
|
||||
expect(
|
||||
screen.getByRole<HTMLInputElement>("radio", { name: /ultra/i }).checked,
|
||||
).toBe(true);
|
||||
expect(screen.getByRole("button", { name: "继续" })).toHaveProperty(
|
||||
expect(screen.getByRole("button", { name: "配置模型" })).toHaveProperty(
|
||||
"disabled",
|
||||
false,
|
||||
);
|
||||
@@ -1430,7 +1458,7 @@ describe("OnboardingFlow S1.3", () => {
|
||||
await openApps();
|
||||
await user.click(screen.getByRole("button", { name: SKIP }));
|
||||
await selectMode(user, /pro/i);
|
||||
await user.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "模型启用回执已确认" }),
|
||||
);
|
||||
@@ -1484,7 +1512,7 @@ describe("OnboardingFlow S1.3", () => {
|
||||
await openApps();
|
||||
await user.click(screen.getByRole("button", { name: SKIP }));
|
||||
await selectMode(user, /pro/i);
|
||||
await user.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "模型启用回执已确认" }),
|
||||
);
|
||||
@@ -1492,7 +1520,7 @@ describe("OnboardingFlow S1.3", () => {
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "返回导入" }));
|
||||
modelActive = false;
|
||||
await user.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "模型启用回执已确认" }),
|
||||
);
|
||||
@@ -1538,7 +1566,7 @@ describe("OnboardingFlow S1.3", () => {
|
||||
await openApps();
|
||||
await user.click(screen.getByRole("button", { name: SKIP }));
|
||||
await selectMode(user, /pro/i);
|
||||
await user.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "模型启用回执已确认" }),
|
||||
);
|
||||
@@ -1580,7 +1608,7 @@ describe("OnboardingFlow S1.3", () => {
|
||||
await openApps();
|
||||
await user.click(screen.getByRole("button", { name: SKIP }));
|
||||
await selectMode(user, /pro/i);
|
||||
await user.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
const revisionBeforePut = confirmedMemoryModeSnapshot().revision;
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "模型启用回执已确认" }),
|
||||
@@ -1629,7 +1657,7 @@ describe("OnboardingFlow S1.3", () => {
|
||||
await openApps();
|
||||
await user.click(screen.getByRole("button", { name: SKIP }));
|
||||
await selectMode(user, /pro/i);
|
||||
await user.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "模型启用回执已确认" }),
|
||||
);
|
||||
@@ -1669,7 +1697,7 @@ describe("OnboardingFlow S1.3", () => {
|
||||
await waitFor(() => expect(document.activeElement).toBe(firstTitle));
|
||||
|
||||
await selectMode(user, /pro/i);
|
||||
await user.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "返回导入" }));
|
||||
const returnedTitle = screen.getByRole("heading", {
|
||||
name: "选择记忆模式",
|
||||
@@ -1699,7 +1727,7 @@ describe("OnboardingFlow S1.3", () => {
|
||||
await openApps();
|
||||
await user.click(screen.getByRole("button", { name: SKIP }));
|
||||
await selectMode(user, /pro/i);
|
||||
await user.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
await waitFor(() => expect(finish).toHaveBeenCalledWith("sources"));
|
||||
expect(screen.queryByRole("alert")).toBeNull();
|
||||
expect(setMemoryMode).not.toHaveBeenCalled();
|
||||
@@ -1718,7 +1746,7 @@ describe("OnboardingFlow S1.3", () => {
|
||||
await openApps();
|
||||
await user.click(screen.getByRole("button", { name: SKIP }));
|
||||
await selectMode(user, /pro/i);
|
||||
await user.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
const lateReceipt = screen.getByRole("button", {
|
||||
name: "模型启用回执已确认",
|
||||
});
|
||||
@@ -1767,14 +1795,14 @@ describe("OnboardingFlow S1.3", () => {
|
||||
await user.click(screen.getByRole("button", { name: SKIP }));
|
||||
const pro = screen.getByRole<HTMLInputElement>("radio", { name: /pro/i });
|
||||
await selectMode(user, /pro/i);
|
||||
await user.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
expect((await screen.findByRole("alert")).textContent).toContain(
|
||||
"模式未能保存",
|
||||
);
|
||||
expect(pro.checked).toBe(true);
|
||||
expect(screen.queryByRole("region", { name: "模型步骤边界" })).toBeNull();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "继续配置模型" }));
|
||||
await user.click(screen.getByRole("button", { name: "配置模型" }));
|
||||
expect(
|
||||
await screen.findByRole("region", { name: "模型步骤边界" }),
|
||||
).toBeTruthy();
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
SourceResponse,
|
||||
} from "@one-memory/contracts";
|
||||
import { legacyMemoryModePlan } from "@one-memory/operations";
|
||||
import { Check, X } from "lucide-react";
|
||||
import { Check, Clock3, X } from "lucide-react";
|
||||
import {
|
||||
type CSSProperties,
|
||||
type ReactNode,
|
||||
@@ -112,7 +112,7 @@ export function OnboardingFlow({
|
||||
imports: ImportSourcesController;
|
||||
onFinish(destination: OnboardingDestination): void;
|
||||
}) {
|
||||
const { text, t, locale } = useOnboardingTranslation();
|
||||
const { text, t } = useOnboardingTranslation();
|
||||
const [sources, setSources] = useState(imports.sources);
|
||||
const sourceGeneration = useRef({ value: imports.sources, epoch: 0 });
|
||||
if (sourceGeneration.current.value !== imports.sources) {
|
||||
@@ -165,6 +165,7 @@ export function OnboardingFlow({
|
||||
});
|
||||
const [scheduleLoaded, setScheduleLoaded] = useState(false);
|
||||
const [scheduleReadFailed, setScheduleReadFailed] = useState(false);
|
||||
const [scheduleLoading, setScheduleLoading] = useState(true);
|
||||
const scheduleReadOperation = useRef(0);
|
||||
const [modeFocusToken, setModeFocusToken] = useState(
|
||||
resumedStep === "mode" ? 1 : 0,
|
||||
@@ -430,7 +431,7 @@ export function OnboardingFlow({
|
||||
const refreshUltraSchedule = useCallback(async () => {
|
||||
const operation = ++scheduleReadOperation.current;
|
||||
setScheduleLoaded(false);
|
||||
setScheduleReadFailed(false);
|
||||
setScheduleLoading(true);
|
||||
try {
|
||||
const schedule = await getEvolveSchedule();
|
||||
if (!alive.current || scheduleReadOperation.current !== operation) return;
|
||||
@@ -446,6 +447,9 @@ export function OnboardingFlow({
|
||||
setScheduleLoaded(false);
|
||||
setScheduleReadFailed(true);
|
||||
}
|
||||
} finally {
|
||||
if (alive.current && scheduleReadOperation.current === operation)
|
||||
setScheduleLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -1260,11 +1264,11 @@ export function OnboardingFlow({
|
||||
? "正在保存模式…"
|
||||
: modePhase === "completing"
|
||||
? "正在完成首次设置…"
|
||||
: modeChoice === "lite"
|
||||
? "使用 Lite 进入"
|
||||
: modeChoice === "pro"
|
||||
? "继续配置模型"
|
||||
: "继续",
|
||||
: !modeChoice
|
||||
? "继续"
|
||||
: modeChoice === "lite"
|
||||
? "使用 Lite 进入"
|
||||
: "配置模型",
|
||||
)}
|
||||
</R6Button>
|
||||
}
|
||||
@@ -1274,17 +1278,19 @@ export function OnboardingFlow({
|
||||
aria-label={text("记忆模式")}
|
||||
>
|
||||
<div className="onboarding-r6-mode-intro">
|
||||
<h2>
|
||||
{locale === "zh-Hans"
|
||||
? "Choose how MemHub remembers"
|
||||
: text("你希望 MemHub 如何形成记忆?")}
|
||||
</h2>
|
||||
<h2>{text("你希望 MemHub 如何形成记忆?")}</h2>
|
||||
<p>{text("需要明确选择一种方式;之后仍可在设置中更改。")}</p>
|
||||
</div>
|
||||
<MemoryModeCards
|
||||
aria-label={text("记忆模式")}
|
||||
value={modeChoice}
|
||||
layout="rows"
|
||||
describedBy={{
|
||||
ultra:
|
||||
modeChoice === "ultra"
|
||||
? "onboarding-ultra-schedule-description"
|
||||
: undefined,
|
||||
}}
|
||||
disabled={modeSaving || completionBusy}
|
||||
onChange={(mode) => {
|
||||
modeChoiceTouched.current = true;
|
||||
@@ -1292,11 +1298,19 @@ export function OnboardingFlow({
|
||||
setModeChoice(mode);
|
||||
setModeError(null);
|
||||
}}
|
||||
ultraDetail={
|
||||
scheduleLoaded ? (
|
||||
<div className="onboarding-r6-ultra-default">
|
||||
/>
|
||||
{modeChoice === "ultra" && (
|
||||
<aside
|
||||
id="onboarding-ultra-schedule"
|
||||
className="onboarding-r6-ultra-status"
|
||||
aria-label={text("每天自动整合")}
|
||||
aria-busy={scheduleLoading || undefined}
|
||||
>
|
||||
<Clock3 size={18} aria-hidden="true" />
|
||||
{scheduleLoaded ? (
|
||||
<div className="onboarding-r6-ultra-default" role="status">
|
||||
<strong>{text("每天自动整合")}</strong>
|
||||
<small>
|
||||
<small id="onboarding-ultra-schedule-description">
|
||||
{text(
|
||||
ultraSchedule.enabled
|
||||
? "每天 {time} 左右自动整合,使用这台设备的本地时间;休眠错过后会补跑。"
|
||||
@@ -1306,21 +1320,32 @@ export function OnboardingFlow({
|
||||
</div>
|
||||
) : scheduleReadFailed ? (
|
||||
<div className="onboarding-r6-schedule-recovery">
|
||||
<p role="alert">
|
||||
<p
|
||||
id="onboarding-ultra-schedule-description"
|
||||
role="alert"
|
||||
>
|
||||
{text("记忆模式状态暂时无法读取,请重试。")}
|
||||
</p>
|
||||
<R6Button
|
||||
className="onboarding-r6-schedule-retry"
|
||||
pending={scheduleLoading}
|
||||
disabled={scheduleLoading}
|
||||
onClick={() => void refreshUltraSchedule()}
|
||||
>
|
||||
{text("重试")}
|
||||
</R6Button>
|
||||
</div>
|
||||
) : (
|
||||
<p>{text("正在读取自动整合设置…")}</p>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<p
|
||||
id="onboarding-ultra-schedule-description"
|
||||
className="onboarding-r6-schedule-loading"
|
||||
role="status"
|
||||
>
|
||||
{text("正在读取自动整合设置…")}
|
||||
</p>
|
||||
)}
|
||||
</aside>
|
||||
)}
|
||||
{(modeError || error) && (
|
||||
<p className="onboarding-r6-mode-error" role="alert">
|
||||
{text(modeError ?? error ?? "")}
|
||||
|
||||
@@ -82,7 +82,7 @@ it("does not reserve an empty eyebrow row", () => {
|
||||
expect(screen.queryByText("你的记忆,从这里接上")).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps progress cards content-sized and the step-two footer in normal flow", () => {
|
||||
it("keeps the v1.0.4 step-two split layout and progress geometry", () => {
|
||||
const card = declaration(
|
||||
"r6-import-progress.css",
|
||||
".r6-import-progress-card",
|
||||
@@ -94,13 +94,12 @@ it("keeps progress cards content-sized and the step-two footer in normal flow",
|
||||
"first-import-r6.css",
|
||||
".first-import-r6-main > .r6-import-progress",
|
||||
),
|
||||
).toMatch(/flex:\s*0 0 auto/);
|
||||
expect(declaration("first-import-r6.css", ".first-import-r6-layout")).toMatch(
|
||||
/height:\s*100%/,
|
||||
).toMatch(/flex:\s*0 0 218px/);
|
||||
const layout = declaration("first-import-r6.css", ".first-import-r6-layout");
|
||||
expect(layout).toMatch(
|
||||
/grid-template-columns:\s*minmax\(0,\s*738px\)\s+278px/,
|
||||
);
|
||||
expect(
|
||||
declaration("first-import-r6.css", ".first-import-r6-feedback"),
|
||||
).not.toMatch(/max-height|overflow/);
|
||||
expect(layout).toMatch(/height:\s*560\.2px/);
|
||||
expect(declaration("onboarding-r6.css", ".onboarding-r6-body")).toMatch(
|
||||
/overflow-y:\s*auto/,
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* 143:2419/2502/2594/2685: only step2; shared frame/progress are owned elsewhere. */
|
||||
.first-import-r6-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 738px) 278px;
|
||||
gap: 30px;
|
||||
height: 560.2px;
|
||||
min-height: 0;
|
||||
}
|
||||
.first-import-r6-main {
|
||||
min-width: 0;
|
||||
@@ -13,24 +13,19 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
.first-import-r6-intro {
|
||||
position: relative;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.first-import-r6-intro h2 {
|
||||
margin: 0 0 3px;
|
||||
color: #262a2e;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 26px;
|
||||
}
|
||||
.first-import-r6-intro p {
|
||||
margin: 0;
|
||||
color: #5a6169;
|
||||
font-size: 13px;
|
||||
line-height: 25px;
|
||||
}
|
||||
.first-import-r6-subintro {
|
||||
margin: 1px 0 20px;
|
||||
color: #63707b;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.first-import-r6-history-window {
|
||||
margin: 0 0 12px;
|
||||
margin: -10px 0 12px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #ead7a7;
|
||||
border-radius: 8px;
|
||||
@@ -40,114 +35,13 @@
|
||||
line-height: 20px;
|
||||
}
|
||||
.first-import-r6-main > .r6-import-progress {
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
margin-inline: auto;
|
||||
}
|
||||
.first-import-r6-main > .r6-import-progress[data-count="1"] {
|
||||
grid-auto-columns: min(320px, 100%);
|
||||
justify-content: center;
|
||||
}
|
||||
.first-import-r6-main > .r6-import-progress[data-count="2"] {
|
||||
grid-auto-columns: min(320px, calc((100% - 16px) / 2));
|
||||
justify-content: center;
|
||||
}
|
||||
.first-import-r6-main > .r6-import-progress[data-count="3"] {
|
||||
grid-auto-columns: calc((100% - 32px) / 3);
|
||||
justify-content: center;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.first-import-r6-main > .r6-import-progress[data-count="many"] {
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: calc((100% - 32px) / 3);
|
||||
justify-content: start;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scroll-snap-type: x mandatory;
|
||||
scrollbar-color: rgb(102 128 145 / 50%) transparent;
|
||||
}
|
||||
.first-import-r6-main > .r6-import-progress:focus-visible {
|
||||
outline: 2px solid #557c93;
|
||||
outline-offset: 3px;
|
||||
}
|
||||
.first-import-r6-main > .r6-import-progress .r6-import-progress-card {
|
||||
min-height: 128px;
|
||||
padding: 16px 20px;
|
||||
display: grid;
|
||||
grid-template-columns: 92px minmax(0, 1fr);
|
||||
grid-template-rows: auto auto auto;
|
||||
align-content: center;
|
||||
column-gap: 18px;
|
||||
text-align: left;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
.first-import-r6-main > .r6-import-progress .r6-import-progress-orbit {
|
||||
width: 86px;
|
||||
height: 86px;
|
||||
grid-column: 1;
|
||||
grid-row: 1 / 4;
|
||||
margin: 0;
|
||||
}
|
||||
.first-import-r6-main > .r6-import-progress .r6-import-progress-icon {
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.first-import-r6-main > .r6-import-progress :is(h2, p, small) {
|
||||
grid-column: 2;
|
||||
}
|
||||
.first-import-r6-main > .r6-import-progress .r6-import-progress-card h2 {
|
||||
align-self: end;
|
||||
}
|
||||
.first-import-r6-intro .first-import-r6-submission-status {
|
||||
min-height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
color: #41697d;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.first-import-r6-submission-status::before {
|
||||
content: "";
|
||||
flex: 0 0 auto;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border: 1.5px solid rgb(88 124 144 / 28%);
|
||||
border-top-color: #587c90;
|
||||
border-radius: 50%;
|
||||
animation: first-import-r6-checking 1.2s linear infinite;
|
||||
}
|
||||
.first-import-r6-intro:has(.first-import-r6-submission-status) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.first-import-r6-intro:has(.first-import-r6-submission-status) h2 {
|
||||
flex: 0 0 auto;
|
||||
margin: 0;
|
||||
}
|
||||
.first-import-r6-intro .first-import-r6-scroll-hint {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
color: #6b7e8b;
|
||||
font-size: 11px;
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.first-import-r6-intro:has(.first-import-r6-scroll-hint) {
|
||||
padding-right: 190px;
|
||||
}
|
||||
@keyframes first-import-r6-checking {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
flex: 0 0 218px;
|
||||
}
|
||||
.first-import-r6-feed {
|
||||
flex: 0 0 auto;
|
||||
margin-top: 20px;
|
||||
overflow: visible;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
margin-top: 26px;
|
||||
overflow: auto;
|
||||
scrollbar-width: thin;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
@@ -194,14 +88,9 @@
|
||||
margin: 0;
|
||||
}
|
||||
.first-import-r6-options {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
min-height: 132px;
|
||||
padding: 22px 24px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(190px, 0.7fr) minmax(0, 1.3fr);
|
||||
align-items: center;
|
||||
gap: 0 28px;
|
||||
min-height: 0;
|
||||
padding: 24px;
|
||||
border: 1px solid #ffffffe0;
|
||||
border-radius: 22px;
|
||||
background:
|
||||
@@ -210,13 +99,9 @@
|
||||
box-shadow:
|
||||
inset 0 1px #fff,
|
||||
0 12px 32px rgb(49 75 101 / 4%);
|
||||
overflow: visible;
|
||||
overflow: auto;
|
||||
scrollbar-width: thin;
|
||||
overscroll-behavior: contain;
|
||||
align-self: stretch;
|
||||
}
|
||||
.first-import-r6-options-head {
|
||||
min-width: 0;
|
||||
}
|
||||
.first-import-r6-eyebrow {
|
||||
margin: 0 0 14px;
|
||||
@@ -243,12 +128,12 @@
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 104px;
|
||||
margin: 0;
|
||||
padding: 18px 52px 16px 18px;
|
||||
min-height: 124px;
|
||||
margin: 0 0 16px;
|
||||
padding: 18px 16px 12px;
|
||||
border: 1px solid rgb(179 196 209 / 42%);
|
||||
border-radius: 12px;
|
||||
background: rgb(255 255 255 / 34%);
|
||||
border-radius: 0;
|
||||
background: rgb(255 255 255 / 10%);
|
||||
box-shadow: inset 0 1px 1px #ffffffd9;
|
||||
backdrop-filter: blur(20px);
|
||||
cursor: pointer;
|
||||
@@ -277,8 +162,8 @@
|
||||
.first-import-r6-selection input {
|
||||
appearance: none;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
top: 22px;
|
||||
right: 18px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
min-height: 16px;
|
||||
@@ -316,11 +201,10 @@
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
margin-top: 7px;
|
||||
margin-top: 8px;
|
||||
color: #5e6e7a;
|
||||
}
|
||||
.first-import-r6-options-note {
|
||||
grid-column: 1 / -1;
|
||||
font-size: 11px;
|
||||
line-height: 22px;
|
||||
margin: 0 0 12px;
|
||||
@@ -340,37 +224,33 @@
|
||||
.first-import-r6-options details p {
|
||||
margin: 6px 0;
|
||||
}
|
||||
.first-import-r6-feedback {
|
||||
margin: 0 0 12px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
background: rgb(255 255 255 / 50%);
|
||||
.first-import-r6-footer-note {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-height: 64px;
|
||||
overflow: auto;
|
||||
}
|
||||
.first-import-r6-feedback p {
|
||||
.first-import-r6-footer-note p {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: #63707b;
|
||||
}
|
||||
.first-import-r6-feedback [role="alert"] {
|
||||
.first-import-r6-footer-note [role="alert"] {
|
||||
color: #944242;
|
||||
}
|
||||
.first-import-r6-feedback .first-import-r6-partial-success {
|
||||
.first-import-r6-footer-note .first-import-r6-partial-success {
|
||||
color: #245f50;
|
||||
font-weight: 600;
|
||||
}
|
||||
.first-import-r6-footer-actions {
|
||||
display: flex;
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
}
|
||||
.first-import-r6-footer-actions > [data-variant="primary"] {
|
||||
min-width: min(160px, 100%);
|
||||
min-width: 210px;
|
||||
}
|
||||
.first-import-r6-footer-actions .r6-button-label {
|
||||
display: inline-flex;
|
||||
@@ -517,28 +397,13 @@ dialog.first-import-r6-original[open] {
|
||||
}
|
||||
@media (max-width: 1150px) {
|
||||
.first-import-r6-layout {
|
||||
gap: 18px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 761px) and (max-height: 859px) {
|
||||
.onboarding-r6:has(.first-import-r6-layout)
|
||||
> :is(.onboarding-r6-titlebar, .onboarding-r6-local-footer) {
|
||||
display: none;
|
||||
}
|
||||
.onboarding-r6:has(.first-import-r6-layout) > .onboarding-r6-stage {
|
||||
min-height: 0;
|
||||
height: 100dvh;
|
||||
padding: 10px 24px 2px;
|
||||
}
|
||||
.onboarding-r6:has(.first-import-r6-layout)
|
||||
> .onboarding-r6-stage
|
||||
> .onboarding-r6-shell {
|
||||
height: 100%;
|
||||
grid-template-columns: minmax(0, 1fr) 278px;
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 761px) and (max-height: 959px) {
|
||||
.onboarding-r6-body:has(> .first-import-r6-layout) {
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
@@ -548,8 +413,8 @@ dialog.first-import-r6-original[open] {
|
||||
}
|
||||
@media (min-width: 761px) and (max-height: 859px) {
|
||||
.first-import-r6-options {
|
||||
padding: 18px 20px;
|
||||
overflow: visible;
|
||||
padding: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.first-import-r6-eyebrow {
|
||||
margin-bottom: 4px;
|
||||
@@ -566,16 +431,16 @@ dialog.first-import-r6-original[open] {
|
||||
}
|
||||
.first-import-r6-selection,
|
||||
.first-import-r6-selection + .first-import-r6-selection {
|
||||
min-height: 96px;
|
||||
margin-bottom: 0;
|
||||
padding: 15px 48px 13px 16px;
|
||||
min-height: 86px;
|
||||
margin-bottom: 8px;
|
||||
padding: 9px 12px;
|
||||
}
|
||||
.first-import-r6-selection strong {
|
||||
line-height: 24px;
|
||||
}
|
||||
.first-import-r6-selection input {
|
||||
top: 17px;
|
||||
right: 16px;
|
||||
top: 13px;
|
||||
right: 12px;
|
||||
}
|
||||
.first-import-r6-selection span {
|
||||
margin-top: 4px;
|
||||
@@ -596,7 +461,6 @@ dialog.first-import-r6-original[open] {
|
||||
overflow: visible;
|
||||
}
|
||||
.first-import-r6-options {
|
||||
display: block;
|
||||
overflow: visible;
|
||||
}
|
||||
.first-import-r6-footer-note {
|
||||
@@ -654,10 +518,6 @@ dialog.first-import-r6-original[open] {
|
||||
outline-offset: 3px;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.first-import-r6-submission-status::before {
|
||||
animation: none;
|
||||
border-color: #587c90;
|
||||
}
|
||||
.first-import-r6-selection,
|
||||
.first-import-r6-selection input::after {
|
||||
transition: none;
|
||||
|
||||
@@ -300,40 +300,40 @@
|
||||
|
||||
@media (min-width: 761px) and (max-height: 959px) {
|
||||
.memory-mode-cards--rows {
|
||||
min-height: 360px;
|
||||
min-height: 284px;
|
||||
}
|
||||
.memory-mode-cards--rows .memory-mode-card-list {
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
.memory-mode-cards--rows .memory-mode-card {
|
||||
min-height: 112px;
|
||||
min-height: 88px;
|
||||
}
|
||||
.memory-mode-cards--rows .memory-mode-card-choice {
|
||||
min-height: 0;
|
||||
padding: 13px 16px;
|
||||
padding: 9px 16px;
|
||||
grid-template-columns: minmax(205px, 0.82fr) minmax(0, 1.5fr);
|
||||
column-gap: 22px;
|
||||
}
|
||||
.memory-mode-cards--rows .memory-mode-radio {
|
||||
top: 13px;
|
||||
top: 9px;
|
||||
right: 16px;
|
||||
}
|
||||
.memory-mode-cards--rows .memory-mode-title strong {
|
||||
font-size: 17px;
|
||||
line-height: 25px;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
}
|
||||
.memory-mode-cards--rows .memory-mode-description {
|
||||
min-height: 0;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
margin-top: 2px;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
}
|
||||
.memory-mode-cards--rows .memory-mode-features {
|
||||
margin-top: 0;
|
||||
padding: 0 36px 0 22px;
|
||||
gap: 6px;
|
||||
gap: 3px;
|
||||
font-size: 11px;
|
||||
line-height: 17px;
|
||||
line-height: 15px;
|
||||
}
|
||||
.memory-mode-cards--rows .memory-mode-detail {
|
||||
margin-top: 5px;
|
||||
|
||||
@@ -588,6 +588,52 @@
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.onboarding-r6-ultra-status {
|
||||
min-height: 66px;
|
||||
padding: 12px 14px;
|
||||
display: grid;
|
||||
grid-template-columns: 22px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
border: 1px solid rgb(143 169 158 / 42%);
|
||||
border-radius: 12px;
|
||||
background:
|
||||
linear-gradient(105deg, rgb(236 244 240 / 88%), rgb(255 255 255 / 56%)),
|
||||
rgb(255 255 255 / 42%);
|
||||
box-shadow:
|
||||
inset 0 1px rgb(255 255 255 / 82%),
|
||||
0 8px 24px rgb(47 107 87 / 5%);
|
||||
color: #405867;
|
||||
animation: onboarding-r6-ultra-status-in 180ms cubic-bezier(0.16, 1, 0.3, 1)
|
||||
both;
|
||||
}
|
||||
.onboarding-r6-ultra-status > svg {
|
||||
color: #527565;
|
||||
}
|
||||
.onboarding-r6-schedule-loading {
|
||||
margin: 0;
|
||||
color: #607482;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.onboarding-r6-ultra-status .onboarding-r6-schedule-recovery {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
.onboarding-r6-ultra-status .onboarding-r6-schedule-retry {
|
||||
flex: 0 0 auto;
|
||||
min-width: 76px;
|
||||
}
|
||||
@keyframes onboarding-r6-ultra-status-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
}
|
||||
.onboarding-r6-evolve-settings {
|
||||
margin-top: 0;
|
||||
padding: 18px 20px 4px;
|
||||
@@ -615,7 +661,7 @@
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
}
|
||||
.onboarding-r6-schedule-recovery {
|
||||
.onboarding-r6-mode .onboarding-r6-schedule-recovery {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 10px;
|
||||
@@ -623,7 +669,7 @@
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.onboarding-r6-schedule-recovery p {
|
||||
.onboarding-r6-mode .onboarding-r6-schedule-recovery p {
|
||||
margin: 0;
|
||||
}
|
||||
.onboarding-r6-mode-error {
|
||||
@@ -637,7 +683,23 @@
|
||||
@media (min-width: 761px) {
|
||||
.onboarding-r6-mode {
|
||||
min-height: 100%;
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 761px) and (max-height: 959px) {
|
||||
.onboarding-r6-mode {
|
||||
gap: 12px;
|
||||
}
|
||||
.onboarding-r6-mode-intro h2 {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
.onboarding-r6-mode-intro p {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.onboarding-r6-ultra-status {
|
||||
min-height: 58px;
|
||||
padding-block: 9px;
|
||||
}
|
||||
}
|
||||
.onboarding-r6-empty {
|
||||
@@ -980,6 +1042,9 @@
|
||||
.onboarding-r6-badge[data-found="true"] i {
|
||||
animation: none;
|
||||
}
|
||||
.onboarding-r6-ultra-status {
|
||||
animation: none;
|
||||
}
|
||||
.onboarding-r6-row,
|
||||
.onboarding-r6 .onboarding-r6-quiet,
|
||||
.onboarding-r6-list-status {
|
||||
|
||||
@@ -23,7 +23,7 @@ const modeChineseMessages = {
|
||||
"当前模型已经启用,可以继续完成首次设置。":
|
||||
"当前模型已经启用,可以继续完成首次设置。",
|
||||
继续使用当前模型: "继续使用当前模型",
|
||||
"你希望 MemHub 如何形成记忆?": "选择记忆模式",
|
||||
"你希望 MemHub 如何形成记忆?": "你希望 MemHub 如何形成记忆?",
|
||||
"需要明确选择一种方式;之后仍可在设置中更改。": "之后可在设置中更改。",
|
||||
记忆模式: "记忆模式",
|
||||
记忆模式与模型: "记忆模式与模型",
|
||||
|
||||
Reference in new issue
Block a user