feat(welcome): show first-run welcome dialog on fresh install

Introduce a one-time welcome dialog that explains CC Switch's workflow
to new users: how their existing config is preserved as a "default"
provider and how the bundled "Official" preset enables one-click revert.
Upgrade users are excluded by checking is_providers_empty() at startup
and never see the dialog.

Persistence follows the existing *_confirmed convention in AppSettings
(proxy/usage/stream_check/failover), stored in settings.json. The field
is only written when the user explicitly clicks the confirm button,
keeping its semantics strictly about user acknowledgement.

Also adds two reusable DAO helpers:
- Database::is_providers_empty for fresh-install detection, using
  EXISTS(SELECT 1) for a short-circuit query.
- Database::get_bool_flag accepting "true" | "1", with
  init_default_official_providers migrated to use it.

Dialog copy in zh/en/ja uses conditional phrasing so it stays
accurate whether or not existing live config was found.
This commit is contained in:
Jason
2026-04-09 13:21:50 +08:00
parent a058ebeafc
commit 8669879ad0
10 changed files with 139 additions and 5 deletions
+2
View File
@@ -62,6 +62,7 @@ import PromptPanel from "@/components/prompts/PromptPanel";
import { SkillsPage } from "@/components/skills/SkillsPage";
import UnifiedSkillsPanel from "@/components/skills/UnifiedSkillsPanel";
import { DeepLinkImportDialog } from "@/components/DeepLinkImportDialog";
import { FirstRunNoticeDialog } from "@/components/FirstRunNoticeDialog";
import { AgentsPanel } from "@/components/agents/AgentsPanel";
import { UniversalProviderPanel } from "@/components/universal";
import { McpIcon } from "@/components/BrandIcons";
@@ -1354,6 +1355,7 @@ function App() {
/>
<DeepLinkImportDialog />
<FirstRunNoticeDialog />
</div>
);
}
+67
View File
@@ -0,0 +1,67 @@
import { useTranslation } from "react-i18next";
import { useQueryClient } from "@tanstack/react-query";
import { Sparkles } from "lucide-react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { useSettingsQuery } from "@/lib/query";
import { settingsApi } from "@/lib/api";
/** 首次运行欢迎提示:仅当后端启动阶段保留 firstRunNoticeConfirmed 为空时弹出。 */
export function FirstRunNoticeDialog() {
const { t } = useTranslation();
const queryClient = useQueryClient();
const { data: settings } = useSettingsQuery();
// 后端启动时已经决定好要不要弹:条件不满足的话字段会立即被写成 true,
// 所以前端这里只需要判空即可——完全对齐 streamCheckConfirmed 等既有 flag 的模式。
const isOpen = settings != null && settings.firstRunNoticeConfirmed !== true;
const handleAcknowledge = async () => {
if (!settings) return;
try {
const { webdavSync: _, ...rest } = settings;
await settingsApi.save({ ...rest, firstRunNoticeConfirmed: true });
await queryClient.invalidateQueries({ queryKey: ["settings"] });
} catch (error) {
console.error("Failed to save firstRunNoticeConfirmed:", error);
}
};
return (
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open) void handleAcknowledge();
}}
>
<DialogContent className="max-w-md" zIndex="top">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Sparkles className="h-5 w-5 text-blue-500" />
{t("firstRunNotice.title")}
</DialogTitle>
</DialogHeader>
<div className="space-y-3 px-6 py-5">
<DialogDescription className="whitespace-pre-line leading-relaxed">
{t("firstRunNotice.bodyDefault")}
</DialogDescription>
<DialogDescription className="whitespace-pre-line leading-relaxed">
{t("firstRunNotice.bodyOfficial")}
</DialogDescription>
</div>
<DialogFooter>
<Button onClick={handleAcknowledge}>
{t("firstRunNotice.confirm")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
+6
View File
@@ -42,6 +42,12 @@
"enabled": "Enabled",
"notSet": "Not Set"
},
"firstRunNotice": {
"title": "Welcome to CC Switch",
"bodyDefault": "CC Switch lets you switch between multiple Claude Code / Codex / Gemini CLI providers with a single click. If you already had these tools configured, CC Switch has saved your existing setup as a provider named “default” so none of your previous configuration is lost.",
"bodyOfficial": "An “Official” preset is also included in the list — click it any time you want to switch back to the official default. CC Switch automatically backs up your current config to “default” before switching, so you can move back and forth freely. That's how CC Switch works 😊",
"confirm": "Got it"
},
"apiKeyInput": {
"placeholder": "Enter API Key",
"show": "Show API Key",
+6
View File
@@ -42,6 +42,12 @@
"enabled": "有効",
"notSet": "未設定"
},
"firstRunNotice": {
"title": "CC Switch へようこそ",
"bodyDefault": "CC Switch は Claude Code / Codex / Gemini CLI の複数プロバイダーをワンクリックで切り替えられるツールです。すでにこれらのツールを設定済みの場合、CC Switch は現在の設定を「default」という名前のプロバイダーとして自動的に保存するので、既存の設定が失われることはありません。",
"bodyOfficial": "リストには「Official(公式)」プリセットも用意されており、公式バージョンに戻したくなったらクリックするだけで切り替えられます。切り替える前に現在の設定は自動で default にバックアップされるので、自由に行き来できます。これが CC Switch の仕組みです 😊",
"confirm": "了解しました"
},
"apiKeyInput": {
"placeholder": "API Key を入力",
"show": "API Key を表示",
+6
View File
@@ -42,6 +42,12 @@
"enabled": "已开启",
"notSet": "未设置"
},
"firstRunNotice": {
"title": "欢迎使用 CC Switch",
"bodyDefault": "CC Switch 可以帮你在 Claude Code / Codex / Gemini CLI 的多个供应商之间一键切换。如果你之前已经配置过这些工具,CC Switch 会自动把现有设置保存为名为 “default” 的供应商,保证你的配置不会丢失。",
"bodyOfficial": "列表里还预置了 “官方(Official)” 供应商,随时点一下即可切回官方默认配置。切换前 CC Switch 会自动把当前配置备份回 default,可以放心来回切。CC Switch 就是这样工作的 😊",
"confirm": "我知道了"
},
"apiKeyInput": {
"placeholder": "请输入API Key",
"show": "显示API Key",
+2
View File
@@ -264,6 +264,8 @@ export interface Settings {
enableFailoverToggle?: boolean;
// User has confirmed the failover toggle first-run notice
failoverConfirmed?: boolean;
// User has confirmed the first-run welcome notice
firstRunNoticeConfirmed?: boolean;
// User has confirmed the auto-sync traffic warning
autoSyncConfirmed?: boolean;
// 首选语言(可选,默认中文)