feat: add profile management page with full CRUD UI
- Add frontend API layer, Pinia store, and 5 components (ProfileCard, ProfilesPanel, ProfileCreateModal, ProfileRenameModal, ProfileImportModal) - Add ProfilesView page with card grid layout and expandable details - Modify export endpoint to stream file as browser download instead of returning server path - Add sidebar nav entry, router route, and i18n translations (en/zh) - Support create, rename, delete, switch (with gateway restart), export, and import profiles Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import * as profilesApi from '@/api/hermes/profiles'
|
||||
import type { HermesProfile, HermesProfileDetail } from '@/api/hermes/profiles'
|
||||
|
||||
export const useProfilesStore = defineStore('profiles', () => {
|
||||
const profiles = ref<HermesProfile[]>([])
|
||||
const activeProfile = ref<HermesProfile | null>(null)
|
||||
const detailMap = ref<Record<string, HermesProfileDetail>>({})
|
||||
const loading = ref(false)
|
||||
const switching = ref(false)
|
||||
|
||||
async function fetchProfiles() {
|
||||
loading.value = true
|
||||
try {
|
||||
profiles.value = await profilesApi.fetchProfiles()
|
||||
activeProfile.value = profiles.value.find(p => p.active) ?? null
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch profiles:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchProfileDetail(name: string) {
|
||||
if (detailMap.value[name]) return detailMap.value[name]
|
||||
try {
|
||||
const detail = await profilesApi.fetchProfileDetail(name)
|
||||
detailMap.value[name] = detail
|
||||
return detail
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function createProfile(name: string, clone?: boolean) {
|
||||
const ok = await profilesApi.createProfile(name, clone)
|
||||
if (ok) await fetchProfiles()
|
||||
return ok
|
||||
}
|
||||
|
||||
async function deleteProfile(name: string) {
|
||||
const ok = await profilesApi.deleteProfile(name)
|
||||
if (ok) {
|
||||
delete detailMap.value[name]
|
||||
await fetchProfiles()
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
async function renameProfile(name: string, newName: string) {
|
||||
const ok = await profilesApi.renameProfile(name, newName)
|
||||
if (ok) {
|
||||
delete detailMap.value[name]
|
||||
await fetchProfiles()
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
async function switchProfile(name: string) {
|
||||
switching.value = true
|
||||
try {
|
||||
const ok = await profilesApi.switchProfile(name)
|
||||
if (ok) await fetchProfiles()
|
||||
return ok
|
||||
} finally {
|
||||
switching.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function exportProfile(name: string) {
|
||||
return profilesApi.exportProfile(name)
|
||||
}
|
||||
|
||||
async function importProfile(archive: string, name?: string) {
|
||||
const ok = await profilesApi.importProfile(archive, name)
|
||||
if (ok) await fetchProfiles()
|
||||
return ok
|
||||
}
|
||||
|
||||
return {
|
||||
profiles,
|
||||
activeProfile,
|
||||
detailMap,
|
||||
loading,
|
||||
switching,
|
||||
fetchProfiles,
|
||||
fetchProfileDetail,
|
||||
createProfile,
|
||||
deleteProfile,
|
||||
renameProfile,
|
||||
switchProfile,
|
||||
exportProfile,
|
||||
importProfile,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user