feat: implement Home view with image generation

This commit is contained in:
Claude
2026-03-24 15:23:32 +08:00
parent 3aa9f3425f
commit 2a50b0685c
2 changed files with 196 additions and 0 deletions
+24
View File
@@ -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)
},
}
+172
View File
@@ -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>