diff --git a/packages/client/src/api/client.ts b/packages/client/src/api/client.ts index d75315f..6493f5f 100644 --- a/packages/client/src/api/client.ts +++ b/packages/client/src/api/client.ts @@ -46,7 +46,7 @@ export function isStoredSuperAdmin(): boolean { return getStoredUserRole() === 'super_admin' } -function getActiveProfileName(): string | null { +export function getActiveProfileName(): string | null { return localStorage.getItem('hermes_active_profile_name') } diff --git a/packages/client/src/api/hermes/download.ts b/packages/client/src/api/hermes/download.ts index 7d8d9e2..76707a8 100644 --- a/packages/client/src/api/hermes/download.ts +++ b/packages/client/src/api/hermes/download.ts @@ -1,4 +1,4 @@ -import { getApiKey, getBaseUrlValue } from '../client' +import { getActiveProfileName, getApiKey, getBaseUrlValue } from '../client' /** * Construct a download URL with auth token as query parameter. @@ -27,6 +27,8 @@ export function getDownloadUrl(filePath: string, fileName?: string): string { const decodedName = decodeURIComponent(fileName) params.set('name', decodedName) } + const profileName = getActiveProfileName() + if (profileName) params.set('profile', profileName) const token = getApiKey() if (token) params.set('token', token) return `${base}/api/hermes/download?${params.toString()}` diff --git a/packages/client/src/api/hermes/files.ts b/packages/client/src/api/hermes/files.ts index 4e76da7..9bd027d 100644 --- a/packages/client/src/api/hermes/files.ts +++ b/packages/client/src/api/hermes/files.ts @@ -1,4 +1,4 @@ -import { request, getApiKey, getBaseUrlValue } from '../client' +import { request, getActiveProfileName, getApiKey, getBaseUrlValue } from '../client' export interface FileEntry { name: string @@ -83,6 +83,8 @@ export async function uploadFiles(targetDir: string, files: File[]): Promise<{ n const headers: Record = {} const token = getApiKey() if (token) headers['Authorization'] = `Bearer ${token}` + const profileName = getActiveProfileName() + if (profileName) headers['X-Hermes-Profile'] = profileName const res = await fetch(url, { method: 'POST', headers, body: formData }) if (!res.ok) { @@ -97,6 +99,8 @@ export function getFileDownloadUrl(relativePath: string, fileName?: string): str const base = getBaseUrlValue() const params = new URLSearchParams({ path: relativePath }) if (fileName) params.set('name', fileName) + const profileName = getActiveProfileName() + if (profileName) params.set('profile', profileName) const token = getApiKey() if (token) params.set('token', token) return `${base}/api/hermes/download?${params.toString()}` diff --git a/packages/client/src/components/hermes/chat/MessageItem.vue b/packages/client/src/components/hermes/chat/MessageItem.vue index 3f1982a..c6509bf 100644 --- a/packages/client/src/components/hermes/chat/MessageItem.vue +++ b/packages/client/src/components/hermes/chat/MessageItem.vue @@ -3,8 +3,7 @@ import type { Message, ContentBlock } from "@/stores/hermes/chat"; import { computed, onBeforeUnmount, onMounted, ref, watchEffect } from "vue"; import { useI18n } from "vue-i18n"; import { useMessage } from "naive-ui"; -import { downloadFile } from "@/api/hermes/download"; - import { getApiKey } from "@/api/client"; +import { downloadFile, getDownloadUrl } from "@/api/hermes/download"; import { copyToClipboard } from "@/utils/clipboard"; import MarkdownRenderer from "./MarkdownRenderer.vue"; import { parseThinking, countThinkingChars } from "@/utils/thinking-parser"; @@ -167,13 +166,6 @@ const contentFiles = computed(() => { }); }); -// Generate download URL with auth token -function getDownloadUrl(path: string, name: string): string { - const token = getApiKey(); - const base = `/api/hermes/download?path=${encodeURIComponent(path)}&name=${encodeURIComponent(name)}`; - return token ? `${base}&token=${encodeURIComponent(token)}` : base; -} - function getContentFileUrl(file: DisplayContentFile): string { if (file.url) return file.url return file.path ? getDownloadUrl(file.path, file.name) : '' diff --git a/packages/client/src/components/hermes/jobs/JobRunHistory.vue b/packages/client/src/components/hermes/jobs/JobRunHistory.vue index f65a6b4..90be26d 100644 --- a/packages/client/src/components/hermes/jobs/JobRunHistory.vue +++ b/packages/client/src/components/hermes/jobs/JobRunHistory.vue @@ -9,6 +9,7 @@ import MarkdownRenderer from '@/components/hermes/chat/MarkdownRenderer.vue' const props = defineProps<{ selectedJobId: string | null jobNameMap: Record + profileKey: string }>() const { t } = useI18n() @@ -66,7 +67,7 @@ function getJobName(jobId: string): string { return props.jobNameMap[jobId] || jobId } -watch(() => props.selectedJobId, () => { +watch(() => [props.selectedJobId, props.profileKey], () => { expandedContent.value = {} fetchRuns() }, { immediate: true }) diff --git a/packages/client/src/stores/hermes/chat.ts b/packages/client/src/stores/hermes/chat.ts index 35e77ee..5de3044 100644 --- a/packages/client/src/stores/hermes/chat.ts +++ b/packages/client/src/stores/hermes/chat.ts @@ -1,6 +1,7 @@ import { startRunViaSocket, resumeSession, registerSessionHandlers, unregisterSessionHandlers, getChatRunSocket, respondToolApproval, onPeerUserMessage, type RunEvent, type ContentBlock as ContentBlockImport } from '@/api/hermes/chat' import { deleteSession as deleteSessionApi, fetchSession, fetchSessions, setSessionModel, type HermesMessage, type SessionSummary } from '@/api/hermes/sessions' -import { getApiKey } from '@/api/client' +import { getActiveProfileName } from '@/api/client' +import { getDownloadUrl } from '@/api/hermes/download' import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { useAppStore } from './app' @@ -101,10 +102,14 @@ async function uploadFiles(attachments: Attachment[]): Promise<{ name: string; p if (att.file) formData.append('file', att.file, att.name) } const token = localStorage.getItem('hermes_api_key') || '' + const profileName = getActiveProfileName() + const headers: Record = {} + if (token) headers.Authorization = `Bearer ${token}` + if (profileName) headers['X-Hermes-Profile'] = profileName const res = await fetch('/upload', { method: 'POST', body: formData, - headers: token ? { Authorization: `Bearer ${token}` } : {}, + headers, }) if (!res.ok) throw new Error(`Upload failed: ${res.status}`) const data = await res.json() as { files: { name: string; path: string }[] } @@ -1040,10 +1045,8 @@ export const useChatStore = defineStore('chat', () => { const uploaded = await uploadFiles(attachments) // Update attachment URLs on the user message for display - const token = getApiKey() const urlMap = new Map(uploaded.map(f => { - const base = `/api/hermes/download?path=${encodeURIComponent(f.path)}&name=${encodeURIComponent(f.name)}` - return [f.name, token ? `${base}&token=${encodeURIComponent(token)}` : base] + return [f.name, getDownloadUrl(f.path, f.name)] })) if (shouldQueue && userMsg.attachments) { userMsg.attachments = userMsg.attachments.map(a => { diff --git a/packages/client/src/views/hermes/JobsView.vue b/packages/client/src/views/hermes/JobsView.vue index 8566420..f20d0d0 100644 --- a/packages/client/src/views/hermes/JobsView.vue +++ b/packages/client/src/views/hermes/JobsView.vue @@ -1,17 +1,20 @@