Files
geekai/api/service/ppt/slide_edit.go
T
RockYang 9ccff4efbc 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
2026-08-11 14:51:20 +08:00

183 lines
4.3 KiB
Go

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
}