mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-18 03:57:06 +08:00
- Integrated a tooltip with a button to open the contributor's GitHub profile in the CursorAccountCard component. - Updated the layout of the account card for better visual organization. - Removed routing mode options from the configuration view and adjusted related translations in multiple languages. - Cleaned up unused routing mode logic in the app state management.
189 lines
5.3 KiB
Vue
189 lines
5.3 KiB
Vue
<script setup>
|
||
import Button from "@/components/ui/Button.vue";
|
||
import Card from "@/components/ui/Card.vue";
|
||
import HomeMetricsCard from "@/components/HomeMetricsCard.vue";
|
||
import CursorAccountCard from "@/components/CursorAccountCard.vue";
|
||
import { showModal } from "@/composables/useModal";
|
||
import { getAdRuntime } from "@/services/clientApi";
|
||
import {
|
||
appState,
|
||
appViewState,
|
||
openConfigWindow,
|
||
openModelConfigWindow,
|
||
syncHomeMetrics,
|
||
syncServiceState,
|
||
toUserError,
|
||
toggleService,
|
||
} from "@/state/appState";
|
||
import { Events } from "@wailsio/runtime";
|
||
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
||
|
||
const AD_UPDATED_EVENT = "ad:updated";
|
||
const OPEN_AD_EVENT = "cursor:open-ad";
|
||
|
||
const adRuntime = ref(null);
|
||
let unsubscribeAdUpdated = null;
|
||
|
||
function asString(value) {
|
||
if (typeof value === "string") {
|
||
return value.trim();
|
||
}
|
||
if (typeof value === "number" || typeof value === "boolean") {
|
||
return String(value);
|
||
}
|
||
return "";
|
||
}
|
||
|
||
function asBoolean(value) {
|
||
return value === true || value === "true" || value === 1 || value === "1";
|
||
}
|
||
|
||
const homeAds = computed(() => {
|
||
const runtime = adRuntime.value && typeof adRuntime.value === "object" ? adRuntime.value : {};
|
||
const slots = Array.isArray(runtime.slots) && runtime.slots.length > 0 ? runtime.slots : [runtime];
|
||
return slots
|
||
.map((slot, index) => {
|
||
const item = slot && typeof slot === "object" ? slot : {};
|
||
const home = item.home && typeof item.home === "object" ? item.home : {};
|
||
const title = asString(home.title);
|
||
if (
|
||
!title ||
|
||
!asBoolean(item.available) ||
|
||
!asBoolean(item.enabled) ||
|
||
!asString(item.packageHash)
|
||
) {
|
||
return null;
|
||
}
|
||
return {
|
||
id: asString(item.id) || String(index + 1),
|
||
title,
|
||
subtitle: asString(home.subtitle),
|
||
};
|
||
})
|
||
.filter(Boolean);
|
||
});
|
||
|
||
async function syncAdRuntimeQuietly() {
|
||
try {
|
||
adRuntime.value = await getAdRuntime();
|
||
} catch (_error) {
|
||
adRuntime.value = null;
|
||
}
|
||
}
|
||
|
||
function handleAdUpdated() {
|
||
void syncAdRuntimeQuietly();
|
||
}
|
||
|
||
function handleOpenHomeAd(slotId) {
|
||
window.dispatchEvent(new CustomEvent(OPEN_AD_EVENT, { detail: { slotId: asString(slotId) } }));
|
||
}
|
||
|
||
async function showActionError(title, error) {
|
||
await showModal({
|
||
title,
|
||
content: String(error || "服务错误").trim() || "服务错误",
|
||
});
|
||
}
|
||
|
||
async function handleToggleService() {
|
||
const result = await toggleService();
|
||
if (!result.ok) {
|
||
await showActionError("服务操作失败", result.error);
|
||
}
|
||
}
|
||
|
||
async function handleRefreshState() {
|
||
const [serviceStateResult] = await Promise.allSettled([
|
||
syncServiceState(),
|
||
syncHomeMetrics(),
|
||
]);
|
||
if (serviceStateResult.status === "rejected") {
|
||
await showActionError("刷新失败", toUserError(serviceStateResult.reason));
|
||
}
|
||
}
|
||
|
||
async function handleRefreshMetrics() {
|
||
await syncHomeMetrics().catch(() => {});
|
||
}
|
||
|
||
async function handleOpenConfig() {
|
||
try {
|
||
await openConfigWindow();
|
||
} catch (error) {
|
||
await showActionError("打开失败", toUserError(error));
|
||
}
|
||
}
|
||
|
||
async function handleOpenModelConfig() {
|
||
try {
|
||
await openModelConfigWindow();
|
||
} catch (error) {
|
||
await showActionError("打开失败", toUserError(error));
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
unsubscribeAdUpdated = Events.On(AD_UPDATED_EVENT, handleAdUpdated);
|
||
void syncAdRuntimeQuietly();
|
||
});
|
||
|
||
onBeforeUnmount(() => {
|
||
if (unsubscribeAdUpdated) {
|
||
unsubscribeAdUpdated();
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div class="flex h-full min-h-0 flex-col gap-4 overflow-y-auto p-4 pt-0 text-[#e5e5e5]">
|
||
<HomeMetricsCard
|
||
:metrics="appState.homeMetrics"
|
||
:loading="appState.homeMetricsLoading"
|
||
:error="appState.homeMetricsError"
|
||
:home-ads="homeAds"
|
||
@refresh="handleRefreshMetrics"
|
||
@open-ad="handleOpenHomeAd"
|
||
/>
|
||
|
||
<Card>
|
||
<div class="flex flex-col gap-4">
|
||
<div class="flex items-start justify-between gap-4">
|
||
<div class="flex flex-col gap-1">
|
||
<div class="text-sm" :class="appViewState.serviceStatusClass">
|
||
{{ appViewState.serviceStatusText }}
|
||
</div>
|
||
</div>
|
||
<div class="center-row gap-2">
|
||
<Button variant="primary" :disabled="appState.serviceBusy" @click="handleToggleService">
|
||
<span class="icon-[mdi--pause] text-[16px]" v-if="appState.serviceRunning"></span>
|
||
<span class="icon-[mdi--play] text-[16px]" v-else></span>
|
||
<span> {{ appViewState.serviceButtonText }}</span>
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="appState.serviceLastError"
|
||
class="rounded-[8px] border border-[#4b1d1d] bg-[#2a1313] px-3 py-2 text-sm text-[#fca5a5]">
|
||
{{ appState.serviceLastError }}
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
<CursorAccountCard />
|
||
|
||
<Card>
|
||
<div class="flex items-center justify-between gap-4">
|
||
<div>
|
||
<h2 class="text-base font-medium text-white">本地配置</h2>
|
||
<div class="text-sm text-[#a3a3a3]">打开设置目录,或单独管理模型配置</div>
|
||
</div>
|
||
<div class="center-row gap-2">
|
||
<Button variant="default" @click="handleOpenConfig">设置文件夹</Button>
|
||
<Button variant="primary" @click="handleOpenModelConfig">模型配置</Button>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
</template>
|