feat: implement Home view with image generation
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import api from './index'
|
||||
|
||||
export interface PromptOptimizeRequest {
|
||||
prompt: string
|
||||
mode: 'text2image' | 'image2image'
|
||||
original_description?: string
|
||||
}
|
||||
|
||||
export interface ImageGenerateRequest {
|
||||
prompt: string
|
||||
optimized_prompt: string
|
||||
mode: 'text2image' | 'image2image'
|
||||
model: string
|
||||
input_image_b64?: string
|
||||
}
|
||||
|
||||
export const imagesApi = {
|
||||
optimizePrompt(data: PromptOptimizeRequest) {
|
||||
return api.post<{ optimized_prompt: string }>('/images/optimize', data)
|
||||
},
|
||||
generateImage(data: ImageGenerateRequest) {
|
||||
return api.post<{ image_url: string; model: string }>('/images/generate', data)
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<nav class="bg-white shadow">
|
||||
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
|
||||
<h1 class="text-xl font-bold">ImageCreator</h1>
|
||||
<div class="flex gap-4">
|
||||
<router-link to="/" class="text-blue-500">生成</router-link>
|
||||
<router-link to="/history" class="text-blue-500">历史</router-link>
|
||||
<n-button size="small" @click="handleLogout">退出</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
||||
<n-tabs v-model:value="activeMode" type="segment">
|
||||
<n-tab-pane name="text2image" tab="文生图" />
|
||||
<n-tab-pane name="image2image" tab="图生图" />
|
||||
</n-tabs>
|
||||
|
||||
<div class="mt-6 grid grid-cols-2 gap-6">
|
||||
<div>
|
||||
<n-card title="提示词输入">
|
||||
<n-input
|
||||
v-model:value="originalPrompt"
|
||||
type="textarea"
|
||||
placeholder="描述你想要生成的图片..."
|
||||
:rows="4"
|
||||
/>
|
||||
<div class="mt-4">
|
||||
<n-button type="primary" block @click="handleOptimize" :loading="optimizing">
|
||||
优化提示词
|
||||
</n-button>
|
||||
</div>
|
||||
<div v-if="optimizedPrompt" class="mt-4">
|
||||
<n-divider>优化后</n-divider>
|
||||
<p class="text-gray-700 whitespace-pre-wrap">{{ optimizedPrompt }}</p>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<n-card v-if="activeMode === 'image2image'" title="上传参考图片" class="mt-4">
|
||||
<n-upload
|
||||
accept="image/*"
|
||||
:max-size="10 * 1024 * 1024"
|
||||
@change="handleImageUpload"
|
||||
@error="handleUploadError"
|
||||
>
|
||||
<n-button>选择图片</n-button>
|
||||
</n-upload>
|
||||
</n-card>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<n-card title="生成结果">
|
||||
<div v-if="generating" class="flex justify-center py-8">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
<div v-else-if="generatedImageUrl" class="text-center">
|
||||
<img :src="generatedImageUrl" class="max-w-full rounded" alt="Generated" />
|
||||
<n-button type="primary" class="mt-4" tag="a" :href="generatedImageUrl" download>
|
||||
下载图片
|
||||
</n-button>
|
||||
</div>
|
||||
<div v-else class="text-center text-gray-400 py-8">
|
||||
生成的图片将显示在这里
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<div class="mt-4">
|
||||
<n-button
|
||||
type="primary"
|
||||
block
|
||||
size="large"
|
||||
@click="handleGenerate"
|
||||
:disabled="!optimizedPrompt"
|
||||
:loading="generating"
|
||||
>
|
||||
生成图片
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { NCard, NInput, NButton, NTabs, NTabPane, NDivider, NSpin, NUpload, useMessage, useDialog } from 'naive-ui'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { imagesApi } from '../api/images'
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const activeMode = ref<'text2image' | 'image2image'>('text2image')
|
||||
const originalPrompt = ref('')
|
||||
const optimizedPrompt = ref('')
|
||||
const generatedImageUrl = ref('')
|
||||
const inputImageB64 = ref('')
|
||||
const optimizing = ref(false)
|
||||
const generating = ref(false)
|
||||
|
||||
const handleLogout = () => {
|
||||
authStore.logout()
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
const handleOptimize = async () => {
|
||||
if (!originalPrompt.value.trim()) {
|
||||
message.warning('请输入提示词')
|
||||
return
|
||||
}
|
||||
optimizing.value = true
|
||||
try {
|
||||
const response = await imagesApi.optimizePrompt({
|
||||
prompt: originalPrompt.value,
|
||||
mode: activeMode.value,
|
||||
})
|
||||
optimizedPrompt.value = response.data.optimized_prompt
|
||||
} catch (error) {
|
||||
message.error('优化失败')
|
||||
} finally {
|
||||
optimizing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageUpload = (options: any) => {
|
||||
const file = options.file.file
|
||||
if (!file) return
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
const result = e.target?.result as string
|
||||
inputImageB64.value = result.split(',')[1] // Remove data URL prefix
|
||||
message.success('图片上传成功')
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
|
||||
const handleUploadError = () => {
|
||||
message.error('图片上传失败')
|
||||
}
|
||||
|
||||
const handleGenerate = async () => {
|
||||
if (!optimizedPrompt.value) {
|
||||
message.warning('请先优化提示词')
|
||||
return
|
||||
}
|
||||
|
||||
generating.value = true
|
||||
generatedImageUrl.value = ''
|
||||
|
||||
try {
|
||||
const response = await imagesApi.generateImage({
|
||||
prompt: originalPrompt.value,
|
||||
optimized_prompt: optimizedPrompt.value,
|
||||
mode: activeMode.value,
|
||||
model: 'gemini-2.5-flash-image',
|
||||
input_image_b64: activeMode.value === 'image2image' ? inputImageB64.value : undefined,
|
||||
})
|
||||
|
||||
// Construct full URL for image
|
||||
generatedImageUrl.value = `/uploads/${response.data.image_url.split('/uploads/')[1]}`
|
||||
} catch (error) {
|
||||
message.error('生成失败')
|
||||
} finally {
|
||||
generating.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user