[codex] add customizable profile avatars (#870)

* add customizable profile avatars

* keep profile avatar visible when sidebar collapses

* simplify collapsed profile avatar styling

* force managed gateway startup in docker

* limit gateway autostart to active profile

* restore all profile gateway autostart

* fix managed gateway runtime detection
This commit is contained in:
ekko
2026-05-20 14:15:01 +08:00
committed by GitHub
parent 663afb61ff
commit c90eba226d
27 changed files with 892 additions and 94 deletions
@@ -0,0 +1,56 @@
<script setup lang="ts">
import { computed } from 'vue'
import multiavatar from '@multiavatar/multiavatar'
import type { ProfileAvatar } from '@/api/hermes/profiles'
const props = withDefaults(defineProps<{
name: string
avatar?: ProfileAvatar | null
size?: number
}>(), {
size: 24,
})
const fallbackSeed = computed(() => props.name || 'default')
const generatedSvg = computed(() => multiavatar(props.avatar?.seed || fallbackSeed.value))
const style = computed(() => ({
width: `${props.size}px`,
height: `${props.size}px`,
flexBasis: `${props.size}px`,
}))
</script>
<template>
<span class="profile-avatar-view" :style="style">
<img
v-if="avatar?.type === 'image' && avatar.dataUrl"
class="profile-avatar-image"
:src="avatar.dataUrl"
alt=""
draggable="false"
>
<span v-else class="profile-avatar-svg" v-html="generatedSvg" />
</span>
</template>
<style scoped>
.profile-avatar-view {
display: inline-flex;
flex: 0 0 auto;
border-radius: 50%;
overflow: hidden;
background: var(--bg-secondary);
}
.profile-avatar-image,
.profile-avatar-svg,
.profile-avatar-svg :deep(svg) {
width: 100%;
height: 100%;
display: block;
}
.profile-avatar-image {
object-fit: cover;
}
</style>
@@ -4,6 +4,7 @@ import { NButton, NTag, NSpin, useMessage, useDialog } from 'naive-ui'
import type { HermesProfile, HermesProfileDetail } from '@/api/hermes/profiles'
import { useProfilesStore } from '@/stores/hermes/profiles'
import { useI18n } from 'vue-i18n'
import ProfileAvatar from './ProfileAvatar.vue'
const props = defineProps<{ profile: HermesProfile }>()
const emit = defineEmits<{}>()
@@ -86,7 +87,10 @@ async function handleExport() {
<template>
<div class="profile-card" :class="{ active: profile.active }">
<div class="card-header">
<h3 class="profile-name">{{ profile.name }}</h3>
<div class="profile-title">
<ProfileAvatar :name="profile.name" :avatar="profile.avatar" :size="28" />
<h3 class="profile-name">{{ profile.name }}</h3>
</div>
<NTag v-if="profile.active" size="tiny" type="success" :bordered="false">
{{ t('profiles.active') }}
</NTag>
@@ -188,9 +192,17 @@ async function handleExport() {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 12px;
}
.profile-title {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.profile-name {
font-size: 15px;
font-weight: 600;
@@ -198,7 +210,8 @@ async function handleExport() {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 70%;
max-width: 100%;
margin: 0;
}
.card-body {