feat: profile-aware cache isolation and UX improvements
- Fix chat store cache keys to include profile name, prevent data leaking between profiles - Defer cache hydration to after profile load to avoid race condition - Remove collapsible sidebar feature (not needed) - Remove confirmation dialog on profile switch (direct reload) - Auto-start gateway when creating new profile - Clear profile-specific localStorage cache on profile delete (safe prefix matching) - Clean up unused imports in SettingsView Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,12 +35,6 @@ async function toggleDetail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleSwitch() {
|
function handleSwitch() {
|
||||||
dialog.warning({
|
|
||||||
title: t('profiles.switchTo'),
|
|
||||||
content: t('profiles.switchConfirm', { name: props.profile.name }),
|
|
||||||
positiveText: t('common.confirm'),
|
|
||||||
negativeText: t('common.cancel'),
|
|
||||||
onPositiveClick: async () => {
|
|
||||||
profilesStore.switchProfile(props.profile.name).then(ok => {
|
profilesStore.switchProfile(props.profile.name).then(ok => {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
@@ -48,8 +42,6 @@ function handleSwitch() {
|
|||||||
message.error(t('profiles.switchFailed'))
|
message.error(t('profiles.switchFailed'))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { deleteSession as deleteSessionApi, fetchSession, fetchSessions, type He
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { useAppStore } from './app'
|
import { useAppStore } from './app'
|
||||||
|
import { useProfilesStore } from './profiles'
|
||||||
|
|
||||||
export interface Attachment {
|
export interface Attachment {
|
||||||
id: string
|
id: string
|
||||||
@@ -160,18 +161,31 @@ function mapHermesSession(s: SessionSummary): Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cache keys for stale-while-revalidate loading of sessions / messages.
|
// Cache keys for stale-while-revalidate loading of sessions / messages.
|
||||||
|
// All keys include the active profile name to isolate cache between profiles.
|
||||||
// Rendering from cache on boot avoids the multi-round-trip wait the user sees
|
// Rendering from cache on boot avoids the multi-round-trip wait the user sees
|
||||||
// every time they open the page (esp. noticeable on mobile).
|
// every time they open the page (esp. noticeable on mobile).
|
||||||
const STORAGE_KEY = 'hermes_active_session'
|
const STORAGE_KEY_PREFIX = 'hermes_active_session_'
|
||||||
const SESSIONS_CACHE_KEY = 'hermes_sessions_cache_v1'
|
const SESSIONS_CACHE_KEY_PREFIX = 'hermes_sessions_cache_v1_'
|
||||||
const MSGS_CACHE_KEY_PREFIX = 'hermes_session_msgs_v1_'
|
|
||||||
// tmux-like resume: persist active run info so a refresh/reopen mid-run can
|
|
||||||
// pick up the working indicator and poll fetchSession for new progress.
|
|
||||||
const IN_FLIGHT_KEY_PREFIX = 'hermes_in_flight_v1_'
|
|
||||||
const IN_FLIGHT_TTL_MS = 15 * 60 * 1000 // Give up after 15 minutes
|
const IN_FLIGHT_TTL_MS = 15 * 60 * 1000 // Give up after 15 minutes
|
||||||
const POLL_INTERVAL_MS = 2000
|
const POLL_INTERVAL_MS = 2000
|
||||||
const POLL_STABLE_EXITS = 3 // 3 × 2s = 6s of no change → assume run finished
|
const POLL_STABLE_EXITS = 3 // 3 × 2s = 6s of no change → assume run finished
|
||||||
|
|
||||||
|
// 获取当前 profile 名称,用于隔离缓存。
|
||||||
|
// 从 profiles store 的 activeProfileName(同步 localStorage)读取,
|
||||||
|
// 避免异步加载导致 chat store 初始化时拿到 null。
|
||||||
|
function getProfileName(): string {
|
||||||
|
try {
|
||||||
|
return useProfilesStore().activeProfileName || 'default'
|
||||||
|
} catch {
|
||||||
|
return 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function storageKey(): string { return STORAGE_KEY_PREFIX + getProfileName() }
|
||||||
|
function sessionsCacheKey(): string { return SESSIONS_CACHE_KEY_PREFIX + getProfileName() }
|
||||||
|
function msgsCacheKey(sid: string): string { return `hermes_session_msgs_v1_${getProfileName()}_${sid}_` }
|
||||||
|
function inFlightKey(sid: string): string { return `hermes_in_flight_v1_${getProfileName()}_${sid}` }
|
||||||
|
|
||||||
interface InFlightRun {
|
interface InFlightRun {
|
||||||
runId: string
|
runId: string
|
||||||
startedAt: number
|
startedAt: number
|
||||||
@@ -216,7 +230,7 @@ function sanitizeForCache(msgs: Message[]): Message[] {
|
|||||||
|
|
||||||
export const useChatStore = defineStore('chat', () => {
|
export const useChatStore = defineStore('chat', () => {
|
||||||
const sessions = ref<Session[]>([])
|
const sessions = ref<Session[]>([])
|
||||||
const activeSessionId = ref<string | null>(localStorage.getItem(STORAGE_KEY))
|
const activeSessionId = ref<string | null>(null)
|
||||||
const streamStates = ref<Map<string, AbortController>>(new Map())
|
const streamStates = ref<Map<string, AbortController>>(new Map())
|
||||||
const isStreaming = computed(() => activeSessionId.value != null && streamStates.value.has(activeSessionId.value))
|
const isStreaming = computed(() => activeSessionId.value != null && streamStates.value.has(activeSessionId.value))
|
||||||
const isLoadingSessions = ref(false)
|
const isLoadingSessions = ref(false)
|
||||||
@@ -235,25 +249,10 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
const activeSession = ref<Session | null>(null)
|
const activeSession = ref<Session | null>(null)
|
||||||
const messages = computed<Message[]>(() => activeSession.value?.messages || [])
|
const messages = computed<Message[]>(() => activeSession.value?.messages || [])
|
||||||
|
|
||||||
// Hydrate from cache synchronously so the UI renders instantly on boot.
|
|
||||||
// Network revalidation happens in loadSessions() below.
|
|
||||||
const cachedSessions = loadJson<Session[]>(SESSIONS_CACHE_KEY)
|
|
||||||
if (cachedSessions?.length) {
|
|
||||||
sessions.value = cachedSessions
|
|
||||||
if (activeSessionId.value) {
|
|
||||||
const cachedActive = cachedSessions.find(s => s.id === activeSessionId.value) || null
|
|
||||||
if (cachedActive) {
|
|
||||||
const cachedMsgs = loadJson<Message[]>(MSGS_CACHE_KEY_PREFIX + activeSessionId.value)
|
|
||||||
if (cachedMsgs) cachedActive.messages = cachedMsgs
|
|
||||||
activeSession.value = cachedActive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function persistSessionsList() {
|
function persistSessionsList() {
|
||||||
// Cache lightweight summaries only (messages are cached per-session).
|
// Cache lightweight summaries only (messages are cached per-session).
|
||||||
saveJson(
|
saveJson(
|
||||||
SESSIONS_CACHE_KEY,
|
sessionsCacheKey(),
|
||||||
sessions.value.map(s => ({ ...s, messages: [] })),
|
sessions.value.map(s => ({ ...s, messages: [] })),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -262,22 +261,22 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
const sid = activeSessionId.value
|
const sid = activeSessionId.value
|
||||||
if (!sid) return
|
if (!sid) return
|
||||||
const s = sessions.value.find(sess => sess.id === sid)
|
const s = sessions.value.find(sess => sess.id === sid)
|
||||||
if (s) saveJson(MSGS_CACHE_KEY_PREFIX + sid, sanitizeForCache(s.messages))
|
if (s) saveJson(msgsCacheKey(sid), sanitizeForCache(s.messages))
|
||||||
}
|
}
|
||||||
|
|
||||||
function markInFlight(sid: string, runId: string) {
|
function markInFlight(sid: string, runId: string) {
|
||||||
saveJson(IN_FLIGHT_KEY_PREFIX + sid, { runId, startedAt: Date.now() } as InFlightRun)
|
saveJson(inFlightKey(sid), { runId, startedAt: Date.now() } as InFlightRun)
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearInFlight(sid: string) {
|
function clearInFlight(sid: string) {
|
||||||
removeItem(IN_FLIGHT_KEY_PREFIX + sid)
|
removeItem(inFlightKey(sid))
|
||||||
}
|
}
|
||||||
|
|
||||||
function readInFlight(sid: string): InFlightRun | null {
|
function readInFlight(sid: string): InFlightRun | null {
|
||||||
const rec = loadJson<InFlightRun>(IN_FLIGHT_KEY_PREFIX + sid)
|
const rec = loadJson<InFlightRun>(inFlightKey(sid))
|
||||||
if (!rec) return null
|
if (!rec) return null
|
||||||
if (Date.now() - rec.startedAt > IN_FLIGHT_TTL_MS) {
|
if (Date.now() - rec.startedAt > IN_FLIGHT_TTL_MS) {
|
||||||
removeItem(IN_FLIGHT_KEY_PREFIX + sid)
|
removeItem(inFlightKey(sid))
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return rec
|
return rec
|
||||||
@@ -379,6 +378,22 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
async function loadSessions() {
|
async function loadSessions() {
|
||||||
isLoadingSessions.value = true
|
isLoadingSessions.value = true
|
||||||
try {
|
try {
|
||||||
|
// 从 profile 对应的缓存中恢复,实现 instant render
|
||||||
|
const cachedSessions = loadJson<Session[]>(sessionsCacheKey())
|
||||||
|
if (cachedSessions?.length) {
|
||||||
|
sessions.value = cachedSessions
|
||||||
|
const savedId = localStorage.getItem(storageKey())
|
||||||
|
if (savedId) {
|
||||||
|
const cachedActive = cachedSessions.find(s => s.id === savedId) || null
|
||||||
|
if (cachedActive) {
|
||||||
|
const cachedMsgs = loadJson<Message[]>(msgsCacheKey(savedId))
|
||||||
|
if (cachedMsgs) cachedActive.messages = cachedMsgs
|
||||||
|
activeSession.value = cachedActive
|
||||||
|
activeSessionId.value = savedId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const list = await fetchSessions()
|
const list = await fetchSessions()
|
||||||
const fresh = list.map(mapHermesSession)
|
const fresh = list.map(mapHermesSession)
|
||||||
const freshIds = new Set(fresh.map(s => s.id))
|
const freshIds = new Set(fresh.map(s => s.id))
|
||||||
@@ -455,7 +470,7 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
|
|
||||||
async function switchSession(sessionId: string) {
|
async function switchSession(sessionId: string) {
|
||||||
activeSessionId.value = sessionId
|
activeSessionId.value = sessionId
|
||||||
localStorage.setItem(STORAGE_KEY, sessionId)
|
localStorage.setItem(storageKey(), sessionId)
|
||||||
activeSession.value = sessions.value.find(s => s.id === sessionId) || null
|
activeSession.value = sessions.value.find(s => s.id === sessionId) || null
|
||||||
|
|
||||||
if (!activeSession.value) return
|
if (!activeSession.value) return
|
||||||
@@ -465,7 +480,7 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
// loading state while we fetch.
|
// loading state while we fetch.
|
||||||
const hasLocalMessages = activeSession.value.messages.length > 0
|
const hasLocalMessages = activeSession.value.messages.length > 0
|
||||||
if (!hasLocalMessages) {
|
if (!hasLocalMessages) {
|
||||||
const cachedMsgs = loadJson<Message[]>(MSGS_CACHE_KEY_PREFIX + sessionId)
|
const cachedMsgs = loadJson<Message[]>(msgsCacheKey(sessionId))
|
||||||
if (cachedMsgs?.length) {
|
if (cachedMsgs?.length) {
|
||||||
activeSession.value.messages = cachedMsgs
|
activeSession.value.messages = cachedMsgs
|
||||||
}
|
}
|
||||||
@@ -559,7 +574,7 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
async function deleteSession(sessionId: string) {
|
async function deleteSession(sessionId: string) {
|
||||||
await deleteSessionApi(sessionId)
|
await deleteSessionApi(sessionId)
|
||||||
sessions.value = sessions.value.filter(s => s.id !== sessionId)
|
sessions.value = sessions.value.filter(s => s.id !== sessionId)
|
||||||
removeItem(MSGS_CACHE_KEY_PREFIX + sessionId)
|
removeItem(msgsCacheKey(sessionId))
|
||||||
persistSessionsList()
|
persistSessionsList()
|
||||||
if (activeSessionId.value === sessionId) {
|
if (activeSessionId.value === sessionId) {
|
||||||
if (sessions.value.length > 0) {
|
if (sessions.value.length > 0) {
|
||||||
@@ -878,29 +893,11 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load sessions on init (cache has already hydrated the UI above).
|
// Tab visibility: re-sync when returning to foreground
|
||||||
loadSessions()
|
|
||||||
|
|
||||||
// tmux-like resume on boot: if the last active session has a persisted
|
|
||||||
// in-flight run that's still fresh, show the working indicator immediately
|
|
||||||
// and start polling the server. loadSessions() above will call
|
|
||||||
// switchSession which also triggers this path, but doing it synchronously
|
|
||||||
// here means the UI shows "working" from the very first frame even while
|
|
||||||
// loadSessions is still in flight.
|
|
||||||
if (activeSessionId.value && readInFlight(activeSessionId.value)) {
|
|
||||||
startPolling(activeSessionId.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the tab returns to the foreground, re-sync the active session from
|
|
||||||
// the server. Mobile browsers suspend tabs aggressively, and any in-flight
|
|
||||||
// run that completed while we were backgrounded won't have reached the
|
|
||||||
// in-memory state otherwise.
|
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
document.addEventListener('visibilitychange', () => {
|
document.addEventListener('visibilitychange', () => {
|
||||||
if (document.visibilityState === 'visible' && activeSessionId.value && !isStreaming.value) {
|
if (document.visibilityState === 'visible' && activeSessionId.value && !isStreaming.value) {
|
||||||
void refreshActiveSession()
|
void refreshActiveSession()
|
||||||
// Resume polling too in case we put a run in-flight before going to
|
|
||||||
// background and the SSE got killed.
|
|
||||||
if (readInFlight(activeSessionId.value)) {
|
if (readInFlight(activeSessionId.value)) {
|
||||||
startPolling(activeSessionId.value)
|
startPolling(activeSessionId.value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ import { ref } from 'vue'
|
|||||||
import * as profilesApi from '@/api/hermes/profiles'
|
import * as profilesApi from '@/api/hermes/profiles'
|
||||||
import type { HermesProfile, HermesProfileDetail } from '@/api/hermes/profiles'
|
import type { HermesProfile, HermesProfileDetail } from '@/api/hermes/profiles'
|
||||||
|
|
||||||
|
const ACTIVE_PROFILE_STORAGE_KEY = 'hermes_active_profile_name'
|
||||||
|
|
||||||
export const useProfilesStore = defineStore('profiles', () => {
|
export const useProfilesStore = defineStore('profiles', () => {
|
||||||
const profiles = ref<HermesProfile[]>([])
|
const profiles = ref<HermesProfile[]>([])
|
||||||
|
// 初始化时同步读 localStorage,确保其他 store(如 chat)在启动时能拿到 profile name
|
||||||
|
const activeProfileName = ref<string | null>(localStorage.getItem(ACTIVE_PROFILE_STORAGE_KEY))
|
||||||
const activeProfile = ref<HermesProfile | null>(null)
|
const activeProfile = ref<HermesProfile | null>(null)
|
||||||
const detailMap = ref<Record<string, HermesProfileDetail>>({})
|
const detailMap = ref<Record<string, HermesProfileDetail>>({})
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -15,6 +19,11 @@ export const useProfilesStore = defineStore('profiles', () => {
|
|||||||
try {
|
try {
|
||||||
profiles.value = await profilesApi.fetchProfiles()
|
profiles.value = await profilesApi.fetchProfiles()
|
||||||
activeProfile.value = profiles.value.find(p => p.active) ?? null
|
activeProfile.value = profiles.value.find(p => p.active) ?? null
|
||||||
|
// 同步缓存 profile name,供其他 store 启动时读取
|
||||||
|
if (activeProfile.value) {
|
||||||
|
activeProfileName.value = activeProfile.value.name
|
||||||
|
localStorage.setItem(ACTIVE_PROFILE_STORAGE_KEY, activeProfile.value.name)
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to fetch profiles:', err)
|
console.error('Failed to fetch profiles:', err)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -43,11 +52,31 @@ export const useProfilesStore = defineStore('profiles', () => {
|
|||||||
const ok = await profilesApi.deleteProfile(name)
|
const ok = await profilesApi.deleteProfile(name)
|
||||||
if (ok) {
|
if (ok) {
|
||||||
delete detailMap.value[name]
|
delete detailMap.value[name]
|
||||||
|
// 清理该 profile 的 localStorage 缓存
|
||||||
|
clearProfileCache(name)
|
||||||
await fetchProfiles()
|
await fetchProfiles()
|
||||||
}
|
}
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清理指定 profile 的所有 localStorage 缓存(精确匹配缓存 key 前缀)
|
||||||
|
function clearProfileCache(profileName: string) {
|
||||||
|
const prefixes = [
|
||||||
|
`hermes_sessions_cache_v1_${profileName}`,
|
||||||
|
`hermes_session_msgs_v1_${profileName}_`,
|
||||||
|
`hermes_in_flight_v1_${profileName}_`,
|
||||||
|
`hermes_active_session_${profileName}`,
|
||||||
|
]
|
||||||
|
const keysToRemove: string[] = []
|
||||||
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
|
const key = localStorage.key(i)
|
||||||
|
if (key && prefixes.some(p => key.startsWith(p))) {
|
||||||
|
keysToRemove.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keysToRemove.forEach(key => localStorage.removeItem(key))
|
||||||
|
}
|
||||||
|
|
||||||
async function renameProfile(name: string, newName: string) {
|
async function renameProfile(name: string, newName: string) {
|
||||||
const ok = await profilesApi.renameProfile(name, newName)
|
const ok = await profilesApi.renameProfile(name, newName)
|
||||||
if (ok) {
|
if (ok) {
|
||||||
@@ -81,6 +110,7 @@ export const useProfilesStore = defineStore('profiles', () => {
|
|||||||
return {
|
return {
|
||||||
profiles,
|
profiles,
|
||||||
activeProfile,
|
activeProfile,
|
||||||
|
activeProfileName,
|
||||||
detailMap,
|
detailMap,
|
||||||
loading,
|
loading,
|
||||||
switching,
|
switching,
|
||||||
|
|||||||
@@ -3,12 +3,16 @@ import { onMounted } from 'vue'
|
|||||||
import ChatPanel from '@/components/hermes/chat/ChatPanel.vue'
|
import ChatPanel from '@/components/hermes/chat/ChatPanel.vue'
|
||||||
import { useAppStore } from '@/stores/hermes/app'
|
import { useAppStore } from '@/stores/hermes/app'
|
||||||
import { useChatStore } from '@/stores/hermes/chat'
|
import { useChatStore } from '@/stores/hermes/chat'
|
||||||
|
import { useProfilesStore } from '@/stores/hermes/profiles'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const chatStore = useChatStore()
|
const chatStore = useChatStore()
|
||||||
|
const profilesStore = useProfilesStore()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
appStore.loadModels()
|
appStore.loadModels()
|
||||||
|
// 先加载 profile,确保缓存 key 使用正确的 profile name
|
||||||
|
await profilesStore.fetchProfiles()
|
||||||
chatStore.loadSessions()
|
chatStore.loadSessions()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,10 +4,6 @@ import {
|
|||||||
NTabs,
|
NTabs,
|
||||||
NTabPane,
|
NTabPane,
|
||||||
NSpin,
|
NSpin,
|
||||||
NSwitch,
|
|
||||||
NInput,
|
|
||||||
NInputNumber,
|
|
||||||
useMessage,
|
|
||||||
} from "naive-ui";
|
} from "naive-ui";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useSettingsStore } from "@/stores/hermes/settings";
|
import { useSettingsStore } from "@/stores/hermes/settings";
|
||||||
@@ -16,23 +12,13 @@ import AgentSettings from "@/components/hermes/settings/AgentSettings.vue";
|
|||||||
import MemorySettings from "@/components/hermes/settings/MemorySettings.vue";
|
import MemorySettings from "@/components/hermes/settings/MemorySettings.vue";
|
||||||
import SessionSettings from "@/components/hermes/settings/SessionSettings.vue";
|
import SessionSettings from "@/components/hermes/settings/SessionSettings.vue";
|
||||||
import PrivacySettings from "@/components/hermes/settings/PrivacySettings.vue";
|
import PrivacySettings from "@/components/hermes/settings/PrivacySettings.vue";
|
||||||
import SettingRow from "@/components/hermes/settings/SettingRow.vue";
|
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const message = useMessage();
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
settingsStore.fetchSettings();
|
settingsStore.fetchSettings();
|
||||||
});
|
});
|
||||||
async function saveApiServer(values: Record<string, any>) {
|
|
||||||
try {
|
|
||||||
await settingsStore.saveSection("platforms", { api_server: values });
|
|
||||||
message.success(t("settings.saved"));
|
|
||||||
} catch (err: any) {
|
|
||||||
message.error(t("settings.saveFailed"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ profileRoutes.post('/api/hermes/profiles', async (ctx) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const output = await hermesCli.createProfile(name, clone)
|
const output = await hermesCli.createProfile(name, clone)
|
||||||
|
|
||||||
|
// 创建完成后启动该 profile 的网关
|
||||||
|
const mgr = getGatewayManager()
|
||||||
|
if (mgr) {
|
||||||
|
try { await mgr.start(name) } catch (err: any) {
|
||||||
|
console.error(`[Profile] Failed to start gateway for ${name}:`, err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.body = { success: true, message: output.trim() }
|
ctx.body = { success: true, message: output.trim() }
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
ctx.status = 500
|
ctx.status = 500
|
||||||
|
|||||||
Reference in New Issue
Block a user