feat(integrations): import prompt-garden examples
This commit is contained in:
1 parent
23f54d7a13
commit
b034b56ff6
2 files changed
+551
-15
No files matched your search
@@ -104,6 +104,11 @@ type FetchedPrompt = {
|
||||
promptText?: string
|
||||
promptMessages?: ConversationMessage[]
|
||||
variables: Array<{ name: string; defaultValue?: string }>
|
||||
examples: Array<{
|
||||
id?: string
|
||||
parameters?: Record<string, string>
|
||||
inputImages?: string[]
|
||||
}>
|
||||
}
|
||||
|
||||
type ImportedVariable = {
|
||||
@@ -117,6 +122,29 @@ type TemporaryVariablesSessionApi = {
|
||||
clearTemporaryVariables: () => void
|
||||
}
|
||||
|
||||
const getTemporaryVariablesSession = (
|
||||
targetKey: SupportedSubModeKey,
|
||||
api: {
|
||||
proMultiMessageSession: ProMultiMessageSessionApi
|
||||
proVariableSession: ProVariableSessionApi
|
||||
imageText2ImageSession: ImageText2ImageSessionApi
|
||||
imageImage2ImageSession: ImageImage2ImageSessionApi
|
||||
},
|
||||
): TemporaryVariablesSessionApi | null => {
|
||||
switch (targetKey) {
|
||||
case 'pro-multi':
|
||||
return api.proMultiMessageSession
|
||||
case 'pro-variable':
|
||||
return api.proVariableSession
|
||||
case 'image-text2image':
|
||||
return api.imageText2ImageSession
|
||||
case 'image-image2image':
|
||||
return api.imageImage2ImageSession
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const ensureImportedTemporaryVariables = (
|
||||
targetKey: SupportedSubModeKey,
|
||||
api: {
|
||||
@@ -129,20 +157,7 @@ const ensureImportedTemporaryVariables = (
|
||||
variables: ImportedVariable[]
|
||||
},
|
||||
) => {
|
||||
const session: TemporaryVariablesSessionApi | null = (() => {
|
||||
switch (targetKey) {
|
||||
case 'pro-multi':
|
||||
return api.proMultiMessageSession
|
||||
case 'pro-variable':
|
||||
return api.proVariableSession
|
||||
case 'image-text2image':
|
||||
return api.imageText2ImageSession
|
||||
case 'image-image2image':
|
||||
return api.imageImage2ImageSession
|
||||
default:
|
||||
return null
|
||||
}
|
||||
})()
|
||||
const session = getTemporaryVariablesSession(targetKey, api)
|
||||
|
||||
if (!session) return
|
||||
|
||||
@@ -306,12 +321,48 @@ const fetchPromptFromGarden = async (opts: {
|
||||
})
|
||||
.filter((v) => isValidVariableName(v.name))
|
||||
|
||||
const assets = isPlainObject((data as any).assets) ? ((data as any).assets as Record<string, unknown>) : null
|
||||
const rawExamples = assets && Array.isArray((assets as any).examples) ? ((assets as any).examples as unknown[]) : []
|
||||
const examples = rawExamples
|
||||
.map((ex): { id?: string; parameters?: Record<string, string>; inputImages?: string[] } => {
|
||||
if (!isPlainObject(ex)) return {}
|
||||
|
||||
const id = typeof ex.id === 'string' ? ex.id.trim() : undefined
|
||||
|
||||
const parameters = (() => {
|
||||
const p = (ex as any).parameters
|
||||
if (!isPlainObject(p)) return undefined
|
||||
const out: Record<string, string> = {}
|
||||
for (const [k, v] of Object.entries(p)) {
|
||||
const key = String(k || '').trim()
|
||||
if (!isValidVariableName(key)) continue
|
||||
if (v === undefined || v === null) continue
|
||||
out[key] = String(v)
|
||||
}
|
||||
return Object.keys(out).length ? out : undefined
|
||||
})()
|
||||
|
||||
const inputImages = Array.isArray((ex as any).inputImages)
|
||||
? ((ex as any).inputImages as unknown[])
|
||||
.map((u) => (typeof u === 'string' ? u.trim() : ''))
|
||||
.filter(Boolean)
|
||||
: undefined
|
||||
|
||||
return {
|
||||
id,
|
||||
parameters,
|
||||
inputImages,
|
||||
}
|
||||
})
|
||||
.filter((ex) => Boolean(ex.parameters) || (Array.isArray(ex.inputImages) && ex.inputImages.length > 0))
|
||||
|
||||
return {
|
||||
optimizerTargetKey,
|
||||
promptFormat: format,
|
||||
promptText,
|
||||
promptMessages,
|
||||
variables,
|
||||
examples,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +376,72 @@ const fetchPromptFromGarden = async (opts: {
|
||||
return parseV1(data)
|
||||
}
|
||||
|
||||
const resolveGardenUrl = (opts: { gardenBaseUrl: string | null; url: string }): string | null => {
|
||||
const raw = String(opts.url || '').trim()
|
||||
if (!raw) return null
|
||||
if (/^https?:\/\//u.test(raw)) return raw
|
||||
|
||||
const base = opts.gardenBaseUrl ? normalizeBaseUrl(opts.gardenBaseUrl) : null
|
||||
if (!base) return null
|
||||
|
||||
try {
|
||||
return new URL(raw, `${base}/`).toString()
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const fetchImageAsBase64 = async (absoluteUrl: string): Promise<{ b64: string; mimeType: string } | null> => {
|
||||
const resp = await fetch(absoluteUrl, { method: 'GET' })
|
||||
if (!resp.ok) {
|
||||
throw new Error(`Example image request failed: ${resp.status}`)
|
||||
}
|
||||
|
||||
const headerType = resp.headers.get('content-type')
|
||||
const mimeType = typeof headerType === 'string' ? headerType.split(';')[0].trim() : ''
|
||||
|
||||
// Tests run in Node where Buffer is available; browsers use FileReader.
|
||||
const maybeBuffer = (globalThis as any).Buffer as any
|
||||
if (maybeBuffer && typeof maybeBuffer.from === 'function') {
|
||||
const ab = await resp.arrayBuffer()
|
||||
const b64 = maybeBuffer.from(ab).toString('base64')
|
||||
return { b64, mimeType }
|
||||
}
|
||||
|
||||
if (typeof FileReader === 'undefined') {
|
||||
throw new Error('FileReader is not available to decode images')
|
||||
}
|
||||
|
||||
const blob = await resp.blob()
|
||||
const actualMime = blob.type || mimeType
|
||||
const dataUrl = await new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onerror = () => reject(new Error('Failed to read image blob'))
|
||||
reader.onload = () => resolve(String(reader.result || ''))
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
|
||||
const match = dataUrl.match(/^data:.*?;base64,(.*)$/u)
|
||||
const b64 = match ? match[1] : ''
|
||||
if (!b64) {
|
||||
throw new Error('Failed to decode image data URL')
|
||||
}
|
||||
return { b64, mimeType: actualMime }
|
||||
}
|
||||
|
||||
const pickImportedExample = (
|
||||
examples: FetchedPrompt['examples'],
|
||||
exampleId: string | null,
|
||||
): FetchedPrompt['examples'][number] | null => {
|
||||
if (!Array.isArray(examples) || examples.length === 0) return null
|
||||
const id = (exampleId || '').trim()
|
||||
if (id) {
|
||||
const found = examples.find((ex) => (ex.id || '').trim() === id)
|
||||
if (found) return found
|
||||
}
|
||||
return examples[0] || null
|
||||
}
|
||||
|
||||
const clearSessionForExternalImport = (targetKey: SupportedSubModeKey, api: {
|
||||
basicSystemSession: BasicSystemSessionApi
|
||||
basicUserSession: BasicUserSessionApi
|
||||
@@ -453,6 +570,8 @@ export function useAppPromptGardenImport(options: AppPromptGardenImportOptions)
|
||||
const importCode = getQueryString(query, 'importCode')
|
||||
if (!importCode) return
|
||||
|
||||
const exampleId = getQueryString(query, 'exampleId')
|
||||
|
||||
inFlight.value = true
|
||||
isLoadingExternalData.value = true
|
||||
try {
|
||||
@@ -461,6 +580,8 @@ export function useAppPromptGardenImport(options: AppPromptGardenImportOptions)
|
||||
importCode,
|
||||
})
|
||||
|
||||
const importedExample = pickImportedExample(fetched.examples, exampleId)
|
||||
|
||||
const targetKey = resolveTargetKey(query, currentRoute.path, fetched.optimizerTargetKey)
|
||||
|
||||
// If caller opened the wrong workspace, navigate first.
|
||||
@@ -539,6 +660,48 @@ export function useAppPromptGardenImport(options: AppPromptGardenImportOptions)
|
||||
}
|
||||
)
|
||||
|
||||
// If the imported prompt provides a full example, apply the example's parameter values
|
||||
// (and input image for image2image) so the user can reproduce the result directly.
|
||||
if (importedExample) {
|
||||
const session = getTemporaryVariablesSession(targetKey, {
|
||||
proMultiMessageSession,
|
||||
proVariableSession,
|
||||
imageText2ImageSession,
|
||||
imageImage2ImageSession,
|
||||
})
|
||||
|
||||
const importedVariableNames = new Set(
|
||||
fetched.variables
|
||||
.map((v) => String(v?.name || '').trim())
|
||||
.filter((name) => isValidVariableName(name))
|
||||
)
|
||||
|
||||
if (session && importedExample.parameters) {
|
||||
for (const [key, value] of Object.entries(importedExample.parameters)) {
|
||||
if (!importedVariableNames.has(key)) continue
|
||||
session.setTemporaryVariable(key, String(value))
|
||||
}
|
||||
}
|
||||
|
||||
if (targetKey === 'image-image2image' && Array.isArray(importedExample.inputImages) && importedExample.inputImages.length > 0) {
|
||||
const inputUrl = resolveGardenUrl({
|
||||
gardenBaseUrl,
|
||||
url: importedExample.inputImages[0],
|
||||
})
|
||||
if (inputUrl) {
|
||||
try {
|
||||
const img = await fetchImageAsBase64(inputUrl)
|
||||
if (img?.b64) {
|
||||
imageImage2ImageSession.updateInputImage(img.b64, img.mimeType)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[PromptGardenImport] Failed to load example input image:', e)
|
||||
toast.warning('示例输入图加载失败(请检查 Prompt Garden /prompt-assets 的 CORS 配置)')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Best-effort persist.
|
||||
try {
|
||||
if (targetKey === 'basic-system') await basicSystemSession.saveSession()
|
||||
@@ -552,7 +715,7 @@ export function useAppPromptGardenImport(options: AppPromptGardenImportOptions)
|
||||
}
|
||||
|
||||
// Remove import params to avoid re-import on refresh.
|
||||
const cleanedQuery = omitKeys(query, ['importCode', 'subModeKey'])
|
||||
const cleanedQuery = omitKeys(query, ['importCode', 'subModeKey', 'exampleId'])
|
||||
await router.replace({ path: router.currentRoute.value.path, query: cleanedQuery })
|
||||
await nextTick()
|
||||
|
||||
|
||||
@@ -596,6 +596,243 @@ describe('useAppPromptGardenImport', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('applies default example parameters into pro-variable temporary variables', async () => {
|
||||
const { pinia } = createTestPinia()
|
||||
|
||||
// Avoid console.warn from useToast (tests fail on console.warn).
|
||||
const createReactive = (): MessageReactive => ({
|
||||
destroy: () => {},
|
||||
} as unknown as MessageReactive)
|
||||
setGlobalMessageApi({
|
||||
success: vi.fn(() => createReactive()),
|
||||
error: vi.fn(() => createReactive()),
|
||||
warning: vi.fn(() => createReactive()),
|
||||
info: vi.fn(() => createReactive()),
|
||||
})
|
||||
|
||||
const basicSystemSession = useBasicSystemSession(pinia)
|
||||
const basicUserSession = useBasicUserSession(pinia)
|
||||
const proMultiMessageSession = useProMultiMessageSession(pinia)
|
||||
const proVariableSession = useProVariableSession(pinia)
|
||||
const imageText2ImageSession = useImageText2ImageSession(pinia)
|
||||
const imageImage2ImageSession = useImageImage2ImageSession(pinia)
|
||||
|
||||
// Seed existing values; example parameters should override them.
|
||||
proVariableSession.setTemporaryVariable('name', 'Bob')
|
||||
|
||||
const optimizerCurrentVersions = ref<PromptRecordChain['versions']>([makeDummyRecord()])
|
||||
const hasRestoredInitialState = ref(false)
|
||||
const isLoadingExternalData = ref(false)
|
||||
|
||||
const query: LocationQuery = {
|
||||
importCode: 'NB-PVAR-EX-001',
|
||||
}
|
||||
|
||||
const currentRoute = ref<RouteLocationNormalizedLoaded>(makeRoute('/basic/system', query))
|
||||
|
||||
let replaceResolve: (() => void) | undefined
|
||||
const replaceDone = new Promise<void>((resolve) => {
|
||||
replaceResolve = resolve
|
||||
})
|
||||
|
||||
const push: Router['push'] = vi.fn(async (to) => {
|
||||
applyNavigation(currentRoute, to)
|
||||
return undefined
|
||||
})
|
||||
|
||||
const replace: Router['replace'] = vi.fn(async (to) => {
|
||||
applyNavigation(currentRoute, to)
|
||||
replaceResolve?.()
|
||||
return undefined
|
||||
})
|
||||
|
||||
const router: Pick<Router, 'currentRoute' | 'push' | 'replace'> = {
|
||||
currentRoute,
|
||||
push,
|
||||
replace,
|
||||
}
|
||||
|
||||
const v1Payload = {
|
||||
schema: 'prompt-garden.prompt.v1',
|
||||
schemaVersion: 1,
|
||||
optimizerTarget: { subModeKey: 'pro-variable' },
|
||||
prompt: {
|
||||
format: 'text',
|
||||
text: 'Hello {{name}}',
|
||||
},
|
||||
variables: [{ name: 'name' }, { name: 'tone' }],
|
||||
assets: {
|
||||
examples: [
|
||||
{
|
||||
id: 'ex-001',
|
||||
parameters: {
|
||||
name: 'Alice',
|
||||
tone: 'friendly',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const fetchMock = vi.fn<
|
||||
(input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
|
||||
>(async () => {
|
||||
return new Response(JSON.stringify(v1Payload), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
})
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
const scope = effectScope()
|
||||
try {
|
||||
scope.run(() => {
|
||||
useAppPromptGardenImport({
|
||||
router,
|
||||
hasRestoredInitialState,
|
||||
isLoadingExternalData,
|
||||
gardenBaseUrl: 'http://garden.local',
|
||||
basicSystemSession,
|
||||
basicUserSession,
|
||||
proMultiMessageSession,
|
||||
proVariableSession,
|
||||
imageText2ImageSession,
|
||||
imageImage2ImageSession,
|
||||
optimizerCurrentVersions,
|
||||
})
|
||||
})
|
||||
|
||||
hasRestoredInitialState.value = true
|
||||
|
||||
await replaceDone
|
||||
await waitForCondition(() => isLoadingExternalData.value === false)
|
||||
|
||||
expect(currentRoute.value.path).toBe('/pro/variable')
|
||||
expect(proVariableSession.prompt).toBe('Hello {{name}}')
|
||||
|
||||
// Example parameters override existing values.
|
||||
expect(proVariableSession.getTemporaryVariable('name')).toBe('Alice')
|
||||
expect(proVariableSession.getTemporaryVariable('tone')).toBe('friendly')
|
||||
} finally {
|
||||
scope.stop()
|
||||
}
|
||||
})
|
||||
|
||||
it('applies selected exampleId parameters when provided', async () => {
|
||||
const { pinia } = createTestPinia()
|
||||
|
||||
// Avoid console.warn from useToast (tests fail on console.warn).
|
||||
const createReactive = (): MessageReactive => ({
|
||||
destroy: () => {},
|
||||
} as unknown as MessageReactive)
|
||||
setGlobalMessageApi({
|
||||
success: vi.fn(() => createReactive()),
|
||||
error: vi.fn(() => createReactive()),
|
||||
warning: vi.fn(() => createReactive()),
|
||||
info: vi.fn(() => createReactive()),
|
||||
})
|
||||
|
||||
const basicSystemSession = useBasicSystemSession(pinia)
|
||||
const basicUserSession = useBasicUserSession(pinia)
|
||||
const proMultiMessageSession = useProMultiMessageSession(pinia)
|
||||
const proVariableSession = useProVariableSession(pinia)
|
||||
const imageText2ImageSession = useImageText2ImageSession(pinia)
|
||||
const imageImage2ImageSession = useImageImage2ImageSession(pinia)
|
||||
|
||||
const optimizerCurrentVersions = ref<PromptRecordChain['versions']>([makeDummyRecord()])
|
||||
const hasRestoredInitialState = ref(false)
|
||||
const isLoadingExternalData = ref(false)
|
||||
|
||||
const query: LocationQuery = {
|
||||
importCode: 'NB-PVAR-EX-002',
|
||||
exampleId: 'ex-b',
|
||||
}
|
||||
|
||||
const currentRoute = ref<RouteLocationNormalizedLoaded>(makeRoute('/basic/system', query))
|
||||
|
||||
let replaceResolve: (() => void) | undefined
|
||||
const replaceDone = new Promise<void>((resolve) => {
|
||||
replaceResolve = resolve
|
||||
})
|
||||
|
||||
const push: Router['push'] = vi.fn(async (to) => {
|
||||
applyNavigation(currentRoute, to)
|
||||
return undefined
|
||||
})
|
||||
|
||||
const replace: Router['replace'] = vi.fn(async (to) => {
|
||||
applyNavigation(currentRoute, to)
|
||||
replaceResolve?.()
|
||||
return undefined
|
||||
})
|
||||
|
||||
const router: Pick<Router, 'currentRoute' | 'push' | 'replace'> = {
|
||||
currentRoute,
|
||||
push,
|
||||
replace,
|
||||
}
|
||||
|
||||
const v1Payload = {
|
||||
schema: 'prompt-garden.prompt.v1',
|
||||
schemaVersion: 1,
|
||||
optimizerTarget: { subModeKey: 'pro-variable' },
|
||||
prompt: {
|
||||
format: 'text',
|
||||
text: 'Hello {{name}}',
|
||||
},
|
||||
variables: [{ name: 'name' }],
|
||||
assets: {
|
||||
examples: [
|
||||
{ id: 'ex-a', parameters: { name: 'Alice' } },
|
||||
{ id: 'ex-b', parameters: { name: 'Charlie' } },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const fetchMock = vi.fn<
|
||||
(input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
|
||||
>(async () => {
|
||||
return new Response(JSON.stringify(v1Payload), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
})
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
const scope = effectScope()
|
||||
try {
|
||||
scope.run(() => {
|
||||
useAppPromptGardenImport({
|
||||
router,
|
||||
hasRestoredInitialState,
|
||||
isLoadingExternalData,
|
||||
gardenBaseUrl: 'http://garden.local',
|
||||
basicSystemSession,
|
||||
basicUserSession,
|
||||
proMultiMessageSession,
|
||||
proVariableSession,
|
||||
imageText2ImageSession,
|
||||
imageImage2ImageSession,
|
||||
optimizerCurrentVersions,
|
||||
})
|
||||
})
|
||||
|
||||
hasRestoredInitialState.value = true
|
||||
|
||||
await replaceDone
|
||||
await waitForCondition(() => isLoadingExternalData.value === false)
|
||||
|
||||
expect(currentRoute.value.path).toBe('/pro/variable')
|
||||
expect(proVariableSession.getTemporaryVariable('name')).toBe('Charlie')
|
||||
|
||||
// Import params removed from the URL.
|
||||
expect(currentRoute.value.query.importCode).toBeUndefined()
|
||||
expect(currentRoute.value.query.exampleId).toBeUndefined()
|
||||
} finally {
|
||||
scope.stop()
|
||||
}
|
||||
})
|
||||
|
||||
it('injects {{var}} placeholders into image temporary variables', async () => {
|
||||
const { pinia } = createTestPinia({
|
||||
// Image sessions require ImageStorageService to persist.
|
||||
@@ -725,6 +962,142 @@ describe('useAppPromptGardenImport', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('loads image2image example input image when present', async () => {
|
||||
const { pinia } = createTestPinia({
|
||||
imageStorageService: {
|
||||
getMetadata: async () => null,
|
||||
saveImage: async () => {},
|
||||
listAllMetadata: async () => [],
|
||||
deleteImages: async () => {},
|
||||
} as unknown as never,
|
||||
})
|
||||
|
||||
// Avoid console.warn from useToast (tests fail on console.warn).
|
||||
const createReactive = (): MessageReactive => ({
|
||||
destroy: () => {},
|
||||
} as unknown as MessageReactive)
|
||||
setGlobalMessageApi({
|
||||
success: vi.fn(() => createReactive()),
|
||||
error: vi.fn(() => createReactive()),
|
||||
warning: vi.fn(() => createReactive()),
|
||||
info: vi.fn(() => createReactive()),
|
||||
})
|
||||
|
||||
const basicSystemSession = useBasicSystemSession(pinia)
|
||||
const basicUserSession = useBasicUserSession(pinia)
|
||||
const proMultiMessageSession = useProMultiMessageSession(pinia)
|
||||
const proVariableSession = useProVariableSession(pinia)
|
||||
const imageText2ImageSession = useImageText2ImageSession(pinia)
|
||||
const imageImage2ImageSession = useImageImage2ImageSession(pinia)
|
||||
|
||||
const optimizerCurrentVersions = ref<PromptRecordChain['versions']>([makeDummyRecord()])
|
||||
const hasRestoredInitialState = ref(false)
|
||||
const isLoadingExternalData = ref(false)
|
||||
|
||||
const query: LocationQuery = {
|
||||
importCode: 'NB-I2I-001',
|
||||
subModeKey: 'image-image2image',
|
||||
}
|
||||
|
||||
const currentRoute = ref<RouteLocationNormalizedLoaded>(makeRoute('/basic/system', query))
|
||||
|
||||
let replaceResolve: (() => void) | undefined
|
||||
const replaceDone = new Promise<void>((resolve) => {
|
||||
replaceResolve = resolve
|
||||
})
|
||||
|
||||
const push: Router['push'] = vi.fn(async (to) => {
|
||||
applyNavigation(currentRoute, to)
|
||||
return undefined
|
||||
})
|
||||
|
||||
const replace: Router['replace'] = vi.fn(async (to) => {
|
||||
applyNavigation(currentRoute, to)
|
||||
replaceResolve?.()
|
||||
return undefined
|
||||
})
|
||||
|
||||
const router: Pick<Router, 'currentRoute' | 'push' | 'replace'> = {
|
||||
currentRoute,
|
||||
push,
|
||||
replace,
|
||||
}
|
||||
|
||||
const inputAssetPath = '/prompt-assets/NB-I2I-001/examples/ex-001/01.png'
|
||||
const inputAssetUrl = `http://garden.local${inputAssetPath}`
|
||||
|
||||
const v1Payload = {
|
||||
schema: 'prompt-garden.prompt.v1',
|
||||
schemaVersion: 1,
|
||||
optimizerTarget: { subModeKey: 'image-image2image' },
|
||||
prompt: { format: 'text', text: 'Transform the image' },
|
||||
variables: [],
|
||||
assets: {
|
||||
examples: [
|
||||
{
|
||||
id: 'ex-001',
|
||||
inputImages: [inputAssetPath],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const fetchMock = vi.fn<
|
||||
(input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
|
||||
>(async (input) => {
|
||||
const url = String(input)
|
||||
if (url === 'http://garden.local/api/prompt-source/NB-I2I-001') {
|
||||
return new Response(JSON.stringify(v1Payload), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
})
|
||||
}
|
||||
if (url === inputAssetUrl) {
|
||||
return new Response(new Uint8Array([0, 1, 2, 3]), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'image/png' },
|
||||
})
|
||||
}
|
||||
throw new Error(`Unexpected fetch URL: ${url}`)
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
const scope = effectScope()
|
||||
try {
|
||||
scope.run(() => {
|
||||
useAppPromptGardenImport({
|
||||
router,
|
||||
hasRestoredInitialState,
|
||||
isLoadingExternalData,
|
||||
gardenBaseUrl: 'http://garden.local',
|
||||
basicSystemSession,
|
||||
basicUserSession,
|
||||
proMultiMessageSession,
|
||||
proVariableSession,
|
||||
imageText2ImageSession,
|
||||
imageImage2ImageSession,
|
||||
optimizerCurrentVersions,
|
||||
})
|
||||
})
|
||||
|
||||
hasRestoredInitialState.value = true
|
||||
|
||||
await replaceDone
|
||||
await waitForCondition(() => isLoadingExternalData.value === false)
|
||||
|
||||
expect(currentRoute.value.path).toBe('/image/image2image')
|
||||
expect(imageImage2ImageSession.originalPrompt).toBe('Transform the image')
|
||||
|
||||
// [0,1,2,3] -> AAECAw==
|
||||
expect(imageImage2ImageSession.inputImageB64).toBe('AAECAw==')
|
||||
expect(imageImage2ImageSession.inputImageMime).toBe('image/png')
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||||
} finally {
|
||||
scope.stop()
|
||||
}
|
||||
})
|
||||
|
||||
it('clears temporary variables when importing an empty variable list into pro-variable', async () => {
|
||||
const { pinia } = createTestPinia()
|
||||
|
||||
|
||||
Reference in new issue
Block a user