mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-08-13 03:00:59 +00:00
feat(release): migrate GeekAI v4.3.0 to open source
- Sync backend and frontend from GeekAI Plus v4.3.0 - Remove commercial License flows and update open-source deployment defaults - Preserve Docker Compose deployment and bump image tags to v4.3.0 BREAKING CHANGE: commercial License configuration and related endpoints are removed
This commit is contained in:
-510
@@ -1,510 +0,0 @@
|
||||
## 开发 3D 图片生成功能
|
||||
|
||||
对接 3D 图片生成接口,为当前系统添加 3D 模型生成功能,默认支持腾讯云和 Gitee 的图生 3D API 接口。
|
||||
|
||||
## 要求
|
||||
|
||||
1. 完成数据库设计,后端 API 设计,前端页面设计。
|
||||
2. 完成前端功能页面以及后台管理页面,具体设计结构可以参考即梦 AI,在对应的模块建立独立的模块 。
|
||||
3. 页面设计要精美,但是整体风格要跟整站风格一致。
|
||||
4. 支持前端 3D 模型预览,支持 3D 模型下载。
|
||||
|
||||
## 腾讯云图生 3D API 接口文档
|
||||
|
||||
1. 提交任务: https://cloud.tencent.com/document/product/1804/120826
|
||||
2. 查询任务: https://cloud.tencent.com/document/product/1804/120827
|
||||
3. Golang SDK: https://gitee.com/TencentCloud/tencentcloud-sdk-go/blob/master/tencentcloud/ai3d/v20250513/client.go 依赖已经安装到本地了,如果你无法读取远程文件,下面是文件主要内容:
|
||||
|
||||
client.go
|
||||
|
||||
```go
|
||||
// Copyright (c) 2017-2025 Tencent. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package v20250513
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
)
|
||||
|
||||
const APIVersion = "2025-05-13"
|
||||
|
||||
type Client struct {
|
||||
common.Client
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func NewClientWithSecretId(secretId, secretKey, region string) (client *Client, err error) {
|
||||
cpf := profile.NewClientProfile()
|
||||
client = &Client{}
|
||||
client.Init(region).WithSecretId(secretId, secretKey).WithProfile(cpf)
|
||||
return
|
||||
}
|
||||
|
||||
func NewClient(credential common.CredentialIface, region string, clientProfile *profile.ClientProfile) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
client.Init(region).
|
||||
WithCredential(credential).
|
||||
WithProfile(clientProfile)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func NewQueryHunyuanTo3DJobRequest() (request *QueryHunyuanTo3DJobRequest) {
|
||||
request = &QueryHunyuanTo3DJobRequest{
|
||||
BaseRequest: &tchttp.BaseRequest{},
|
||||
}
|
||||
|
||||
request.Init().WithApiInfo("ai3d", APIVersion, "QueryHunyuanTo3DJob")
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NewQueryHunyuanTo3DJobResponse() (response *QueryHunyuanTo3DJobResponse) {
|
||||
response = &QueryHunyuanTo3DJobResponse{
|
||||
BaseResponse: &tchttp.BaseResponse{},
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// QueryHunyuanTo3DJob
|
||||
// 混元生3D接口,基于混元大模型,根据输入的文本描述/图片智能生成3D。
|
||||
//
|
||||
// 默认提供1个并发,代表最多能同时处理1个已提交的任务,上一个任务处理完毕后,才能开始处理下一个任务。
|
||||
func (c *Client) QueryHunyuanTo3DJob(request *QueryHunyuanTo3DJobRequest) (response *QueryHunyuanTo3DJobResponse, err error) {
|
||||
return c.QueryHunyuanTo3DJobWithContext(context.Background(), request)
|
||||
}
|
||||
|
||||
// QueryHunyuanTo3DJob
|
||||
// 混元生3D接口,基于混元大模型,根据输入的文本描述/图片智能生成3D。
|
||||
//
|
||||
// 默认提供1个并发,代表最多能同时处理1个已提交的任务,上一个任务处理完毕后,才能开始处理下一个任务。
|
||||
func (c *Client) QueryHunyuanTo3DJobWithContext(ctx context.Context, request *QueryHunyuanTo3DJobRequest) (response *QueryHunyuanTo3DJobResponse, err error) {
|
||||
if request == nil {
|
||||
request = NewQueryHunyuanTo3DJobRequest()
|
||||
}
|
||||
c.InitBaseRequest(&request.BaseRequest, "ai3d", APIVersion, "QueryHunyuanTo3DJob")
|
||||
|
||||
if c.GetCredential() == nil {
|
||||
return nil, errors.New("QueryHunyuanTo3DJob require credential")
|
||||
}
|
||||
|
||||
request.SetContext(ctx)
|
||||
|
||||
response = NewQueryHunyuanTo3DJobResponse()
|
||||
err = c.Send(request, response)
|
||||
return
|
||||
}
|
||||
|
||||
func NewSubmitHunyuanTo3DJobRequest() (request *SubmitHunyuanTo3DJobRequest) {
|
||||
request = &SubmitHunyuanTo3DJobRequest{
|
||||
BaseRequest: &tchttp.BaseRequest{},
|
||||
}
|
||||
|
||||
request.Init().WithApiInfo("ai3d", APIVersion, "SubmitHunyuanTo3DJob")
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NewSubmitHunyuanTo3DJobResponse() (response *SubmitHunyuanTo3DJobResponse) {
|
||||
response = &SubmitHunyuanTo3DJobResponse{
|
||||
BaseResponse: &tchttp.BaseResponse{},
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// SubmitHunyuanTo3DJob
|
||||
// 混元生3D接口,基于混元大模型,根据输入的文本描述/图片智能生成3D。
|
||||
//
|
||||
// 默认提供1个并发,代表最多能同时处理1个已提交的任务,上一个任务处理完毕后,才能开始处理下一个任务。
|
||||
func (c *Client) SubmitHunyuanTo3DJob(request *SubmitHunyuanTo3DJobRequest) (response *SubmitHunyuanTo3DJobResponse, err error) {
|
||||
return c.SubmitHunyuanTo3DJobWithContext(context.Background(), request)
|
||||
}
|
||||
|
||||
// SubmitHunyuanTo3DJob
|
||||
// 混元生3D接口,基于混元大模型,根据输入的文本描述/图片智能生成3D。
|
||||
//
|
||||
// 默认提供1个并发,代表最多能同时处理1个已提交的任务,上一个任务处理完毕后,才能开始处理下一个任务。
|
||||
func (c *Client) SubmitHunyuanTo3DJobWithContext(ctx context.Context, request *SubmitHunyuanTo3DJobRequest) (response *SubmitHunyuanTo3DJobResponse, err error) {
|
||||
if request == nil {
|
||||
request = NewSubmitHunyuanTo3DJobRequest()
|
||||
}
|
||||
c.InitBaseRequest(&request.BaseRequest, "ai3d", APIVersion, "SubmitHunyuanTo3DJob")
|
||||
|
||||
if c.GetCredential() == nil {
|
||||
return nil, errors.New("SubmitHunyuanTo3DJob require credential")
|
||||
}
|
||||
|
||||
request.SetContext(ctx)
|
||||
|
||||
response = NewSubmitHunyuanTo3DJobResponse()
|
||||
err = c.Send(request, response)
|
||||
return
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
model.go
|
||||
|
||||
```go
|
||||
// Copyright (c) 2017-2025 Tencent. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package v20250513
|
||||
|
||||
import (
|
||||
tcerr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
|
||||
tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/json"
|
||||
)
|
||||
|
||||
type File3D struct {
|
||||
// 文件格式
|
||||
Type *string `json:"Type,omitnil,omitempty" name:"Type"`
|
||||
|
||||
// 文件的Url(有效期24小时)
|
||||
Url *string `json:"Url,omitnil,omitempty" name:"Url"`
|
||||
|
||||
// 预览图片Url
|
||||
PreviewImageUrl *string `json:"PreviewImageUrl,omitnil,omitempty" name:"PreviewImageUrl"`
|
||||
}
|
||||
|
||||
// Predefined struct for user
|
||||
type QueryHunyuanTo3DJobRequestParams struct {
|
||||
// 任务ID。
|
||||
JobId *string `json:"JobId,omitnil,omitempty" name:"JobId"`
|
||||
}
|
||||
|
||||
type QueryHunyuanTo3DJobRequest struct {
|
||||
*tchttp.BaseRequest
|
||||
|
||||
// 任务ID。
|
||||
JobId *string `json:"JobId,omitnil,omitempty" name:"JobId"`
|
||||
}
|
||||
|
||||
func (r *QueryHunyuanTo3DJobRequest) ToJsonString() string {
|
||||
b, _ := json.Marshal(r)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// FromJsonString It is highly **NOT** recommended to use this function
|
||||
// because it has no param check, nor strict type check
|
||||
func (r *QueryHunyuanTo3DJobRequest) FromJsonString(s string) error {
|
||||
f := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(s), &f); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(f, "JobId")
|
||||
if len(f) > 0 {
|
||||
return tcerr.NewTencentCloudSDKError("ClientError.BuildRequestError", "QueryHunyuanTo3DJobRequest has unknown keys!", "")
|
||||
}
|
||||
return json.Unmarshal([]byte(s), &r)
|
||||
}
|
||||
|
||||
// Predefined struct for user
|
||||
type QueryHunyuanTo3DJobResponseParams struct {
|
||||
// 任务状态。WAIT:等待中,RUN:执行中,FAIL:任务失败,DONE:任务成功
|
||||
Status *string `json:"Status,omitnil,omitempty" name:"Status"`
|
||||
|
||||
// 错误码
|
||||
ErrorCode *string `json:"ErrorCode,omitnil,omitempty" name:"ErrorCode"`
|
||||
|
||||
// 错误信息
|
||||
ErrorMessage *string `json:"ErrorMessage,omitnil,omitempty" name:"ErrorMessage"`
|
||||
|
||||
// 生成的3D文件数组。
|
||||
ResultFile3Ds []*File3D `json:"ResultFile3Ds,omitnil,omitempty" name:"ResultFile3Ds"`
|
||||
|
||||
// 唯一请求 ID,由服务端生成,每次请求都会返回(若请求因其他原因未能抵达服务端,则该次请求不会获得 RequestId)。定位问题时需要提供该次请求的 RequestId。
|
||||
RequestId *string `json:"RequestId,omitnil,omitempty" name:"RequestId"`
|
||||
}
|
||||
|
||||
type QueryHunyuanTo3DJobResponse struct {
|
||||
*tchttp.BaseResponse
|
||||
Response *QueryHunyuanTo3DJobResponseParams `json:"Response"`
|
||||
}
|
||||
|
||||
func (r *QueryHunyuanTo3DJobResponse) ToJsonString() string {
|
||||
b, _ := json.Marshal(r)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// FromJsonString It is highly **NOT** recommended to use this function
|
||||
// because it has no param check, nor strict type check
|
||||
func (r *QueryHunyuanTo3DJobResponse) FromJsonString(s string) error {
|
||||
return json.Unmarshal([]byte(s), &r)
|
||||
}
|
||||
|
||||
// Predefined struct for user
|
||||
type SubmitHunyuanTo3DJobRequestParams struct {
|
||||
// 文生3D,3D内容的描述,中文正向提示词。
|
||||
// 最多支持1024个 utf-8 字符。
|
||||
// 文生3D, image、image_url和 prompt必填其一,且prompt和image/image_url不能同时存在。
|
||||
Prompt *string `json:"Prompt,omitnil,omitempty" name:"Prompt"`
|
||||
|
||||
// 输入图 Base64 数据。
|
||||
// 大小:单边分辨率要求不小于128,不大于5000。大小不超过8m(base64编码后会大30%左右,建议实际输入图片不超过6m)
|
||||
// 格式:jpg,png,jpeg,webp。
|
||||
// ImageBase64、ImageUrl和 Prompt必填其一,且Prompt和ImageBase64/ImageUrl不能同时存在。
|
||||
ImageBase64 *string `json:"ImageBase64,omitnil,omitempty" name:"ImageBase64"`
|
||||
|
||||
// 输入图Url。
|
||||
// 大小:单边分辨率要求不小于128,不大于5000。大小不超过8m(base64编码后会大30%左右,建议实际输入图片不超过6m)
|
||||
// 格式:jpg,png,jpeg,webp。
|
||||
// ImageBase64/ImageUrl和 Prompt必填其一,且Prompt和ImageBase64/ImageUrl不能同时存在。
|
||||
ImageUrl *string `json:"ImageUrl,omitnil,omitempty" name:"ImageUrl"`
|
||||
|
||||
// 多视角的模型图片,视角参考值:
|
||||
// left:左视图;
|
||||
// right:右视图;
|
||||
// back:后视图;
|
||||
//
|
||||
// 每个视角仅限制一张图片。
|
||||
// ●图片大小限制:编码后大小不可超过8M。
|
||||
// ●图片分辨率限制:单边分辨率小于5000且大于128。
|
||||
// ●支持图片格式:支持jpg或png
|
||||
MultiViewImages []*ViewImage `json:"MultiViewImages,omitnil,omitempty" name:"MultiViewImages"`
|
||||
|
||||
// 生成模型的格式,仅限制生成一种格式。
|
||||
// 生成模型文件组默认返回obj格式。
|
||||
// 可选值:OBJ,GLB,STL,USDZ,FBX,MP4。
|
||||
ResultFormat *string `json:"ResultFormat,omitnil,omitempty" name:"ResultFormat"`
|
||||
|
||||
// 是否开启 PBR材质生成,默认 false。
|
||||
EnablePBR *bool `json:"EnablePBR,omitnil,omitempty" name:"EnablePBR"`
|
||||
}
|
||||
|
||||
type SubmitHunyuanTo3DJobRequest struct {
|
||||
*tchttp.BaseRequest
|
||||
|
||||
// 文生3D,3D内容的描述,中文正向提示词。
|
||||
// 最多支持1024个 utf-8 字符。
|
||||
// 文生3D, image、image_url和 prompt必填其一,且prompt和image/image_url不能同时存在。
|
||||
Prompt *string `json:"Prompt,omitnil,omitempty" name:"Prompt"`
|
||||
|
||||
// 输入图 Base64 数据。
|
||||
// 大小:单边分辨率要求不小于128,不大于5000。大小不超过8m(base64编码后会大30%左右,建议实际输入图片不超过6m)
|
||||
// 格式:jpg,png,jpeg,webp。
|
||||
// ImageBase64、ImageUrl和 Prompt必填其一,且Prompt和ImageBase64/ImageUrl不能同时存在。
|
||||
ImageBase64 *string `json:"ImageBase64,omitnil,omitempty" name:"ImageBase64"`
|
||||
|
||||
// 输入图Url。
|
||||
// 大小:单边分辨率要求不小于128,不大于5000。大小不超过8m(base64编码后会大30%左右,建议实际输入图片不超过6m)
|
||||
// 格式:jpg,png,jpeg,webp。
|
||||
// ImageBase64/ImageUrl和 Prompt必填其一,且Prompt和ImageBase64/ImageUrl不能同时存在。
|
||||
ImageUrl *string `json:"ImageUrl,omitnil,omitempty" name:"ImageUrl"`
|
||||
|
||||
// 多视角的模型图片,视角参考值:
|
||||
// left:左视图;
|
||||
// right:右视图;
|
||||
// back:后视图;
|
||||
//
|
||||
// 每个视角仅限制一张图片。
|
||||
// ●图片大小限制:编码后大小不可超过8M。
|
||||
// ●图片分辨率限制:单边分辨率小于5000且大于128。
|
||||
// ●支持图片格式:支持jpg或png
|
||||
MultiViewImages []*ViewImage `json:"MultiViewImages,omitnil,omitempty" name:"MultiViewImages"`
|
||||
|
||||
// 生成模型的格式,仅限制生成一种格式。
|
||||
// 生成模型文件组默认返回obj格式。
|
||||
// 可选值:OBJ,GLB,STL,USDZ,FBX,MP4。
|
||||
ResultFormat *string `json:"ResultFormat,omitnil,omitempty" name:"ResultFormat"`
|
||||
|
||||
// 是否开启 PBR材质生成,默认 false。
|
||||
EnablePBR *bool `json:"EnablePBR,omitnil,omitempty" name:"EnablePBR"`
|
||||
}
|
||||
|
||||
func (r *SubmitHunyuanTo3DJobRequest) ToJsonString() string {
|
||||
b, _ := json.Marshal(r)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// FromJsonString It is highly **NOT** recommended to use this function
|
||||
// because it has no param check, nor strict type check
|
||||
func (r *SubmitHunyuanTo3DJobRequest) FromJsonString(s string) error {
|
||||
f := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(s), &f); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(f, "Prompt")
|
||||
delete(f, "ImageBase64")
|
||||
delete(f, "ImageUrl")
|
||||
delete(f, "MultiViewImages")
|
||||
delete(f, "ResultFormat")
|
||||
delete(f, "EnablePBR")
|
||||
if len(f) > 0 {
|
||||
return tcerr.NewTencentCloudSDKError("ClientError.BuildRequestError", "SubmitHunyuanTo3DJobRequest has unknown keys!", "")
|
||||
}
|
||||
return json.Unmarshal([]byte(s), &r)
|
||||
}
|
||||
|
||||
// Predefined struct for user
|
||||
type SubmitHunyuanTo3DJobResponseParams struct {
|
||||
// 任务ID(有效期24小时)
|
||||
JobId *string `json:"JobId,omitnil,omitempty" name:"JobId"`
|
||||
|
||||
// 唯一请求 ID,由服务端生成,每次请求都会返回(若请求因其他原因未能抵达服务端,则该次请求不会获得 RequestId)。定位问题时需要提供该次请求的 RequestId。
|
||||
RequestId *string `json:"RequestId,omitnil,omitempty" name:"RequestId"`
|
||||
}
|
||||
|
||||
type SubmitHunyuanTo3DJobResponse struct {
|
||||
*tchttp.BaseResponse
|
||||
Response *SubmitHunyuanTo3DJobResponseParams `json:"Response"`
|
||||
}
|
||||
|
||||
func (r *SubmitHunyuanTo3DJobResponse) ToJsonString() string {
|
||||
b, _ := json.Marshal(r)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// FromJsonString It is highly **NOT** recommended to use this function
|
||||
// because it has no param check, nor strict type check
|
||||
func (r *SubmitHunyuanTo3DJobResponse) FromJsonString(s string) error {
|
||||
return json.Unmarshal([]byte(s), &r)
|
||||
}
|
||||
|
||||
type ViewImage struct {
|
||||
// 视角类型。
|
||||
// 取值:back、left、right
|
||||
ViewType *string `json:"ViewType,omitnil,omitempty" name:"ViewType"`
|
||||
|
||||
// 图片Url地址
|
||||
ViewImageUrl *string `json:"ViewImageUrl,omitnil,omitempty" name:"ViewImageUrl"`
|
||||
}
|
||||
```
|
||||
|
||||
## Gitee 图生 3D API 接口文档
|
||||
|
||||
1. 提交任务: https://ai.gitee.com/docs/openapi/v1#tag/3d-%E7%94%9F%E6%88%90/post/async/image-to-3d
|
||||
2. 查询任务:https://ai.gitee.com/docs/openapi/v1#tag/%E5%BC%82%E6%AD%A5%E4%BB%BB%E5%8A%A1/get/task/{task_id}/get
|
||||
|
||||
首先,你需要认真阅读上述接口文档,然后按照接口文档的示例代码实现腾讯云和 Gitee 的图生 3D API 接口,并且将接口集成到现有的系统中。
|
||||
|
||||
📋 功能概述
|
||||
|
||||
为现有的GeekAI-Plus系统添加3D图片生成功能,集成腾讯云和Gitee的图生3D API接口,包含完整的前后端功能和管理界面。
|
||||
|
||||
🗄️ 数据库设计
|
||||
|
||||
新增数据表:geekai_3d_jobs
|
||||
- id (uint): 主键
|
||||
- type (string): API类型 (tencent/gitee)
|
||||
- user_id (uint): 用户ID
|
||||
- power (int): 消耗算力
|
||||
- task_id (string): 第三方任务ID
|
||||
- img_url (string): 生成的3D模型文件地址
|
||||
- model (string): 使用的3D模型类型
|
||||
- status (string): 任务状态
|
||||
- err_msg (string): 错误信息
|
||||
- params (JSON): 任务参数(包含输入图片、提示词等所有参数)
|
||||
- created_at (int64): 创建时间
|
||||
|
||||
🔧 后端API实现
|
||||
|
||||
路由结构:/api/3d/*
|
||||
- POST /api/3d/generate - 创建3D生成任务
|
||||
- GET /api/3d/jobs - 获取任务列表
|
||||
- GET /api/3d/job/{id} - 获取任务详情
|
||||
- GET /api/3d/download/{id} - 下载3D模型
|
||||
- DELETE /api/3d/job/{id} - 删除任务
|
||||
|
||||
核心服务:
|
||||
- service/3d/tencent_client.go - 腾讯云3D API客户端
|
||||
- service/3d/gitee_client.go - Gitee 3D API客户端
|
||||
- service/3d/service.go - 3D生成服务统一接口
|
||||
- handler/3d_handler.go - HTTP处理器
|
||||
- store/vo/3d_job.go - 数据模型
|
||||
|
||||
🎨 前端界面设计
|
||||
|
||||
用户端页面:/3d - 3D生成主页面
|
||||
- 参考JiMeng.vue的设计风格和布局
|
||||
- 使用CustomTab组件分离平台参数:
|
||||
- Tab 1: "魔力方舟" (Gitee平台参数)
|
||||
- Tab 2: "腾讯混元" (腾讯云平台参数)
|
||||
- 每个Tab内包含:
|
||||
- 图片上传区域
|
||||
- 模型选择下拉框
|
||||
- 算力消耗实时显示
|
||||
- 平台特定的参数配置
|
||||
- 生成按钮
|
||||
- 任务列表和状态显示
|
||||
- 集成3D模型预览器 (three.js)
|
||||
- 模型下载功能
|
||||
|
||||
移动端适配:
|
||||
- mobile/3dCreate.vue - 移动端3D生成页面
|
||||
- 保持Tab切换功能
|
||||
- 响应式设计,触控优化
|
||||
|
||||
🛠️ 管理后台
|
||||
|
||||
管理功能:
|
||||
- admin/3d/3dJobs.vue - 任务管理列表
|
||||
- admin/3d/3dSetting.vue - API配置页面
|
||||
- 模型配置管理:
|
||||
- 分平台配置API秘钥和模型列表
|
||||
- 设置每个模型的算力消耗值
|
||||
- API密钥和端点配置
|
||||
|
||||
🔌 API集成方案
|
||||
|
||||
腾讯云集成:
|
||||
- 使用官方Golang SDK
|
||||
- 支持异步任务提交和状态查询
|
||||
|
||||
Gitee集成:
|
||||
- HTTP客户端实现
|
||||
- 标准化响应处理
|
||||
|
||||
🎯 核心功能特性
|
||||
|
||||
- 平台切换:通过CustomTab在魔力方舟和腾讯混元间切换
|
||||
- 模型选择:每个平台支持不同的3D模型
|
||||
- 动态算力:切换模型时实时更新算力消耗显示
|
||||
- 参数隔离:不同平台的参数配置完全分离
|
||||
- 3D预览:集成Three.js实现模型预览
|
||||
- 统一体验:保持与JiMeng.vue相似的交互风格
|
||||
|
||||
📱 用户体验
|
||||
|
||||
- JiMeng.vue风格的简洁界面
|
||||
- Tab切换流畅的平台选择
|
||||
- 模型选择时算力消耗实时更新
|
||||
- 支持拖拽上传图片
|
||||
- 实时任务状态显示
|
||||
- 3D模型交互式预览
|
||||
|
||||
这个设计将创建一个与现有JiMeng功能风格一致的3D生成模块,通过Tab分离实现平台参数的清晰管理。
|
||||
|
||||
整个实现严格按照现有系统的代码规范和架构模式,与 JiMeng 等模块保持一致的用户体验!
|
||||
@@ -0,0 +1,54 @@
|
||||
# PPT 断点续生成(简化方案 · 修订版)
|
||||
|
||||
## 设计原则(按产品确认)
|
||||
|
||||
- **不新增数据库字段**,只复用现有 `geekai_ppt_jobs.slides`(JSON)。
|
||||
- 分镜结构已在 [`vo.PPTSlideData`](api/store/vo/ppt_job.go) 中体现:`theme/title/points/image_prompt` + `image_url`。
|
||||
- **大模型生成分镜后立刻整表写入 `slides`**:每条记录带完整分镜字段,**`image_url` 为空**,表示「待生图」。
|
||||
- **列表「继续生成」条件**:`status !== 'completed'` 即可展示(实现时需排除无意义的态:如从未写入分镜的异常数据;**`processing` 时按钮应禁用或显示加载**,避免与进行中的任务重复提交)。
|
||||
- **继续生成**:读出 `slides`,**有 `image_url` 则跳过**,无则取当条分镜调生图 API;**每成功一张就更新一次数据库**。
|
||||
- **并发更新 `slides`**:多 goroutine 同时写同一任务的 `slides` 字段时,必须 **加锁**(推荐在 [`PptService`](api/service/ppt/ppt_service.go) 内 **`sync.Mutex` 按 `task_id` 维度**保护「读 JSON → 改一条 slide → 写回」的整段逻辑,避免后写覆盖先写)。
|
||||
|
||||
## 后端改动要点
|
||||
|
||||
### 1. `RunTask` 流程调整([`ppt_service.go`](api/service/ppt/ppt_service.go))
|
||||
|
||||
1. `GenerateSlides` 成功后,将 `[]slidePlan` 转为 `[]SlideData` / `vo.PPTSlides`,**全部 `ImageURL` 置空**,一次性 `Save` / `Updates` 写入 `slides`,并 `SetTotalSlides(total)`。
|
||||
2. **`completed_slides` 语义**:表示「已有配图」的页数,等于 `slides` 中 `image_url` 非空的条数(或单独在更新时维护,与 `len(slides)` 区分)。
|
||||
3. 抽取 **`runSlideImageGeneration`**:对「需要生图」的条目并发生图;每条成功后调用新的 **`applySlideImage(taskID, slideIndex, url)`**(内部持锁、按 index 更新对应元素的 `image_url`,再扣算力)。
|
||||
|
||||
### 2. 替换「仅 append」的 `UpdateProgress`
|
||||
|
||||
- 现有 `UpdateProgress` 是 **append** 一条 slide,与「先写满占位再填图」冲突。
|
||||
- 改为 **按 `slide_index` 原地更新** 指定项的 `image_url`(及必要时 `thumb`),并在持锁下 **整份序列化写回**。
|
||||
|
||||
### 3. `ResumeTask` / `POST .../resume`
|
||||
|
||||
- 校验用户、任务存在;若 `status === processing` 建议直接拒绝或返回「任务进行中」。
|
||||
- 若 `status !== completed`:加载 `slides`,筛出 `image_url` 为空的项,**仅对这些项**走与 `RunTask` 相同的生图 + `applySlideImage`。
|
||||
- 全部非空后:`UpdateStatus(completed)`,清空或保留 `err_msg` 按产品定。
|
||||
- 若某次仍失败:可 `MarkAsFailed` 保留已生成页(与现网一致)。
|
||||
|
||||
### 4. 失败路径
|
||||
|
||||
- 任一页生图失败时,仍可 `MarkAsFailed`,但 **分镜与已生成图均已在 `slides` 中**,续跑只需补空 `image_url`。
|
||||
|
||||
## 前端([`PPTCreate.vue`](web/src/views/PPTCreate.vue))
|
||||
|
||||
- 列表与详情:当 **`status !== 'completed'`**(且建议 **`status !== 'processing'` 才可点**)显示「继续生成」,**`el-tooltip`** 文案:继续生成尚未完成的幻灯片,已有页面保持不变。
|
||||
- 调用 `POST /api/v1/tasks/:id/resume`(路由注册方式同既有 export)。
|
||||
|
||||
## 与旧方案差异
|
||||
|
||||
- **不增加** `slide_plans` 等字段;**不重复存储**分镜,全部以 `slides` 中「无图占位」表达待生成状态。
|
||||
|
||||
## 验证
|
||||
|
||||
- 人为让第 N 页失败:列表出现继续生成,点击后仅补第 N 页及之后空图项。
|
||||
- 并发压测:多页同时完成时 `slides` JSON 无丢失、无覆盖。
|
||||
|
||||
## 实施状态(已实现)
|
||||
|
||||
- 后端:`saveSlidesOutline` + `ApplySlideImage`(`sync.Map` 按 `task_id` 互斥)、`runSlideImageJobs`、`ResumeTask`、`POST /api/v1/tasks/:task_id/resume`。
|
||||
- 前端:`PPTCreate.vue` 列表/详情「继续生成」+ `el-tooltip`,`processing` 时禁用或 loading。
|
||||
- **旧任务**:若 `slides` 条数小于 `total_slides`(无完整占位),`ResumeTask` 返回不可续跑。
|
||||
@@ -0,0 +1,122 @@
|
||||
# 📄 PRD: 智能演示文稿 (PPT) 生成后端 API (Agentic Workflow)
|
||||
|
||||
## 1. 项目概述 (Project Overview)
|
||||
|
||||
本项目旨在开发一个基于 Go (Gin 框架) 的后端 API 服务。该服务接收用户提供的「文本大纲」和「设计提示词(可选)」,通过大语言模型 (LLM) 进行结构化内容拆解与提示词工程,并利用并发调度图像生成模型 (如 Nano Banana 2) 生成幻灯片配图。
|
||||
|
||||
为保证系统稳定性和极佳的用户体验,系统采用**异步任务+前端定时轮询(3秒/次)**的架构。后端负责核心的并发控制、速率限制(Rate Limiting)和状态管理。
|
||||
|
||||
## 2. 技术栈约束 (Tech Stack)
|
||||
|
||||
- **语言/框架**: Go 1.21+, Gin Web Framework
|
||||
- **并发控制**: `golang.org/x/sync/errgroup`
|
||||
- **速率限制**: `golang.org/x/time/rate`
|
||||
- **状态管理**: `sync.Map` (内存级,后续可扩展至 Redis)
|
||||
- **数据交互**: JSON 格式,RESTful 风格
|
||||
|
||||
## 3. 核心 API 规范 (API Specification)
|
||||
|
||||
### 3.1 创建生成任务 (Create Task)
|
||||
|
||||
接收用户的原始文本和要求,立即返回任务 ID,不阻塞等待生成结果。
|
||||
|
||||
- **Endpoint**: `POST /api/v1/tasks/generate-slides`
|
||||
- **Request Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"content": "用户的 Markdown 笔记或大纲内容 (必填)",
|
||||
"prompt": "用户的附加设计要求,如:极简商务风、卡通手绘风 (选填)",
|
||||
"language": "用户选择的语言,如:中文、英文,必填",
|
||||
"pages": "用户选择的页数,如:5-20,必填"
|
||||
}
|
||||
```
|
||||
|
||||
- **Response Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "Task created successfully",
|
||||
"data": {
|
||||
"task_id": "uuid-v4-string",
|
||||
"status": "pending"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 查询任务进度 (Query Task Progress)
|
||||
|
||||
前端每隔 3 秒调用一次此接口,获取最新进度和已生成的幻灯片数据。
|
||||
|
||||
- **Endpoint**: `GET /api/v1/tasks/:task_id`
|
||||
- **Response Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"task_id": "uuid-v4-string",
|
||||
"status": "processing", // 状态枚举: pending, processing, completed, failed
|
||||
"progress": {
|
||||
"total_slides": 10,
|
||||
"completed_slides": 3,
|
||||
"percentage": 30
|
||||
},
|
||||
"slides": [
|
||||
{
|
||||
"slide_index": 1,
|
||||
"theme": "早晨环节",
|
||||
"title": "第一关:起床咕噜咕噜喝温水!",
|
||||
"points": ["水量:300-500ml", "唤醒身体小怪兽"],
|
||||
"image_url": "https://example.com/generated-image-url-1.png"
|
||||
}
|
||||
],
|
||||
"error_message": "" // 仅在 status 为 failed 时返回具体错误
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 核心工作流与状态机 (State Machine & Workflow)
|
||||
|
||||
### 4.1 任务初始化阶段
|
||||
|
||||
1. 接收 `POST` 请求,生成全局唯一的 `task_id`。
|
||||
2. 在 `TaskManager` (基于 `sync.Map`) 中初始化任务实例,初始化状态 `status = pending`,并加上并发安全锁 (`sync.Mutex`)。
|
||||
3. 启动独立的后台 Goroutine 接管耗时业务,主线程立即向客户端返回 `task_id`。
|
||||
|
||||
### 4.2 后台异步执行阶段
|
||||
|
||||
1. **LLM 解析 (状态变更: processing)**
|
||||
调用大语言模型解析传入的 `content` 和 `prompt`。必须注入以下 System Prompt 常量约束大模型输出:
|
||||
> # Role
|
||||
|
||||
> 你是一位顶级的专业演示文稿(PPT)策划专家和 AI 图像提示词(Prompt)工程师。任务是根据用户提供的「内容大纲」或「设计要求」,生成一套逻辑清晰、视觉风格高度统一的幻灯片分镜数据。
|
||||
|
||||
> # Rules
|
||||
|
||||
> 1. 全局风格锚定:根据大纲推断或遵循用户要求的全局视觉风格。所有配图必须严格遵循此风格。
|
||||
> 2. 结构化拆解:合理拆分为多张幻灯片,单页最多 3-4 个简短要点。
|
||||
> 3. 视觉转译:为每页构思具体的画面描述 (image_prompt)。必须包含前缀 `[全局风格描述]`,必须包含后缀 `[画面左侧或右侧留出干净的纯色或虚化空间,用于排版文字。绝对不要在图片中生成任何英文字母、汉字或乱码。]`
|
||||
> 4. 严格输出合法的纯 JSON 数组 `[{"slide_index": 1, "theme": "...", "title": "...", "points": ["..."], "image_prompt": "..."}]`,禁止使用 Markdown 标记。
|
||||
|
||||
2. **获取总数**
|
||||
解析成功后,更新该任务的 `total_slides` 字段,`completed_slides` 保持为 0。
|
||||
3. **并发绘图与限流调度 (核心挑战)**
|
||||
|
||||
- 启动 `errgroup.WithContext` 进行并发调度,设置 `g.SetLimit(3)` 控制最大并发线程。
|
||||
- 引入令牌桶限流器 `rate.NewLimiter(rate.Every(1*time.Second), 1)` 控制 API 外部请求频率(QPS = 1)。
|
||||
- 必须实现**指数退避重试 (Exponential Backoff)**:遇到 HTTP 429 错误时,等待 2s, 4s, 8s 后重试,最高重试 3 次。
|
||||
- **进度更新**:每成功生成一张图片,必须获取 `Task` 实例的写锁,将 `completed_slides` 加 1,并将完整的 `SlideData` 追加到任务的 `slides` 数组中,以便前端下一秒轮询时能拿到最新切片。
|
||||
|
||||
### 4.3 任务收尾阶段
|
||||
|
||||
- 正常结束:`errgroup.Wait()` 返回 nil,状态变更为 `completed`。
|
||||
- 异常中断:发生超过最大重试次数的错误或解析失败,状态变更为 `failed`,并记录错误原因至 `error_message`。
|
||||
|
||||
请按照以下顺序逐步实现 Go 后端代码,每完成一步请与我确认:
|
||||
|
||||
1. **核心模型与接口定义**:定义 `Task` 结构体(包含 sync.Mutex)、DTO 结构体以及 Gin 的路由组搭建。
|
||||
2. **状态管理器实现**:实现一个基于 `sync.Map` 的 `TaskManager`,提供安全的 `CreateTask`、`GetTask`、`UpdateProgress` 和 `MarkAsFailed` 方法。
|
||||
3. **限流与重试 HTTP 客户端**:实现 `callImageAPIWithRetry` 函数,内部封装 `rate.Limiter` 的等待逻辑和针对 429 状态码的指数退避重试逻辑(可先用 Mock 数据代替真实 HTTP 请求)。
|
||||
4. **组装核心 Handler**:实现 `POST` 和 `GET` 控制器。在 `POST` 的后台 Goroutine 中,串联 LLM Mock 解析和 `errgroup` 并发画图流程,确保在循环中安全地调用 `TaskManager.UpdateProgress` 更新进度和结果数组。
|
||||
Reference in New Issue
Block a user