添加青云AI服务提供商支持,修复火山引擎图片生成问题

- 新增青云(qingyun)provider支持,提供文本/图片/视频AI服务
- 修复火山引擎img2img模式下图片尺寸问题(参考图片时强制使用2K)
- 修复火山引擎水印参数(watermark设为必需字段)
- 前端添加图片删除功能,支持删除不满意的生成图片
- 优化素材选择器布局样式,提升用户体验

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-19 00:57:12 +08:00
parent d970107a34
commit 45da3dc0ea
5 changed files with 98 additions and 8 deletions

View File

@@ -458,7 +458,8 @@ func (s *VideoGenerationService) getVideoClient(provider string, modelName strin
var queryEndpoint string
switch config.Provider {
case "chatfire":
case "chatfire", "qingyun":
// 青云和 Chatfire 都使用 OpenAI 兼容格式
endpoint = "/video/generations"
queryEndpoint = "/video/task/{taskId}"
return video.NewChatfireClient(baseURL, apiKey, model, endpoint, queryEndpoint), nil

View File

@@ -24,7 +24,7 @@ type VolcEngineImageRequest struct {
Image []string `json:"image,omitempty"`
SequentialImageGeneration string `json:"sequential_image_generation,omitempty"`
Size string `json:"size,omitempty"`
Watermark bool `json:"watermark,omitempty"`
Watermark bool `json:"watermark"`
}
type VolcEngineImageResponse struct {
@@ -82,7 +82,11 @@ func (c *VolcEngineImageClient) GenerateImage(prompt string, opts ...ImageOption
}
size := options.Size
if size == "" {
// 火山引擎 img2img 模式要求图片尺寸至少 3686400 像素约1920x1920
// 当使用参考图片时,强制使用 2K 尺寸
if len(options.ReferenceImages) > 0 {
size = "2K"
} else if size == "" || size == "1024x1024" {
if model == "doubao-seedream-4-5-251128" {
size = "2K"
} else {

View File

@@ -288,6 +288,18 @@ const providerConfigs: Record<AIServiceType, ProviderConfig[]> = {
'doubao-seed-1-8-251228'
]
},
{
id: 'qingyun',
name: '青云',
models: [
'gpt-5.2',
'claude-opus-4-5-20251101',
'gemini-3-pro-preview',
'deepseek-v3.2',
'qwen-max',
'doubao-seed-1-8-251228'
]
},
{
id: 'gemini',
name: 'Google Gemini',
@@ -305,6 +317,17 @@ const providerConfigs: Record<AIServiceType, ProviderConfig[]> = {
name: 'Chatfire',
models: ['doubao-seedream-4-5-251128', 'nano-banana-pro']
},
{
id: 'qingyun',
name: '青云',
models: [
'gpt-image-1.5',
'gemini-3-pro-image-preview',
'jimeng-4.5',
'jimeng-4.1',
'mj_imagine'
]
},
{
id: 'gemini',
name: 'Google Gemini',
@@ -337,6 +360,19 @@ const providerConfigs: Record<AIServiceType, ProviderConfig[]> = {
'sora-2-pro'
]
},
{
id: 'qingyun',
name: '青云',
models: [
'grok-video-3',
'veo3.1-pro-4k',
'sora-2-all',
'sora-2-pro-all',
'jimeng-video-3.0',
'kling-video-1.5',
'wan2.6-i2v'
]
},
{
id: 'minimax',
name: 'MiniMax 海螺',
@@ -444,7 +480,8 @@ const generateConfigName = (provider: string, serviceType: AIServiceType): strin
'chatfire': 'ChatFire',
'openai': 'OpenAI',
'gemini': 'Gemini',
'google': 'Google'
'google': 'Google',
'qingyun': '青云'
}
const serviceNames: Record<AIServiceType, string> = {
@@ -609,6 +646,8 @@ const handleProviderChange = () => {
form.base_url = 'https://ark.cn-beijing.volces.com/api/v3'
} else if (form.provider === 'openai') {
form.base_url = 'https://api.openai.com/v1'
} else if (form.provider === 'qingyun') {
form.base_url = 'https://api.qingyuntop.top/v1'
} else {
// chatfire 和其他厂商
form.base_url = 'https://api.chatfire.site/v1'

View File

@@ -2162,7 +2162,7 @@ defineExpose({
}
.media-grid {
max-height: 450px;
flex: 1;
overflow-y: auto;
padding: 12px;
display: grid;
@@ -2193,7 +2193,6 @@ defineExpose({
position: relative;
background: var(--bg-secondary);
border-radius: 6px;
overflow: hidden;
cursor: move;
border: 1px solid var(--border-primary);
transition: all 0.3s;
@@ -2223,6 +2222,8 @@ defineExpose({
aspect-ratio: 16/9;
background: var(--bg-card-hover);
cursor: pointer;
overflow: hidden;
border-radius: 6px 6px 0 0;
video {
width: 100%;

View File

@@ -292,12 +292,20 @@
<el-icon :size="32">
<Picture />
</el-icon>
<p>生成中...</p>
<p>{{ img.status === 'failed' ? 'FAILED' : '生成中...' }}</p>
</div>
<div class="image-info">
<el-tag :type="getStatusType(img.status)" size="small">{{ getStatusText(img.status) }}</el-tag>
<span v-if="img.frame_type" class="frame-type-tag">{{ getFrameTypeText(img.frame_type) }}</span>
</div>
<el-button
class="delete-image-btn"
type="danger"
size="small"
circle
:icon="Delete"
@click.stop="deleteImage(img.id)"
/>
</div>
</div>
</div>
@@ -1652,7 +1660,28 @@ const extractLastFrame = async () => {
}
}
// 获取状态标签类型
// 删除图片生成记录
const deleteImage = async (imageId: number) => {
try {
await ElMessageBox.confirm('确定要删除该图片吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
await imageAPI.deleteImage(imageId)
ElMessage.success('删除成功')
// 刷新图片列表
if (currentStoryboard.value) {
await loadStoryboardImages(currentStoryboard.value.id, selectedFrameType.value)
}
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error('删除失败: ' + (error.message || '未知错误'))
}
}
}
const getStatusType = (status: string) => {
const statusMap: Record<string, any> = {
pending: 'info',
@@ -4317,4 +4346,20 @@ onBeforeUnmount(() => {
max-height: 80px;
overflow-y: auto;
}
.image-item {
position: relative;
}
.delete-image-btn {
position: absolute;
top: 4px;
right: 4px;
opacity: 0;
transition: opacity 0.2s;
}
.image-item:hover .delete-image-btn {
opacity: 1;
}
</style>