mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-25 13:04:09 -03:00
feat: support reverse-proxy URL subpaths (llama-swap, SwarmUI) (#1122)
This commit is contained in:
@@ -58,6 +58,7 @@
|
||||
import { ref, computed, watch, nextTick, onUnmounted } from 'vue'
|
||||
import ModalWrapper from '../lora-pool/modals/ModalWrapper.vue'
|
||||
import type { LoraItem } from '../../composables/types'
|
||||
import { lmApiUrl } from '@/utils/basePath'
|
||||
|
||||
interface LoraListItem {
|
||||
index: number
|
||||
@@ -131,7 +132,7 @@ const selectLora = (index: number) => {
|
||||
// in the Vue widgets build, so we need to use the full path with /api prefix
|
||||
const customPreviewUrlResolver = async (modelName: string) => {
|
||||
const response = await fetch(
|
||||
`/api/lm/loras/preview-url?name=${encodeURIComponent(modelName)}&license_flags=true`
|
||||
lmApiUrl(`/api/lm/loras/preview-url?name=${encodeURIComponent(modelName)}&license_flags=true`)
|
||||
)
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch preview URL')
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import type { LoraEntry } from '../../composables/types'
|
||||
import { lmApiUrl } from '@/utils/basePath'
|
||||
|
||||
const props = defineProps<{
|
||||
loras: LoraEntry[]
|
||||
@@ -48,7 +49,7 @@ const previewUrls = ref<Record<string, string>>({})
|
||||
// Fetch preview URL for a lora using API
|
||||
const fetchPreviewUrl = async (loraName: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/lm/loras/preview-url?name=${encodeURIComponent(loraName)}`)
|
||||
const response = await fetch(lmApiUrl(`/api/lm/loras/preview-url?name=${encodeURIComponent(loraName)}`))
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import type { ComponentWidget, CyclerConfig, LoraPoolConfig } from './types'
|
||||
import { lmApiUrl } from '@/utils/basePath'
|
||||
|
||||
export interface CyclerLoraItem {
|
||||
file_name: string
|
||||
@@ -173,7 +174,7 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
||||
requestBody.pool_config = poolConfig.filters
|
||||
}
|
||||
|
||||
const response = await fetch('/api/lm/loras/cycler-list', {
|
||||
const response = await fetch(lmApiUrl('/api/lm/loras/cycler-list'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { ref } from 'vue'
|
||||
import type { BaseModelOption, TagOption, FolderTreeNode, LoraItem } from './types'
|
||||
import { lmApiUrl } from '@/utils/basePath'
|
||||
|
||||
export function useLoraPoolApi() {
|
||||
const isLoading = ref(false)
|
||||
|
||||
const fetchBaseModels = async (limit = 50): Promise<BaseModelOption[]> => {
|
||||
try {
|
||||
const response = await fetch(`/api/lm/loras/base-models?limit=${limit}`)
|
||||
const response = await fetch(lmApiUrl(`/api/lm/loras/base-models?limit=${limit}`))
|
||||
const data = await response.json()
|
||||
return data.base_models || []
|
||||
} catch (error) {
|
||||
@@ -17,7 +18,7 @@ export function useLoraPoolApi() {
|
||||
|
||||
const fetchTags = async (limit = 0): Promise<TagOption[]> => {
|
||||
try {
|
||||
const response = await fetch(`/api/lm/loras/top-tags?limit=${limit}`)
|
||||
const response = await fetch(lmApiUrl(`/api/lm/loras/top-tags?limit=${limit}`))
|
||||
const data = await response.json()
|
||||
return data.tags || []
|
||||
} catch (error) {
|
||||
@@ -28,7 +29,7 @@ export function useLoraPoolApi() {
|
||||
|
||||
const fetchFolderTree = async (): Promise<FolderTreeNode[]> => {
|
||||
try {
|
||||
const response = await fetch('/api/lm/loras/unified-folder-tree')
|
||||
const response = await fetch(lmApiUrl('/api/lm/loras/unified-folder-tree'))
|
||||
const data = await response.json()
|
||||
return transformFolderTree(data.tree || {})
|
||||
} catch (error) {
|
||||
@@ -102,7 +103,7 @@ export function useLoraPoolApi() {
|
||||
urlParams.set('name_pattern_use_regex', String(params.namePatternsUseRegex))
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/lm/loras/list?${urlParams}`)
|
||||
const response = await fetch(lmApiUrl(`/api/lm/loras/list?${urlParams}`))
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import type { ComponentWidget, RandomizerConfig, LoraEntry } from './types'
|
||||
import { lmApiUrl } from '@/utils/basePath'
|
||||
|
||||
export function useLoraRandomizerState(widget: ComponentWidget<RandomizerConfig>) {
|
||||
// Flag to prevent infinite loops during config restoration
|
||||
@@ -160,7 +161,7 @@ export function useLoraRandomizerState(widget: ComponentWidget<RandomizerConfig>
|
||||
}
|
||||
|
||||
// Call API endpoint
|
||||
const response = await fetch('/api/lm/loras/random-sample', {
|
||||
const response = await fetch(lmApiUrl('/api/lm/loras/random-sample'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export function getLmBasePath(): string {
|
||||
const { pathname } = window.location;
|
||||
return pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
|
||||
}
|
||||
|
||||
export function lmApiUrl(path: string): string {
|
||||
return `${getLmBasePath()}${path}`;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { getLmBasePath, lmApiUrl } from '@/utils/basePath'
|
||||
|
||||
const originalPathname = window.location.pathname
|
||||
|
||||
function setPathname(pathname: string) {
|
||||
window.history.replaceState(null, '', pathname)
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
setPathname(originalPathname)
|
||||
})
|
||||
|
||||
describe('getLmBasePath', () => {
|
||||
it('returns an empty string when ComfyUI is served at the root', () => {
|
||||
setPathname('/')
|
||||
|
||||
expect(getLmBasePath()).toBe('')
|
||||
})
|
||||
|
||||
it('strips the trailing slash from a subpath prefix', () => {
|
||||
setPathname('/comfyui/')
|
||||
|
||||
expect(getLmBasePath()).toBe('/comfyui')
|
||||
})
|
||||
|
||||
it('keeps a prefix without a trailing slash as-is', () => {
|
||||
setPathname('/ComfyBackendDirect')
|
||||
|
||||
expect(getLmBasePath()).toBe('/ComfyBackendDirect')
|
||||
})
|
||||
})
|
||||
|
||||
describe('lmApiUrl', () => {
|
||||
it('leaves root-absolute paths unchanged at the root', () => {
|
||||
setPathname('/')
|
||||
|
||||
expect(lmApiUrl('/api/lm/loras/list')).toBe('/api/lm/loras/list')
|
||||
})
|
||||
|
||||
it('prepends the subpath prefix to root-absolute paths', () => {
|
||||
setPathname('/comfyui/')
|
||||
|
||||
expect(lmApiUrl('/api/lm/loras/list')).toBe('/comfyui/api/lm/loras/list')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user