diff --git a/Dockerfile b/Dockerfile index 7d91a3a..1804d23 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,7 @@ RUN npm run build && npm prune --omit=dev ENV NODE_ENV=production ENV HOME=/home/agent ENV HERMES_HOME=/home/agent/.hermes +ENV HERMES_WEB_UI_MANAGED_GATEWAY=1 ENV PATH=/opt/hermes/.venv/bin:$PATH EXPOSE 6060 diff --git a/docker-compose.yml b/docker-compose.yml index 3c41686..a7dd865 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ services: - PORT=${PORT:-6060} - HERMES_HOME=/home/agent/.hermes - HERMES_BIN=/opt/hermes/.venv/bin/hermes + - HERMES_WEB_UI_MANAGED_GATEWAY=1 - HERMES_WEB_UI_XAI_CALLBACK_BIND_HOST=0.0.0.0 - PATH=/opt/hermes/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin - AUTH_DISABLED=${AUTH_DISABLED:-false} diff --git a/packages/client/src/api/hermes/profiles.ts b/packages/client/src/api/hermes/profiles.ts index 53c2071..3498824 100644 --- a/packages/client/src/api/hermes/profiles.ts +++ b/packages/client/src/api/hermes/profiles.ts @@ -6,6 +6,7 @@ export interface HermesProfile { model: string gatewayStatus?: string alias: string + avatar?: ProfileAvatar | null } export interface HermesProfileDetail { @@ -16,6 +17,14 @@ export interface HermesProfileDetail { skills: number hasEnv: boolean hasSoulMd: boolean + avatar?: ProfileAvatar | null +} + +export interface ProfileAvatar { + type: 'generated' | 'image' + seed?: string + dataUrl?: string + updatedAt?: number } export interface ProfileRuntimeStatus { @@ -62,6 +71,18 @@ export async function fetchProfileRuntimeStatuses(): Promise { + const res = await request<{ avatar: ProfileAvatar }>(`/api/hermes/profiles/${encodeURIComponent(name)}/avatar`, { + method: 'PUT', + body: JSON.stringify(avatar), + }) + return res.avatar +} + +export async function deleteProfileAvatar(name: string): Promise { + await request(`/api/hermes/profiles/${encodeURIComponent(name)}/avatar`, { method: 'DELETE' }) +} + export async function restartProfileGateway(name: string): Promise { const res = await request<{ success: boolean; gateway: ProfileRuntimeStatus['gateway'] }>( `/api/hermes/profiles/${encodeURIComponent(name)}/gateway/restart`, diff --git a/packages/client/src/components/hermes/chat/MessageItem.vue b/packages/client/src/components/hermes/chat/MessageItem.vue index 1daaee1..4b6878a 100644 --- a/packages/client/src/components/hermes/chat/MessageItem.vue +++ b/packages/client/src/components/hermes/chat/MessageItem.vue @@ -9,7 +9,9 @@ import { copyToClipboard } from "@/utils/clipboard"; import MarkdownRenderer from "./MarkdownRenderer.vue"; import { parseThinking, countThinkingChars } from "@/utils/thinking-parser"; import { useChatStore } from "@/stores/hermes/chat"; +import { useProfilesStore } from "@/stores/hermes/profiles"; import { useSettingsStore } from "@/stores/hermes/settings"; +import ProfileAvatar from "@/components/hermes/profiles/ProfileAvatar.vue"; import { copyTextToClipboard, handleCodeBlockCopyClick, @@ -180,9 +182,12 @@ const toolExpanded = ref(false); const previewUrl = ref(null); const chatStore = useChatStore(); +const profilesStore = useProfilesStore(); const settingsStore = useSettingsStore(); const speech = useGlobalSpeech(); const voiceSettings = useVoiceSettings(); +const assistantProfileName = computed(() => chatStore.activeSession?.profile || profilesStore.activeProfileName || "default"); +const assistantProfileAvatar = computed(() => profilesStore.profiles.find(profile => profile.name === assistantProfileName.value)?.avatar); // Copy entire bubble content const copyableContent = computed(() => { @@ -773,11 +778,12 @@ onBeforeUnmount(() => {