Scope files jobs and plugins to request profile
This commit is contained in:
@@ -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')
|
||||
}
|
||||
|
||||
|
||||
@@ -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()}`
|
||||
|
||||
@@ -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<string, string> = {}
|
||||
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()}`
|
||||
|
||||
@@ -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<DisplayContentFile[] | null>(() => {
|
||||
});
|
||||
});
|
||||
|
||||
// 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) : ''
|
||||
|
||||
@@ -9,6 +9,7 @@ import MarkdownRenderer from '@/components/hermes/chat/MarkdownRenderer.vue'
|
||||
const props = defineProps<{
|
||||
selectedJobId: string | null
|
||||
jobNameMap: Record<string, string>
|
||||
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 })
|
||||
|
||||
@@ -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<string, string> = {}
|
||||
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 => {
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { NButton, NSpin } from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import JobsPanel from '@/components/hermes/jobs/JobsPanel.vue'
|
||||
import JobRunHistory from '@/components/hermes/jobs/JobRunHistory.vue'
|
||||
import JobFormModal from '@/components/hermes/jobs/JobFormModal.vue'
|
||||
import { useJobsStore } from '@/stores/hermes/jobs'
|
||||
import { useProfilesStore } from '@/stores/hermes/profiles'
|
||||
|
||||
const { t } = useI18n()
|
||||
const jobsStore = useJobsStore()
|
||||
const profilesStore = useProfilesStore()
|
||||
const showModal = ref(false)
|
||||
const editingJob = ref<string | null>(null)
|
||||
const selectedJobId = ref<string | null>(null)
|
||||
const activeProfileName = computed(() => profilesStore.activeProfileName || 'default')
|
||||
|
||||
const jobNameMap = computed(() => {
|
||||
const map: Record<string, string> = {}
|
||||
@@ -22,9 +25,11 @@ const jobNameMap = computed(() => {
|
||||
return map
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
jobsStore.fetchJobs()
|
||||
})
|
||||
watch(activeProfileName, () => {
|
||||
selectedJobId.value = null
|
||||
jobsStore.jobs = []
|
||||
void jobsStore.fetchJobs()
|
||||
}, { immediate: true })
|
||||
|
||||
function openCreateModal() {
|
||||
editingJob.value = null
|
||||
@@ -80,6 +85,7 @@ function handleSelectJob(jobId: string | null) {
|
||||
<JobRunHistory
|
||||
:selected-job-id="selectedJobId"
|
||||
:job-name-map="jobNameMap"
|
||||
:profile-key="activeProfileName"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { NAlert, NButton, NEmpty, NInput, NSelect, NSpin, NTag, useMessage } from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { fetchPlugins, type HermesPluginInfo, type HermesPluginsMetadata } from '@/api/hermes/plugins'
|
||||
import { useProfilesStore } from '@/stores/hermes/profiles'
|
||||
|
||||
const { t, te } = useI18n()
|
||||
const message = useMessage()
|
||||
const profilesStore = useProfilesStore()
|
||||
|
||||
const plugins = ref<HermesPluginInfo[]>([])
|
||||
const warnings = ref<string[]>([])
|
||||
@@ -111,7 +113,12 @@ async function copyCommand(plugin: HermesPluginInfo) {
|
||||
message.success(t('plugins.commandCopied'))
|
||||
}
|
||||
|
||||
onMounted(loadPlugins)
|
||||
watch(() => profilesStore.activeProfileName || 'default', () => {
|
||||
plugins.value = []
|
||||
warnings.value = []
|
||||
metadata.value = null
|
||||
void loadPlugins()
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
Reference in New Issue
Block a user