@@ -0,0 +1,794 @@
/**
* AI创作中心场景
*/
import BaseScene from './BaseScene' ;
export default class AICreateScene extends BaseScene {
constructor ( main , params ) {
super ( main , params ) ;
this . currentTab = 0 ; // 0:改写 1:续写 2:创作
this . tabs = [ 'AI改写' , 'AI续写' , 'AI创作' ] ;
// 滚动
this . scrollY = 0 ;
this . maxScrollY = 0 ;
this . isDragging = false ;
this . lastTouchY = 0 ;
this . hasMoved = false ;
// 用户数据
this . recentStories = [ ] ;
this . aiHistory = [ ] ;
this . quota = { daily : 3 , used : 0 , purchased : 0 } ;
// 创作表单
this . createForm = {
genre : '' ,
keywords : '' ,
protagonist : '' ,
conflict : ''
} ;
// 选中的故事(用于改写/续写)
this . selectedStory = null ;
// 快捷标签
this . rewriteTags = [ '主角逆袭' , '甜蜜HE' , '虐心BE' , '反转剧情' , '意外重逢' , '身份揭秘' ] ;
this . continueTags = [ '增加悬念' , '感情升温' , '冲突加剧' , '真相大白' , '误会解除' ] ;
this . genreTags = [ '都市言情' , '古风宫廷' , '悬疑推理' , '校园青春' , '修仙玄幻' , '职场商战' ] ;
}
async init ( ) {
await this . loadData ( ) ;
}
async loadData ( ) {
try {
// 加载最近游玩的故事
this . recentStories = await this . main . userManager . getRecentPlayed ( ) || [ ] ;
// 加载AI创作历史
this . aiHistory = await this . main . userManager . getAIHistory ( ) || [ ] ;
// 加载配额
const quotaData = await this . main . userManager . getAIQuota ( ) ;
if ( quotaData ) this . quota = quotaData ;
} catch ( e ) {
console . error ( '加载数据失败:' , e ) ;
}
this . calculateMaxScroll ( ) ;
}
calculateMaxScroll ( ) {
let contentHeight = 400 ;
if ( this . currentTab === 0 || this . currentTab === 1 ) {
contentHeight = 300 + this . recentStories . length * 80 ;
} else {
contentHeight = 600 ;
}
this . maxScrollY = Math . max ( 0 , contentHeight - this . screenHeight + 200 ) ;
}
update ( ) { }
render ( ctx ) {
this . renderBackground ( ctx ) ;
this . renderHeader ( ctx ) ;
this . renderQuotaBar ( ctx ) ;
this . renderTabs ( ctx ) ;
this . renderContent ( ctx ) ;
}
renderBackground ( ctx ) {
const gradient = ctx . createLinearGradient ( 0 , 0 , 0 , this . screenHeight ) ;
gradient . addColorStop ( 0 , '#0f0c29' ) ;
gradient . addColorStop ( 0.5 , '#302b63' ) ;
gradient . addColorStop ( 1 , '#24243e' ) ;
ctx . fillStyle = gradient ;
ctx . fillRect ( 0 , 0 , this . screenWidth , this . screenHeight ) ;
// 装饰光效
const glow = ctx . createRadialGradient ( this . screenWidth / 2 , 150 , 0 , this . screenWidth / 2 , 150 , 200 ) ;
glow . addColorStop ( 0 , 'rgba(168, 85, 247, 0.15)' ) ;
glow . addColorStop ( 1 , 'transparent' ) ;
ctx . fillStyle = glow ;
ctx . fillRect ( 0 , 0 , this . screenWidth , 300 ) ;
}
renderHeader ( ctx ) {
// 顶部遮罩
const headerGradient = ctx . createLinearGradient ( 0 , 0 , 0 , 80 ) ;
headerGradient . addColorStop ( 0 , 'rgba(15,12,41,1)' ) ;
headerGradient . addColorStop ( 1 , 'rgba(15,12,41,0)' ) ;
ctx . fillStyle = headerGradient ;
ctx . fillRect ( 0 , 0 , this . screenWidth , 80 ) ;
// 返回按钮
ctx . fillStyle = '#ffffff' ;
ctx . font = '16px sans-serif' ;
ctx . textAlign = 'left' ;
ctx . fillText ( '‹ 返回' , 15 , 40 ) ;
// 标题
ctx . textAlign = 'center' ;
ctx . font = 'bold 18px sans-serif' ;
const titleGradient = ctx . createLinearGradient ( this . screenWidth / 2 - 50 , 0 , this . screenWidth / 2 + 50 , 0 ) ;
titleGradient . addColorStop ( 0 , '#a855f7' ) ;
titleGradient . addColorStop ( 1 , '#ec4899' ) ;
ctx . fillStyle = titleGradient ;
ctx . fillText ( '✨ AI创作中心' , this . screenWidth / 2 , 40 ) ;
}
renderQuotaBar ( ctx ) {
const barY = 60 ;
const remaining = this . quota . daily - this . quota . used + this . quota . purchased ;
// 配额背景
ctx . fillStyle = 'rgba(255,255,255,0.08)' ;
this . roundRect ( ctx , 15 , barY , this . screenWidth - 30 , 36 , 18 ) ;
ctx . fill ( ) ;
// 配额文字
ctx . fillStyle = 'rgba(255,255,255,0.7)' ;
ctx . font = '12px sans-serif' ;
ctx . textAlign = 'left' ;
ctx . fillText ( ` 今日剩余: ${ remaining } 次 ` , 30 , barY + 23 ) ;
// 充值按钮
const btnWidth = 70 ;
const btnX = this . screenWidth - 30 - btnWidth ;
const btnGradient = ctx . createLinearGradient ( btnX , barY + 5 , btnX + btnWidth , barY + 5 ) ;
btnGradient . addColorStop ( 0 , '#a855f7' ) ;
btnGradient . addColorStop ( 1 , '#ec4899' ) ;
ctx . fillStyle = btnGradient ;
this . roundRect ( ctx , btnX , barY + 5 , btnWidth , 26 , 13 ) ;
ctx . fill ( ) ;
ctx . fillStyle = '#ffffff' ;
ctx . font = 'bold 11px sans-serif' ;
ctx . textAlign = 'center' ;
ctx . fillText ( '获取更多' , btnX + btnWidth / 2 , barY + 22 ) ;
this . quotaBtnRect = { x : btnX , y : barY + 5 , width : btnWidth , height : 26 } ;
}
renderTabs ( ctx ) {
const tabY = 110 ;
const tabWidth = ( this . screenWidth - 40 ) / 3 ;
const padding = 15 ;
this . tabRects = [ ] ;
this . tabs . forEach ( ( tab , index ) => {
const x = padding + index * ( tabWidth + 5 ) ;
const isActive = index === this . currentTab ;
if ( isActive ) {
const gradient = ctx . createLinearGradient ( x , tabY , x + tabWidth , tabY ) ;
gradient . addColorStop ( 0 , '#a855f7' ) ;
gradient . addColorStop ( 1 , '#ec4899' ) ;
ctx . fillStyle = gradient ;
} else {
ctx . fillStyle = 'rgba(255,255,255,0.1)' ;
}
this . roundRect ( ctx , x , tabY , tabWidth , 36 , 18 ) ;
ctx . fill ( ) ;
ctx . fillStyle = isActive ? '#ffffff' : 'rgba(255,255,255,0.6)' ;
ctx . font = isActive ? 'bold 13px sans-serif' : '13px sans-serif' ;
ctx . textAlign = 'center' ;
ctx . fillText ( tab , x + tabWidth / 2 , tabY + 23 ) ;
this . tabRects . push ( { x , y : tabY , width : tabWidth , height : 36 , index } ) ;
} ) ;
}
renderContent ( ctx ) {
const contentY = 160 ;
ctx . save ( ) ;
ctx . beginPath ( ) ;
ctx . rect ( 0 , contentY , this . screenWidth , this . screenHeight - contentY ) ;
ctx . clip ( ) ;
switch ( this . currentTab ) {
case 0 :
this . renderRewriteTab ( ctx , contentY ) ;
break ;
case 1 :
this . renderContinueTab ( ctx , contentY ) ;
break ;
case 2 :
this . renderCreateTab ( ctx , contentY ) ;
break ;
}
ctx . restore ( ) ;
}
renderRewriteTab ( ctx , startY ) {
const y = startY - this . scrollY ;
const padding = 15 ;
// 说明文字
ctx . fillStyle = 'rgba(255,255,255,0.6)' ;
ctx . font = '13px sans-serif' ;
ctx . textAlign = 'center' ;
ctx . fillText ( '选择一个已玩过的故事, AI帮你改写结局' , this . screenWidth / 2 , y + 25 ) ;
// 快捷标签
ctx . fillStyle = 'rgba(255,255,255,0.8)' ;
ctx . font = '12px sans-serif' ;
ctx . textAlign = 'left' ;
ctx . fillText ( '热门改写方向:' , padding , y + 60 ) ;
this . renderTags ( ctx , this . rewriteTags , padding , y + 75 , 'rewrite' ) ;
// 选择故事
ctx . fillStyle = 'rgba(255,255,255,0.8)' ;
ctx . font = '13px sans-serif' ;
ctx . fillText ( '选择要改写的故事:' , padding , y + 145 ) ;
this . renderStoryList ( ctx , y + 160 , 'rewrite' ) ;
}
renderContinueTab ( ctx , startY ) {
const y = startY - this . scrollY ;
const padding = 15 ;
ctx . fillStyle = 'rgba(255,255,255,0.6)' ;
ctx . font = '13px sans-serif' ;
ctx . textAlign = 'center' ;
ctx . fillText ( '选择一个进行中的故事, AI帮你续写剧情' , this . screenWidth / 2 , y + 25 ) ;
ctx . fillStyle = 'rgba(255,255,255,0.8)' ;
ctx . font = '12px sans-serif' ;
ctx . textAlign = 'left' ;
ctx . fillText ( '续写方向:' , padding , y + 60 ) ;
this . renderTags ( ctx , this . continueTags , padding , y + 75 , 'continue' ) ;
ctx . fillStyle = 'rgba(255,255,255,0.8)' ;
ctx . font = '13px sans-serif' ;
ctx . fillText ( '选择要续写的故事:' , padding , y + 145 ) ;
this . renderStoryList ( ctx , y + 160 , 'continue' ) ;
}
renderCreateTab ( ctx , startY ) {
const y = startY - this . scrollY ;
const padding = 15 ;
const inputWidth = this . screenWidth - padding * 2 ;
ctx . fillStyle = 'rgba(255,255,255,0.6)' ;
ctx . font = '13px sans-serif' ;
ctx . textAlign = 'center' ;
ctx . fillText ( '输入关键词, AI为你创作全新故事' , this . screenWidth / 2 , y + 25 ) ;
// 题材选择
ctx . fillStyle = 'rgba(255,255,255,0.8)' ;
ctx . font = '13px sans-serif' ;
ctx . textAlign = 'left' ;
ctx . fillText ( '选择题材:' , padding , y + 60 ) ;
this . renderTags ( ctx , this . genreTags , padding , y + 75 , 'genre' ) ;
// 关键词输入
ctx . fillText ( '故事关键词:' , padding , y + 145 ) ;
this . renderInputBox ( ctx , padding , y + 160 , inputWidth , 45 , this . createForm . keywords || '例如:霸总、契约婚姻、追妻火葬场' , 'keywords' ) ;
// 主角设定
ctx . fillText ( '主角设定:' , padding , y + 225 ) ;
this . renderInputBox ( ctx , padding , y + 240 , inputWidth , 45 , this . createForm . protagonist || '例如:独立女性设计师' , 'protagonist' ) ;
// 核心冲突
ctx . fillText ( '核心冲突:' , padding , y + 305 ) ;
this . renderInputBox ( ctx , padding , y + 320 , inputWidth , 45 , this . createForm . conflict || '例如:假结婚变真爱' , 'conflict' ) ;
// 开始创作按钮
const btnY = y + 400 ;
const btnGradient = ctx . createLinearGradient ( padding , btnY , this . screenWidth - padding , btnY ) ;
btnGradient . addColorStop ( 0 , '#a855f7' ) ;
btnGradient . addColorStop ( 1 , '#ec4899' ) ;
ctx . fillStyle = btnGradient ;
this . roundRect ( ctx , padding , btnY , inputWidth , 50 , 25 ) ;
ctx . fill ( ) ;
ctx . fillStyle = '#ffffff' ;
ctx . font = 'bold 16px sans-serif' ;
ctx . textAlign = 'center' ;
ctx . fillText ( '✨ 开始AI创作' , this . screenWidth / 2 , btnY + 32 ) ;
this . createBtnRect = { x : padding , y : btnY + this . scrollY , width : inputWidth , height : 50 } ;
}
renderTags ( ctx , tags , startX , startY , type ) {
const tagHeight = 30 ;
const tagGap = 8 ;
let currentX = startX ;
let currentY = startY ;
if ( ! this . tagRects ) this . tagRects = { } ;
this . tagRects [ type ] = [ ] ;
tags . forEach ( ( tag , index ) => {
ctx . font = '12px sans-serif' ;
const tagWidth = ctx . measureText ( tag ) . width + 20 ;
if ( currentX + tagWidth > this . screenWidth - 15 ) {
currentX = startX ;
currentY += tagHeight + tagGap ;
}
const isSelected = ( type === 'genre' && this . createForm . genre === tag ) ||
( type === 'rewrite' && this . selectedRewriteTag === index ) ||
( type === 'continue' && this . selectedContinueTag === index ) ;
if ( isSelected ) {
const gradient = ctx . createLinearGradient ( currentX , currentY , currentX + tagWidth , currentY ) ;
gradient . addColorStop ( 0 , '#a855f7' ) ;
gradient . addColorStop ( 1 , '#ec4899' ) ;
ctx . fillStyle = gradient ;
} else {
ctx . fillStyle = 'rgba(255,255,255,0.1)' ;
}
this . roundRect ( ctx , currentX , currentY , tagWidth , tagHeight , 15 ) ;
ctx . fill ( ) ;
if ( ! isSelected ) {
ctx . strokeStyle = 'rgba(255,255,255,0.2)' ;
ctx . lineWidth = 1 ;
this . roundRect ( ctx , currentX , currentY , tagWidth , tagHeight , 15 ) ;
ctx . stroke ( ) ;
}
ctx . fillStyle = '#ffffff' ;
ctx . textAlign = 'center' ;
ctx . fillText ( tag , currentX + tagWidth / 2 , currentY + 20 ) ;
this . tagRects [ type ] . push ( {
x : currentX ,
y : currentY + this . scrollY ,
width : tagWidth ,
height : tagHeight ,
index ,
value : tag
} ) ;
currentX += tagWidth + tagGap ;
} ) ;
}
renderInputBox ( ctx , x , y , width , height , placeholder , field ) {
ctx . fillStyle = 'rgba(255,255,255,0.08)' ;
this . roundRect ( ctx , x , y , width , height , 12 ) ;
ctx . fill ( ) ;
ctx . strokeStyle = 'rgba(255,255,255,0.2)' ;
ctx . lineWidth = 1 ;
this . roundRect ( ctx , x , y , width , height , 12 ) ;
ctx . stroke ( ) ;
ctx . font = '13px sans-serif' ;
ctx . textAlign = 'left' ;
if ( this . createForm [ field ] ) {
ctx . fillStyle = '#ffffff' ;
ctx . fillText ( this . createForm [ field ] , x + 15 , y + height / 2 + 5 ) ;
} else {
ctx . fillStyle = 'rgba(255,255,255,0.4)' ;
ctx . fillText ( placeholder , x + 15 , y + height / 2 + 5 ) ;
}
if ( ! this . inputRects ) this . inputRects = { } ;
this . inputRects [ field ] = { x , y : y + this . scrollY , width , height , field } ;
}
renderStoryList ( ctx , startY , type ) {
const padding = 15 ;
const cardHeight = 70 ;
const cardGap = 10 ;
if ( ! this . storyRects ) this . storyRects = { } ;
this . storyRects [ type ] = [ ] ;
if ( this . recentStories . length === 0 ) {
ctx . fillStyle = 'rgba(255,255,255,0.4)' ;
ctx . font = '13px sans-serif' ;
ctx . textAlign = 'center' ;
ctx . fillText ( '暂无游玩记录,去首页体验故事吧' , this . screenWidth / 2 , startY + 40 ) ;
return ;
}
this . recentStories . forEach ( ( story , index ) => {
const y = startY + index * ( cardHeight + cardGap ) ;
const isSelected = this . selectedStory ? . id === story . id ;
// 卡片背景
if ( isSelected ) {
ctx . fillStyle = 'rgba(168, 85, 247, 0.2)' ;
} else {
ctx . fillStyle = 'rgba(255,255,255,0.06)' ;
}
this . roundRect ( ctx , padding , y , this . screenWidth - padding * 2 , cardHeight , 12 ) ;
ctx . fill ( ) ;
if ( isSelected ) {
ctx . strokeStyle = '#a855f7' ;
ctx . lineWidth = 2 ;
this . roundRect ( ctx , padding , y , this . screenWidth - padding * 2 , cardHeight , 12 ) ;
ctx . stroke ( ) ;
}
// 封面占位
const coverSize = 50 ;
ctx . fillStyle = 'rgba(255,255,255,0.1)' ;
this . roundRect ( ctx , padding + 10 , y + 10 , coverSize , coverSize , 8 ) ;
ctx . fill ( ) ;
// 故事标题
ctx . fillStyle = '#ffffff' ;
ctx . font = 'bold 14px sans-serif' ;
ctx . textAlign = 'left' ;
const title = story . title ? . length > 12 ? story . title . substring ( 0 , 12 ) + '...' : story . title ;
ctx . fillText ( title || '未知故事' , padding + 70 , y + 28 ) ;
// 分类和进度
ctx . fillStyle = 'rgba(255,255,255,0.5)' ;
ctx . font = '11px sans-serif' ;
ctx . fillText ( ` ${ story . category || '未分类' } · ${ story . progress || '进行中' } ` , padding + 70 , y + 50 ) ;
// 选择按钮
const btnX = this . screenWidth - padding - 60 ;
ctx . fillStyle = isSelected ? '#a855f7' : 'rgba(255,255,255,0.2)' ;
this . roundRect ( ctx , btnX , y + 20 , 50 , 30 , 15 ) ;
ctx . fill ( ) ;
ctx . fillStyle = '#ffffff' ;
ctx . font = '11px sans-serif' ;
ctx . textAlign = 'center' ;
ctx . fillText ( isSelected ? '已选' : '选择' , btnX + 25 , y + 40 ) ;
this . storyRects [ type ] . push ( {
x : padding ,
y : y + this . scrollY ,
width : this . screenWidth - padding * 2 ,
height : cardHeight ,
story
} ) ;
} ) ;
// 开始按钮
if ( this . selectedStory ) {
const btnY = startY + this . recentStories . length * ( cardHeight + cardGap ) + 20 ;
const btnGradient = ctx . createLinearGradient ( padding , btnY , this . screenWidth - padding , btnY ) ;
btnGradient . addColorStop ( 0 , '#a855f7' ) ;
btnGradient . addColorStop ( 1 , '#ec4899' ) ;
ctx . fillStyle = btnGradient ;
this . roundRect ( ctx , padding , btnY , this . screenWidth - padding * 2 , 48 , 24 ) ;
ctx . fill ( ) ;
ctx . fillStyle = '#ffffff' ;
ctx . font = 'bold 15px sans-serif' ;
ctx . textAlign = 'center' ;
const btnText = type === 'rewrite' ? '✨ 开始AI改写' : '✨ 开始AI续写' ;
ctx . fillText ( btnText , this . screenWidth / 2 , btnY + 30 ) ;
this . actionBtnRect = {
x : padding ,
y : btnY + this . scrollY ,
width : this . screenWidth - padding * 2 ,
height : 48 ,
type
} ;
}
}
roundRect ( ctx , x , y , width , height , radius ) {
ctx . beginPath ( ) ;
ctx . moveTo ( x + radius , y ) ;
ctx . lineTo ( x + width - radius , y ) ;
ctx . quadraticCurveTo ( x + width , y , x + width , y + radius ) ;
ctx . lineTo ( x + width , y + height - radius ) ;
ctx . quadraticCurveTo ( x + width , y + height , x + width - radius , y + height ) ;
ctx . lineTo ( x + radius , y + height ) ;
ctx . quadraticCurveTo ( x , y + height , x , y + height - radius ) ;
ctx . lineTo ( x , y + radius ) ;
ctx . quadraticCurveTo ( x , y , x + radius , y ) ;
ctx . closePath ( ) ;
}
onTouchStart ( e ) {
const touch = e . touches [ 0 ] ;
this . lastTouchY = touch . clientY ;
this . touchStartY = touch . clientY ;
this . hasMoved = false ;
if ( touch . clientY > 160 ) {
this . isDragging = true ;
}
}
onTouchMove ( e ) {
if ( ! this . isDragging ) return ;
const touch = e . touches [ 0 ] ;
const deltaY = this . lastTouchY - touch . clientY ;
if ( Math . abs ( deltaY ) > 3 ) {
this . hasMoved = true ;
}
this . scrollY += deltaY ;
this . scrollY = Math . max ( 0 , Math . min ( this . scrollY , this . maxScrollY ) ) ;
this . lastTouchY = touch . clientY ;
}
onTouchEnd ( e ) {
this . isDragging = false ;
if ( this . hasMoved ) return ;
const touch = e . changedTouches [ 0 ] ;
const x = touch . clientX ;
const y = touch . clientY ;
// 返回按钮
if ( y < 60 && x < 80 ) {
this . main . sceneManager . switchScene ( 'home' ) ;
return ;
}
// 配额按钮
if ( this . quotaBtnRect && this . isInRect ( x , y , this . quotaBtnRect ) ) {
this . showQuotaModal ( ) ;
return ;
}
// Tab切换
if ( this . tabRects ) {
for ( const tab of this . tabRects ) {
if ( this . isInRect ( x , y , tab ) ) {
if ( this . currentTab !== tab . index ) {
this . currentTab = tab . index ;
this . scrollY = 0 ;
this . selectedStory = null ;
this . calculateMaxScroll ( ) ;
}
return ;
}
}
}
// 调整y坐标( 考虑滚动)
const scrolledY = y + this . scrollY ;
// 标签点击
if ( this . tagRects ) {
const tagType = this . currentTab === 0 ? 'rewrite' : this . currentTab === 1 ? 'continue' : 'genre' ;
const tags = this . tagRects [ tagType ] ;
if ( tags ) {
for ( const tag of tags ) {
if ( this . isInRect ( x , scrolledY , tag ) ) {
this . handleTagSelect ( tagType , tag ) ;
return ;
}
}
}
}
// 输入框点击( 创作Tab)
if ( this . currentTab === 2 && this . inputRects ) {
for ( const key in this . inputRects ) {
const rect = this . inputRects [ key ] ;
if ( this . isInRect ( x , scrolledY , rect ) ) {
this . showInputModal ( rect . field ) ;
return ;
}
}
}
// 故事列表点击
if ( this . currentTab < 2 && this . storyRects ) {
const type = this . currentTab === 0 ? 'rewrite' : 'continue' ;
const stories = this . storyRects [ type ] ;
if ( stories ) {
for ( const rect of stories ) {
if ( this . isInRect ( x , scrolledY , rect ) ) {
this . selectedStory = rect . story ;
return ;
}
}
}
}
// 操作按钮
if ( this . actionBtnRect && this . isInRect ( x , scrolledY , this . actionBtnRect ) ) {
this . handleAction ( this . actionBtnRect . type ) ;
return ;
}
// 创作按钮
if ( this . currentTab === 2 && this . createBtnRect && this . isInRect ( x , scrolledY , this . createBtnRect ) ) {
this . handleCreate ( ) ;
return ;
}
}
isInRect ( x , y , rect ) {
return x >= rect . x && x <= rect . x + rect . width && y >= rect . y && y <= rect . y + rect . height ;
}
handleTagSelect ( type , tag ) {
if ( type === 'genre' ) {
this . createForm . genre = tag . value ;
} else if ( type === 'rewrite' ) {
this . selectedRewriteTag = tag . index ;
} else if ( type === 'continue' ) {
this . selectedContinueTag = tag . index ;
}
}
showInputModal ( field ) {
const titles = {
keywords : '输入故事关键词' ,
protagonist : '输入主角设定' ,
conflict : '输入核心冲突'
} ;
wx . showModal ( {
title : titles [ field ] ,
editable : true ,
placeholderText : '请输入...' ,
content : this . createForm [ field ] || '' ,
success : ( res ) => {
if ( res . confirm && res . content ) {
this . createForm [ field ] = res . content ;
}
}
} ) ;
}
showQuotaModal ( ) {
wx . showModal ( {
title : 'AI创作次数' ,
content : ` 今日剩余 ${ this . quota . daily - this . quota . used } 次 \n 购买次数 ${ this . quota . purchased } 次 \n \n 观看广告可获得1次 ` ,
confirmText : '看广告' ,
success : ( res ) => {
if ( res . confirm ) {
this . watchAdForQuota ( ) ;
}
}
} ) ;
}
watchAdForQuota ( ) {
wx . showToast ( { title : '获得1次AI次数' , icon : 'success' } ) ;
this . quota . purchased += 1 ;
}
handleAction ( type ) {
if ( ! this . selectedStory ) {
wx . showToast ( { title : '请先选择故事' , icon : 'none' } ) ;
return ;
}
const remaining = this . quota . daily - this . quota . used + this . quota . purchased ;
if ( remaining <= 0 ) {
this . showQuotaModal ( ) ;
return ;
}
if ( type === 'rewrite' ) {
this . startRewrite ( ) ;
} else {
this . startContinue ( ) ;
}
}
startRewrite ( ) {
const tag = this . selectedRewriteTag !== undefined ? this . rewriteTags [ this . selectedRewriteTag ] : '' ;
wx . showModal ( {
title : 'AI改写' ,
content : '确定要改写这个故事的结局吗?' ,
editable : true ,
placeholderText : tag || '输入改写方向(可选)' ,
success : async ( res ) => {
if ( res . confirm ) {
wx . showLoading ( { title : 'AI创作中...' } ) ;
try {
const result = await this . main . storyManager . rewriteEnding (
this . selectedStory . id ,
{ name : '当前结局' , content : '' } ,
res . content || tag || '改写结局'
) ;
wx . hideLoading ( ) ;
if ( result ) {
this . quota . used += 1 ;
this . main . sceneManager . switchScene ( 'story' , {
storyId : this . selectedStory . id ,
aiContent : result
} ) ;
}
} catch ( e ) {
wx . hideLoading ( ) ;
wx . showToast ( { title : '创作失败' , icon : 'none' } ) ;
}
}
}
} ) ;
}
startContinue ( ) {
const tag = this . selectedContinueTag !== undefined ? this . continueTags [ this . selectedContinueTag ] : '' ;
wx . showModal ( {
title : 'AI续写' ,
content : '确定要让AI续写这个故事吗? ' ,
editable : true ,
placeholderText : tag || '输入续写方向(可选)' ,
success : async ( res ) => {
if ( res . confirm ) {
wx . showLoading ( { title : 'AI创作中...' } ) ;
try {
// TODO: 实现续写API
const result = await this . main . storyManager . continueStory (
this . selectedStory . id ,
res . content || tag || '续写剧情'
) ;
wx . hideLoading ( ) ;
if ( result ) {
this . quota . used += 1 ;
this . main . sceneManager . switchScene ( 'story' , {
storyId : this . selectedStory . id ,
aiContent : result
} ) ;
}
} catch ( e ) {
wx . hideLoading ( ) ;
wx . showToast ( { title : '创作失败' , icon : 'none' } ) ;
}
}
}
} ) ;
}
handleCreate ( ) {
if ( ! this . createForm . genre ) {
wx . showToast ( { title : '请选择题材' , icon : 'none' } ) ;
return ;
}
if ( ! this . createForm . keywords ) {
wx . showToast ( { title : '请输入关键词' , icon : 'none' } ) ;
return ;
}
const remaining = this . quota . daily - this . quota . used + this . quota . purchased ;
if ( remaining < 5 ) {
wx . showModal ( {
title : '次数不足' ,
content : 'AI创作需要5次配额, 当前剩余' + remaining + '次' ,
confirmText : '获取更多' ,
success : ( res ) => {
if ( res . confirm ) this . showQuotaModal ( ) ;
}
} ) ;
return ;
}
wx . showModal ( {
title : '确认创作' ,
content : ` 题材: ${ this . createForm . genre } \n 关键词: ${ this . createForm . keywords } \n \n 将消耗5次AI次数 ` ,
success : async ( res ) => {
if ( res . confirm ) {
wx . showLoading ( { title : 'AI创作中...' , mask : true } ) ;
try {
// TODO: 实现完整创作API
const result = await this . main . storyManager . createStory ( this . createForm ) ;
wx . hideLoading ( ) ;
if ( result ) {
this . quota . used += 5 ;
wx . showToast ( { title : '创作成功!' , icon : 'success' } ) ;
// 跳转到新故事
setTimeout ( ( ) => {
this . main . sceneManager . switchScene ( 'story' , { storyId : result . storyId } ) ;
} , 1500 ) ;
}
} catch ( e ) {
wx . hideLoading ( ) ;
wx . showToast ( { title : '创作失败' , icon : 'none' } ) ;
}
}
}
} ) ;
}
}