feat(support-info): add app support information
This commit is contained in:
16 files changed
+564
-3
No files matched your search
@@ -57,6 +57,7 @@ import {
|
||||
installMcpServer,
|
||||
forgetWebdavSyncSecretsPassphrase,
|
||||
forgetWebdavSavedPassword,
|
||||
getAppSupportInfo,
|
||||
listSystemFonts,
|
||||
saveWebdavSyncSecretsPreference,
|
||||
saveWebdavSavedPassword,
|
||||
@@ -65,6 +66,7 @@ import {
|
||||
webdavSyncSecretsStatus,
|
||||
webdavSyncTest,
|
||||
webdavSyncUpload,
|
||||
type AppSupportInfo,
|
||||
type AiModelInfo,
|
||||
type McpServerStatus,
|
||||
type WebDavConfig,
|
||||
@@ -96,6 +98,7 @@ import { LOCALE_OPTIONS } from "@/lib/app/localeOptions";
|
||||
import { DEFAULT_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES, DEFAULT_WEB_DAV_REMOTE_PATH, normalizedWebDavAutoUploadInterval, writeWebDavAutoUploadFields } from "@/lib/webdav/webdavAutoUploadConfig";
|
||||
import { apiUrl } from "@/lib/common/webPath";
|
||||
import { DEFAULT_UI_FONT_FAMILY, SYSTEM_UI_FONT_FAMILY } from "@/lib/app/appFonts";
|
||||
import { buildAppSupportInfoRows, formatAppSupportInfoForClipboard, type AppSupportInfoLabels } from "@/lib/app/supportInfo";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { toast } = useToast();
|
||||
@@ -1195,6 +1198,20 @@ function setSidebarActivation(value: "single" | "double") {
|
||||
const activeSettingsTab = ref("appearance");
|
||||
const isWeb = !isTauriRuntime();
|
||||
const displayedAppVersion = computed(() => (props.appVersion ? `v${props.appVersion}` : ""));
|
||||
const appSupportInfo = ref<AppSupportInfo | null>(null);
|
||||
const appSupportInfoLoading = ref(false);
|
||||
const appSupportInfoError = ref("");
|
||||
const appSupportInfoCopied = ref(false);
|
||||
const appSupportInfoLabels = computed<AppSupportInfoLabels>(() => ({
|
||||
appVersion: t("settings.supportInfoAppVersion"),
|
||||
runtime: t("settings.supportInfoRuntime"),
|
||||
runtimeDesktop: t("settings.supportInfoRuntimeDesktop"),
|
||||
runtimeWeb: t("settings.supportInfoRuntimeWeb"),
|
||||
operatingSystem: t("settings.supportInfoOperatingSystem"),
|
||||
architecture: t("settings.supportInfoArchitecture"),
|
||||
unknown: t("settings.supportInfoUnknown"),
|
||||
}));
|
||||
const appSupportInfoRows = computed(() => (appSupportInfo.value ? buildAppSupportInfoRows(appSupportInfo.value, appSupportInfoLabels.value) : []));
|
||||
type SettingsCategory = "editor" | "formatter" | "appearance" | "navigation" | "data" | "shortcuts" | "snippets" | "sync" | "ai" | "mcp" | "security" | "about";
|
||||
const settingsCategoryNav = computed<{ value: SettingsCategory; label: string }[]>(() => [
|
||||
{ value: "appearance", label: t("settings.appearanceTab") },
|
||||
@@ -1236,6 +1253,44 @@ async function copyDebugLogs() {
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
function fallbackAppSupportInfo(): AppSupportInfo {
|
||||
return {
|
||||
appVersion: props.appVersion || "",
|
||||
runtime: isWeb ? "web" : "desktop",
|
||||
osName: "",
|
||||
osVersion: null,
|
||||
arch: "",
|
||||
};
|
||||
}
|
||||
|
||||
async function refreshAppSupportInfo() {
|
||||
if (appSupportInfoLoading.value) return;
|
||||
appSupportInfoLoading.value = true;
|
||||
appSupportInfoError.value = "";
|
||||
try {
|
||||
appSupportInfo.value = await getAppSupportInfo();
|
||||
} catch (e: any) {
|
||||
appSupportInfo.value = appSupportInfo.value || fallbackAppSupportInfo();
|
||||
appSupportInfoError.value = e?.message || String(e);
|
||||
} finally {
|
||||
appSupportInfoLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function copyAppSupportInfo() {
|
||||
if (!appSupportInfo.value) await refreshAppSupportInfo();
|
||||
if (!appSupportInfo.value) return;
|
||||
try {
|
||||
await copyToClipboard(formatAppSupportInfoForClipboard(appSupportInfo.value, appSupportInfoLabels.value));
|
||||
appSupportInfoCopied.value = true;
|
||||
window.setTimeout(() => {
|
||||
appSupportInfoCopied.value = false;
|
||||
}, 1500);
|
||||
} catch (e: any) {
|
||||
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function clearDebugLogs() {
|
||||
clearStoredDebugLogs();
|
||||
debugLogCopied.value = false;
|
||||
@@ -1572,6 +1627,7 @@ watch(
|
||||
syncAiEditState();
|
||||
if (!isWeb && activeSettingsTab.value === "mcp") void refreshMcpStatus();
|
||||
if (!isWeb && activeSettingsTab.value === "ai" && aiIsCodexCli.value) void ensureCodexMcpStatus();
|
||||
if (activeSettingsTab.value === "about") void refreshAppSupportInfo();
|
||||
await scrollToInitialSettingsSection();
|
||||
}
|
||||
},
|
||||
@@ -1608,6 +1664,7 @@ watch([webdavAutoUploadEnabled, webdavAutoUploadIntervalMinutes], () => {
|
||||
watch(activeSettingsTab, (tab) => {
|
||||
if (tab === "mcp" && !mcpStatus.value && !mcpStatusLoading.value) void refreshMcpStatus();
|
||||
if (tab === "ai" && aiIsCodexCli.value) void ensureCodexMcpStatus();
|
||||
if (tab === "about" && !appSupportInfo.value) void refreshAppSupportInfo();
|
||||
if (tab === "appearance") {
|
||||
checkLayoutDescTruncation();
|
||||
checkIconThemeDescTruncation();
|
||||
@@ -4144,6 +4201,29 @@ onUnmounted(cleanupPreviewEditor);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div class="min-w-0 space-y-1">
|
||||
<Label>{{ t("settings.supportInfoTitle") }}</Label>
|
||||
<p class="text-sm text-muted-foreground">{{ t("settings.supportInfoDescription") }}</p>
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="sm" class="shrink-0" :disabled="appSupportInfoLoading && !appSupportInfo" @click="copyAppSupportInfo">
|
||||
<Loader2 v-if="appSupportInfoLoading && !appSupportInfo" class="mr-1 h-3.5 w-3.5 animate-spin" />
|
||||
<CheckCircle2 v-else-if="appSupportInfoCopied" class="mr-1 h-3.5 w-3.5" />
|
||||
<Copy v-else class="mr-1 h-3.5 w-3.5" />
|
||||
{{ appSupportInfoCopied ? t("settings.supportInfoCopied") : t("settings.supportInfoCopy") }}
|
||||
</Button>
|
||||
</div>
|
||||
<div v-if="appSupportInfoRows.length" class="mt-4 grid gap-3 sm:grid-cols-2">
|
||||
<div v-for="row in appSupportInfoRows" :key="row.key" class="min-w-0 rounded-md bg-muted/30 px-3 py-2">
|
||||
<div class="text-xs font-medium text-muted-foreground">{{ row.label }}</div>
|
||||
<div class="mt-1 min-w-0 select-text break-words font-mono text-xs text-foreground">{{ row.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="mt-4 text-sm text-muted-foreground">{{ t("settings.supportInfoLoading") }}</p>
|
||||
<p v-if="appSupportInfoError" class="mt-3 text-xs text-destructive">{{ t("settings.supportInfoLoadFailed", { message: appSupportInfoError }) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="min-w-0 space-y-1">
|
||||
|
||||
Reference in new issue
Block a user