AgentIdentityDialog.vue 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344
  1. <script setup lang="ts">
  2. import { ref, watch } from 'vue'
  3. import type { Profile } from '@shared/types'
  4. import AppIcon from '@/components/common/AppIcon.vue'
  5. import AgentIdentityPanel from './AgentIdentityPanel.vue'
  6. import type { ProfileIdentityInput } from '@/api/profiles'
  7. import ModelServicesPanel from './ModelServicesPanel.vue'
  8. import DuplexVoicePanel from './DuplexVoicePanel.vue'
  9. const props = defineProps<{ open: boolean; profile?: Profile; busy?: boolean; error?: string; isAdmin?: boolean }>()
  10. const emit = defineEmits<{ close: []; save: [input: ProfileIdentityInput] }>()
  11. const activeTab = ref<'identity' | 'models' | 'voice'>('identity')
  12. watch(() => [props.open, props.profile] as const, () => {
  13. if (props.open) activeTab.value = 'identity'
  14. }, { immediate: true })
  15. </script>
  16. <template>
  17. <Teleport to="body">
  18. <Transition name="identity-fade">
  19. <div v-if="open && profile" class="identity-layer" @mousedown.self="emit('close')">
  20. <section class="identity-dialog" role="dialog" aria-modal="true" aria-label="机器人管理">
  21. <header><div><small>AGENT 管理</small><h2>{{ profile.agentName || profile.displayName || profile.name }}</h2></div><button class="icon-button" type="button" aria-label="关闭" :disabled="busy" @click="emit('close')"><AppIcon name="close" /></button></header>
  22. <nav v-if="isAdmin" class="management-tabs" aria-label="机器人管理分类"><button type="button" :class="{ active: activeTab === 'identity' }" @click="activeTab = 'identity'">身份</button><button type="button" :class="{ active: activeTab === 'models' }" @click="activeTab = 'models'">模型服务</button><button type="button" :class="{ active: activeTab === 'voice' }" @click="activeTab = 'voice'">双流语音</button></nav>
  23. <AgentIdentityPanel v-if="activeTab === 'identity'" :profile="profile" :busy="busy" :error="error" @save="emit('save', $event)">
  24. <template #actions>
  25. <button class="quiet-button" type="button" :disabled="busy" @click="emit('close')">取消</button>
  26. <button class="primary-button" type="submit" :disabled="busy">{{ busy ? '正在同步…' : '保存并同步' }}</button>
  27. </template>
  28. </AgentIdentityPanel>
  29. <ModelServicesPanel v-else-if="isAdmin && activeTab === 'models'" :profile="profile.name" />
  30. <DuplexVoicePanel v-else-if="isAdmin && activeTab === 'voice'" />
  31. </section>
  32. </div>
  33. </Transition>
  34. </Teleport>
  35. </template>
  36. <style scoped>
  37. .identity-layer{position:fixed;z-index:230;inset:0;display:grid;place-items:center;padding:18px;background:var(--scrim);backdrop-filter:blur(4px)}.identity-dialog{width:min(680px,100%);max-height:calc(100dvh - 36px);box-sizing:border-box;padding:18px;overflow:auto;border:1px solid var(--line);border-radius:16px;background:var(--surface-raised);box-shadow:var(--shadow-float)}.identity-dialog>header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:14px}.identity-dialog h2{margin:3px 0 0;font-size:17px}.identity-dialog header small{color:var(--text-muted);font-size:9px;letter-spacing:.08em}.management-tabs{display:flex;gap:4px;margin-bottom:18px;padding:3px;border-radius:10px;background:var(--surface-soft)}.management-tabs button{min-height:32px;flex:1;border:0;border-radius:8px;background:transparent;color:var(--text-muted);cursor:pointer;font-size:10px}.management-tabs button.active{background:var(--surface-raised);color:var(--text-primary);box-shadow:0 1px 4px rgba(0,0,0,.08)}.identity-fade-enter-active,.identity-fade-leave-active{transition:opacity 130ms ease}.identity-fade-enter-from,.identity-fade-leave-to{opacity:0}
  38. @media(max-width:600px){.identity-layer{place-items:end center;padding:0}.identity-dialog{width:100%;max-height:calc(100dvh - 12px);border-radius:17px 17px 0 0;padding-bottom:max(18px,env(safe-area-inset-bottom))}.management-tabs{position:sticky;top:-18px;z-index:2}}
  39. </style>