mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-08-22 23:37:13 +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:
@@ -0,0 +1,182 @@
|
||||
package ppt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"geekai/core/types"
|
||||
"geekai/store/model"
|
||||
"geekai/store/vo"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EditSlideImage 基于当前激活图做图生图,追加 image_history 并将 image_url 设为新版。
|
||||
func (s *PptService) EditSlideImage(ctx context.Context, taskID string, userID uint, slideIndex int, prompt string, oss types.OSSConfig, app *types.AppConfig) ([]SlideData, error) {
|
||||
prompt = strings.TrimSpace(prompt)
|
||||
if prompt == "" {
|
||||
return nil, fmt.Errorf("请输入修改说明")
|
||||
}
|
||||
task, ok := s.GetTask(taskID)
|
||||
if !ok {
|
||||
return nil, ErrPptTaskNotFound
|
||||
}
|
||||
if task.UserID != userID {
|
||||
return nil, ErrPptTaskNotFound
|
||||
}
|
||||
refURL := ""
|
||||
for _, sl := range task.Slides {
|
||||
if sl.SlideIndex == slideIndex {
|
||||
normalizeSlideImageHistory(&sl)
|
||||
refURL = strings.TrimSpace(sl.ImageURL)
|
||||
break
|
||||
}
|
||||
}
|
||||
if refURL == "" {
|
||||
if slideExists(task.Slides, slideIndex) {
|
||||
return nil, ErrPptSlideNoImage
|
||||
}
|
||||
return nil, ErrPptSlideNotFound
|
||||
}
|
||||
|
||||
cfg, err := s.loadPPTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
power, err := s.userPower(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cfg.PowerCostPerSlide > 0 && power < cfg.PowerCostPerSlide {
|
||||
return nil, ErrInsufficientPower
|
||||
}
|
||||
|
||||
generator, err := NewImageGenerator(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refInputs, err := PrepareReferenceInputsForImg2Img(refURL, oss, app)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("准备参考图失败:%w", err)
|
||||
}
|
||||
|
||||
imgURL, err := generator.GenerateWithReference(ctx, prompt, refInputs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storedURL, err := s.uploadManager.GetUploadHandler().PutUrlFile(imgURL, ".png", false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("转存图片失败:%w", err)
|
||||
}
|
||||
|
||||
if err := s.applySlideImageEdit(taskID, slideIndex, storedURL, prompt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cfg.PowerCostPerSlide > 0 {
|
||||
err = s.userService.DecreasePower(userID, cfg.PowerCostPerSlide, model.PowerLog{
|
||||
Type: types.PowerConsume,
|
||||
Model: generator.Provider(),
|
||||
Remark: fmt.Sprintf("PPT 任务 %s 第 %d 页图生图编辑", taskID, slideIndex),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("扣减算力失败:%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
task2, _ := s.GetTask(taskID)
|
||||
return task2.Slides, nil
|
||||
}
|
||||
|
||||
func slideExists(slides []SlideData, slideIndex int) bool {
|
||||
for _, sl := range slides {
|
||||
if sl.SlideIndex == slideIndex {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *PptService) applySlideImageEdit(taskID string, slideIndex int, newURL string, editPrompt string) error {
|
||||
s.slidesLock.Lock()
|
||||
defer s.slidesLock.Unlock()
|
||||
|
||||
var job model.PPTJob
|
||||
if err := s.db.Where("task_id = ?", taskID).First(&job).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
slides := job.Slides
|
||||
found := false
|
||||
for i := range slides {
|
||||
if slides[i].SlideIndex != slideIndex {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
sd := voToSlide(slides[i])
|
||||
normalizeSlideImageHistory(&sd)
|
||||
if strings.TrimSpace(sd.ImageURL) == "" {
|
||||
return ErrPptSlideNoImage
|
||||
}
|
||||
sd.ImageHistory = append(sd.ImageHistory, vo.PPTSlideImageVersion{
|
||||
ImageURL: newURL,
|
||||
Prompt: editPrompt,
|
||||
})
|
||||
sd.ImageURL = newURL
|
||||
slides[i] = slideToVO(sd)
|
||||
break
|
||||
}
|
||||
if !found {
|
||||
return ErrPptSlideNotFound
|
||||
}
|
||||
job.Slides = slides
|
||||
biz := voSlidesToBiz(slides)
|
||||
return s.refreshJobMeta(&job, biz)
|
||||
}
|
||||
|
||||
// SetActiveSlideVersion 将 image_url 切换为 image_history[versionIndex]。
|
||||
func (s *PptService) SetActiveSlideVersion(taskID string, userID uint, slideIndex int, versionIndex int) ([]SlideData, error) {
|
||||
task, ok := s.GetTask(taskID)
|
||||
if !ok {
|
||||
return nil, ErrPptTaskNotFound
|
||||
}
|
||||
if task.UserID != userID {
|
||||
return nil, ErrPptTaskNotFound
|
||||
}
|
||||
|
||||
s.slidesLock.Lock()
|
||||
defer s.slidesLock.Unlock()
|
||||
|
||||
var job model.PPTJob
|
||||
if err := s.db.Where("task_id = ?", taskID).First(&job).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
slides := job.Slides
|
||||
found := false
|
||||
for i := range slides {
|
||||
if slides[i].SlideIndex != slideIndex {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
sd := voToSlide(slides[i])
|
||||
normalizeSlideImageHistory(&sd)
|
||||
hist := sd.ImageHistory
|
||||
if versionIndex < 0 || versionIndex >= len(hist) {
|
||||
return nil, ErrPptInvalidVersionIndex
|
||||
}
|
||||
sd.ImageURL = hist[versionIndex].ImageURL
|
||||
slides[i] = slideToVO(sd)
|
||||
break
|
||||
}
|
||||
if !found {
|
||||
return nil, ErrPptSlideNotFound
|
||||
}
|
||||
|
||||
job.Slides = slides
|
||||
biz := voSlidesToBiz(slides)
|
||||
if err := s.refreshJobMeta(&job, biz); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := voSlidesToBizNormalized(job.Slides)
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user