Compare commits
2 Commits
77ccaf8acd
...
0a0eb378fa
| Author | SHA1 | Date |
|---|---|---|
|
|
0a0eb378fa | |
|
|
faefda3ea2 |
|
|
@ -1,3 +1,132 @@
|
||||||
.index{
|
.index {
|
||||||
font-size: 1rem;
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用步骤容器 */
|
||||||
|
.step-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 80rpx 60rpx;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标题样式 */
|
||||||
|
.step-title {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 描述文字 */
|
||||||
|
.step-desc {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 60rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 上传按钮 */
|
||||||
|
.upload-btn {
|
||||||
|
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50rpx;
|
||||||
|
padding: 30rpx 80rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 6rpx 20rpx rgba(102, 126, 234, 0.4);
|
||||||
|
min-width: 300rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-btn:active {
|
||||||
|
transform: translateY(2rpx);
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载动画 */
|
||||||
|
.loading-spinner {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border: 6rpx solid #f3f3f3;
|
||||||
|
border-top: 6rpx solid #667eea;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 0 auto 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 结果网格 */
|
||||||
|
.result-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20rpx;
|
||||||
|
margin-bottom: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 280rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
object-fit: cover;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 重新生成按钮 */
|
||||||
|
.restart-btn {
|
||||||
|
background: linear-gradient(45deg, #52c41a, #73d13d);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50rpx;
|
||||||
|
padding: 24rpx 60rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(82, 196, 26, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.restart-btn:active {
|
||||||
|
transform: translateY(2rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 错误描述 */
|
||||||
|
.error-desc {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #ff4d4f;
|
||||||
|
margin-bottom: 50rpx;
|
||||||
|
background: #fff2f0;
|
||||||
|
border: 1rpx solid #ffccc7;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
line-height: 1.5;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 重试按钮 */
|
||||||
|
.retry-btn {
|
||||||
|
background: linear-gradient(45deg, #ff4d4f, #ff7875);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50rpx;
|
||||||
|
padding: 24rpx 60rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(255, 77, 79, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.retry-btn:active {
|
||||||
|
transform: translateY(2rpx);
|
||||||
}
|
}
|
||||||
|
|
@ -1,28 +1,107 @@
|
||||||
import { View, Text, Button } from '@tarojs/components'
|
import { View, Text, Button, Image } from '@tarojs/components'
|
||||||
import { useLoad } from '@tarojs/taro'
|
import { useLoad } from '@tarojs/taro'
|
||||||
|
import { useState } from 'react'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import { useAd, useSdk } from '../../hooks/index'
|
import { useSdk } from '../../hooks/index'
|
||||||
|
|
||||||
|
type PageStep = 'upload' | 'loading' | 'result' | 'error'
|
||||||
|
|
||||||
|
interface AppState {
|
||||||
|
step: PageStep;
|
||||||
|
images: string[];
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
|
|
||||||
const sdk = useSdk()
|
const sdk = useSdk()
|
||||||
|
const [state, setState] = useState<AppState>({
|
||||||
|
step: 'upload',
|
||||||
|
images: [],
|
||||||
|
error: null
|
||||||
|
})
|
||||||
|
|
||||||
useLoad(() => {
|
useLoad(() => {
|
||||||
return () => { }
|
return () => { }
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
const chooseAndGenerateImage = async () => {
|
||||||
<View className='index'>
|
setState({ step: 'loading', images: [], error: null })
|
||||||
<Text>Hello world Ymm !!!!</Text>
|
|
||||||
<Button onClick={() => {
|
try {
|
||||||
sdk.chooseAndGenerateImage().then(task_id => {
|
const task_id = await sdk.chooseAndGenerateImage()
|
||||||
console.log({ task_id })
|
await new Promise((resolve) => setTimeout(resolve, 5000))
|
||||||
return sdk.getTaskStatus(task_id)
|
const urls = await sdk.getTaskStatus(task_id)
|
||||||
}).then(res => {
|
|
||||||
console.log(res)
|
setState({ step: 'result', images: urls, error: null })
|
||||||
})
|
} catch (error) {
|
||||||
}}>
|
const errorMsg = error instanceof Error ? error.message : '生成失败'
|
||||||
生图
|
setState({ step: 'error', images: [], error: errorMsg })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetToUpload = () => {
|
||||||
|
setState({ step: 'upload', images: [], error: null })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 传图片环节
|
||||||
|
const renderUploadStep = () => (
|
||||||
|
<View className="step-container">
|
||||||
|
<Text className="step-title">🎨 AI手办生成器</Text>
|
||||||
|
<Text className="step-desc">选择图片,AI将为您生成精美手办</Text>
|
||||||
|
<Button className="upload-btn" onClick={chooseAndGenerateImage}>
|
||||||
|
选择图片开始生成
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 2. Loading环节
|
||||||
|
const renderLoadingStep = () => (
|
||||||
|
<View className="step-container">
|
||||||
|
<View className="loading-spinner" />
|
||||||
|
<Text className="step-title">AI正在生成中...</Text>
|
||||||
|
<Text className="step-desc">请耐心等待,正在为您精心制作</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
|
||||||
|
// 3. 结果预览环节
|
||||||
|
const renderResultStep = () => (
|
||||||
|
<View className="step-container">
|
||||||
|
<Text className="step-title">✨ 生成完成</Text>
|
||||||
|
<View className="result-grid">
|
||||||
|
{state.images.map((url, index) => (
|
||||||
|
<Image key={index} className="result-image" src={url} mode="aspectFill" />
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<Button className="restart-btn" onClick={resetToUpload}>
|
||||||
|
重新生成
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
|
||||||
|
// 4. 错误环节
|
||||||
|
const renderErrorStep = () => (
|
||||||
|
<View className="step-container">
|
||||||
|
<Text className="step-title">❌ 生成失败</Text>
|
||||||
|
<Text className="error-desc">{state.error}</Text>
|
||||||
|
<Button className="retry-btn" onClick={resetToUpload}>
|
||||||
|
重新尝试
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderCurrentStep = () => {
|
||||||
|
switch (state.step) {
|
||||||
|
case 'upload': return renderUploadStep()
|
||||||
|
case 'loading': return renderLoadingStep()
|
||||||
|
case 'result': return renderResultStep()
|
||||||
|
case 'error': return renderErrorStep()
|
||||||
|
default: return renderUploadStep()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className='index'>
|
||||||
|
{renderCurrentStep()}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -167,11 +167,10 @@ export class BowongAISDK {
|
||||||
|
|
||||||
// 上传第一张图片
|
// 上传第一张图片
|
||||||
const filePath = chooseResult.tempFilePaths[0];
|
const filePath = chooseResult.tempFilePaths[0];
|
||||||
|
const prompt = `将画面中的角色重塑为顶级收藏级树脂手办,全身动态姿势,置于角色主题底座;高精度材质,手工涂装,肌肤纹理与服装材质真实分明。戏剧性硬光为主光源,凸显立体感,无过曝;强效补光消除死黑,细节完整可见。背景为窗边景深模糊,侧后方隐约可见产品包装盒。博物馆级摄影质感,全身细节无损,面部结构精准。禁止:任何2D元素或照搬原图、塑料感、面部模糊、五官错位、细节丢失`
|
||||||
return await this.generateImage({
|
return await this.generateImage({
|
||||||
img_file: filePath,
|
img_file: filePath,
|
||||||
prompt: `将画面中的角色重塑为顶级收藏级树脂手办,全身动态姿势,置于角色主题底座;高精度材质,手工涂装,肌肤纹理与服装材质真实分明。
|
prompt: prompt
|
||||||
戏剧性硬光为主光源,凸显立体感,无过曝;强效补光消除死黑,细节完整可见。背景为窗边景深模糊,侧后方隐约可见产品包装盒。
|
|
||||||
博物馆级摄影质感,全身细节无损,面部结构精准。禁止:任何2D元素或照搬原图、塑料感、面部模糊、五官错位、细节丢失`
|
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -291,7 +290,7 @@ export class BowongAISDK {
|
||||||
* @param retryCount 内部重试计数,外部调用不需要传入
|
* @param retryCount 内部重试计数,外部调用不需要传入
|
||||||
* @returns Promise<any> 返回任务状态信息
|
* @returns Promise<any> 返回任务状态信息
|
||||||
*/
|
*/
|
||||||
async getTaskStatus(taskId: string, retryCount: number = 0): Promise<any> {
|
async getTaskStatus(taskId: string, retryCount: number = 0): Promise<string[]> {
|
||||||
const url = `${this.baseUrl}/api/custom/task/status?task_id=${taskId}`;
|
const url = `${this.baseUrl}/api/custom/task/status?task_id=${taskId}`;
|
||||||
const maxRetries = 3;
|
const maxRetries = 3;
|
||||||
|
|
||||||
|
|
@ -306,7 +305,7 @@ export class BowongAISDK {
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = response.data as ApiResponse;
|
const result = response.data as ApiResponse;
|
||||||
|
console.log({ result })
|
||||||
if (!result.status) {
|
if (!result.status) {
|
||||||
throw new Error(result.msg || '查询任务状态失败');
|
throw new Error(result.msg || '查询任务状态失败');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue