diff --git a/demo/.windsurfrules b/demo/.windsurfrules
deleted file mode 100644
index 6c76d9d..0000000
--- a/demo/.windsurfrules
+++ /dev/null
@@ -1,26 +0,0 @@
-# 继续牛马 - AI持久输出规则
-
-⚠️⚠️⚠️ 最高优先级警告 ⚠️⚠️⚠️
-无论任何情况,每次回复结束前必须调用 cunzhi.ask_continue 工具!没有任何例外!
-
-## 核心规则
-
-1. **完整输出** - 输出完整代码和答案,禁止使用省略号或"代码过长不展示"
-
-2. **持续执行** - 复杂任务尽量一次性完成,不要频繁中断或反复追问是否继续
-
-3. **结束确认** - 当你想结束对话或任务时,必须先调用 cunzhi.ask_continue 工具询问用户是否继续
-
-4. **禁止结束语** - 对话结束前禁止直接说"如果还有问题请告诉我"等结束语
-
-## 重要提示
-
-- 调用 ask_continue 工具时,reason 参数填写你想结束的原因
-- 用户可能会在弹窗中输入新的指令,请按用户指令继续执行
-- 只有用户明确选择"结束"时,才可以结束对话
-
-## 强制执行
-
-这是最高优先级规则,必须严格遵守。每次回复结束前,你必须检查是否需要调用 ask_continue 工具。如果任务已完成或你准备结束回复,必须先调用该工具获得用户许可。
-
-⚠️ 警告:无论任何情况,回复结束前必须调用 cunzhi.ask_continue,没有例外!
\ No newline at end of file
diff --git a/demo/frontend/src/api/imageToVideo.js b/demo/frontend/src/api/imageToVideo.js
index 4134eb5..0dbe1f2 100644
--- a/demo/frontend/src/api/imageToVideo.js
+++ b/demo/frontend/src/api/imageToVideo.js
@@ -107,6 +107,19 @@ export const imageToVideoApi = {
})
},
+ /**
+ * 重试失败的任务
+ * 复用原task_id和已上传的图片,重新提交至外部API
+ * @param {string} taskId - 任务ID
+ * @returns {Promise} API响应
+ */
+ retryTask(taskId) {
+ return request({
+ url: `/image-to-video/tasks/${taskId}/retry`,
+ method: 'POST'
+ })
+ },
+
/**
* 轮询任务状态
* @param {string} taskId - 任务ID
diff --git a/demo/frontend/src/api/request.js b/demo/frontend/src/api/request.js
index 1c56d94..5c60019 100644
--- a/demo/frontend/src/api/request.js
+++ b/demo/frontend/src/api/request.js
@@ -41,9 +41,10 @@ api.interceptors.request.use(
const token = localStorage.getItem('token')
if (token && token !== 'null' && token.trim() !== '') {
config.headers.Authorization = `Bearer ${token}`
- console.log('请求拦截器:添加Authorization头,token长度:', token.length)
+ // 打印token前30字符用于调试
+ console.log('请求拦截器:添加Authorization头,token前30字符:', token.substring(0, 30), '请求URL:', config.url)
} else {
- console.warn('请求拦截器:未找到有效的token')
+ console.warn('请求拦截器:未找到有效的token,请求URL:', config.url)
}
} else {
console.log('请求拦截器:登录相关请求,不添加token:', config.url)
@@ -69,34 +70,47 @@ api.interceptors.response.use(
const isLoginRequest = loginUrls.some(url => response.config.url.includes(url))
if (!isLoginRequest) {
- // 清除无效的token并跳转到登录页
+ // 清除无效的token并跳转到欢迎页
localStorage.removeItem('token')
localStorage.removeItem('user')
// 避免重复跳转
- if (router.currentRoute.value.path !== '/login') {
- ElMessage.error('认证失败,请重新登录')
- router.push('/login')
+ if (router.currentRoute.value.path !== '/' && router.currentRoute.value.path !== '/login') {
+ ElMessage.warning('登录已过期,请重新登录')
+ router.push('/')
}
}
// 返回错误,让调用方知道这是认证失败
return Promise.reject(new Error('认证失败:收到HTML响应'))
}
- // 检查302重定向
- if (response.status === 302) {
- console.error('收到302重定向,可能是认证失败:', response.config.url)
+ // 检查401未授权(Token过期)
+ if (response.status === 401) {
+ console.error('收到401,Token已过期:', response.config.url)
- // 只有非登录请求才清除token并跳转
const loginUrls = ['/auth/login', '/auth/login/email', '/auth/register', '/verification/', '/public/']
const isLoginRequest = loginUrls.some(url => response.config.url.includes(url))
if (!isLoginRequest) {
localStorage.removeItem('token')
localStorage.removeItem('user')
- if (router.currentRoute.value.path !== '/login') {
- ElMessage.error('认证失败,请重新登录')
- router.push('/login')
- }
+ ElMessage.warning('登录已过期,请重新登录')
+ router.push('/')
+ }
+ return Promise.reject(new Error('认证失败:Token已过期'))
+ }
+
+ // 检查302重定向
+ if (response.status === 302) {
+ console.error('收到302重定向,可能是认证失败:', response.config.url)
+
+ const loginUrls = ['/auth/login', '/auth/login/email', '/auth/register', '/verification/', '/public/']
+ const isLoginRequest = loginUrls.some(url => response.config.url.includes(url))
+
+ if (!isLoginRequest) {
+ localStorage.removeItem('token')
+ localStorage.removeItem('user')
+ ElMessage.warning('登录已过期,请重新登录')
+ router.push('/')
}
return Promise.reject(new Error('认证失败:302重定向'))
}
@@ -119,9 +133,9 @@ api.interceptors.response.use(
if (!isLoginRequest) {
localStorage.removeItem('token')
localStorage.removeItem('user')
- if (router.currentRoute.value.path !== '/login') {
- ElMessage.error('认证失败,请重新登录')
- router.push('/login')
+ if (router.currentRoute.value.path !== '/' && router.currentRoute.value.path !== '/login') {
+ ElMessage.warning('登录已过期,请重新登录')
+ router.push('/')
}
}
return Promise.reject(error)
@@ -138,9 +152,10 @@ api.interceptors.response.use(
// 302也可能是认证失败导致的
localStorage.removeItem('token')
localStorage.removeItem('user')
- if (router.currentRoute.value.path !== '/login') {
- ElMessage.error('认证失败,请重新登录')
- router.push('/login')
+ // 跳转到欢迎页
+ if (router.currentRoute.value.path !== '/' && router.currentRoute.value.path !== '/login') {
+ ElMessage.warning('登录已过期,请重新登录')
+ router.push('/')
}
}
break
diff --git a/demo/frontend/src/api/textToVideo.js b/demo/frontend/src/api/textToVideo.js
index e60dc64..d0cdad8 100644
--- a/demo/frontend/src/api/textToVideo.js
+++ b/demo/frontend/src/api/textToVideo.js
@@ -81,6 +81,18 @@ export const textToVideoApi = {
})
},
+ /**
+ * 重试失败的任务
+ * 复用原task_id,重新提交至外部API
+ * @param {string} taskId - 任务ID
+ * @returns {Promise} API响应
+ */
+ retryTask(taskId) {
+ return request({
+ url: `/text-to-video/tasks/${taskId}/retry`,
+ method: 'POST'
+ })
+ },
/**
* 轮询任务状态
diff --git a/demo/frontend/src/locales/en.js b/demo/frontend/src/locales/en.js
index 47e05e0..d06980a 100644
--- a/demo/frontend/src/locales/en.js
+++ b/demo/frontend/src/locales/en.js
@@ -230,6 +230,8 @@ export default {
userAvatar: 'User Avatar',
firstFrame: 'First Frame',
promptPlaceholder: 'Describe the content you want to generate with the image',
+ tipWarning: '⚠️ Do not upload images of real people or anime IP',
+ tip1: '🎬 Supports camera movements, character actions, scene transitions, etc.',
optimizing: 'Optimizing...',
optimizePrompt: 'Optimize',
hdMode: 'HD Mode',
@@ -324,8 +326,10 @@ export default {
uploadSuccess: 'Successfully uploaded {count} images',
imageRemoved: 'Image removed',
promptPlaceholder: 'Example: a coffee advertisement\n\nTip: Simple description is enough, AI will automatically optimize it into professional storyboard\nSupports Chinese or English input, the system will automatically translate and optimize it into professional storyboard description',
- tip1: '💡 AI will automatically generate professional storyboards based on your description',
- tip2: '🎬 Supports various scene compositions and camera types',
+ tipWarning: '⚠️ Do not upload images of real people or anime IP',
+ tip1: '🎬 Supports camera movements, character actions, scene transitions, etc.',
+ imageCost: 'Storyboard generation costs 30 points',
+ videoCost: 'Video generation costs 30 points',
videoPromptLabel: 'Video Description',
videoPromptPlaceholder: 'Describe actions and camera movements in the video, e.g.: camera slowly zooms in, person turns around and smiles',
videoTip1: '💡 Describe actions and camera movements for more vivid video generation',
@@ -480,14 +484,14 @@ export default {
free: 'Free',
standard: 'Standard',
professional: 'Professional',
- perMonth: '/month',
+ perMonth: '/year',
subscribe: 'Subscribe Now',
renew: 'Renew',
upgrade: 'Upgrade',
features: 'Features',
unlimited: 'Unlimited',
limited: 'Limited',
- pointsPerMonth: 'Points/Month',
+ pointsPerMonth: 'Points/Year',
videoQuality: 'Video Quality',
support: 'Support',
priorityQueue: 'Priority Queue',
@@ -506,8 +510,8 @@ export default {
currentPackage: 'Current Plan',
firstPurchaseDiscount: 'First Purchase Discount up to 15% off',
bestValue: 'Best Value',
- standardPoints: '200 points per month',
- premiumPoints: '1000 points per month',
+ standardPoints: '6000 points/year',
+ premiumPoints: '12000 points/year',
freeNewUserBonus: 'New users get 50 points free on first login',
fastGeneration: 'Fast Generation',
superFastGeneration: 'Super Fast Generation',
diff --git a/demo/frontend/src/locales/zh.js b/demo/frontend/src/locales/zh.js
index 7097283..b87206e 100644
--- a/demo/frontend/src/locales/zh.js
+++ b/demo/frontend/src/locales/zh.js
@@ -232,6 +232,8 @@ export default {
userAvatar: '用户头像',
firstFrame: '首帧',
promptPlaceholder: '结合图片,描述想要生成的内容',
+ tipWarning: '⚠️ 图片不能上传真人、涉及动漫IP等类型的图片',
+ tip1: '🎬 支持描述镜头推拉、人物动作、场景变化等',
optimizing: '优化中...',
optimizePrompt: '一键优化',
hdMode: '高清模式',
@@ -327,11 +329,13 @@ export default {
uploadSuccess: '成功上传 {count} 张图片',
imageRemoved: '已删除图片',
promptPlaceholder: '例如:一个咖啡的广告\n\n提示:简单描述即可,AI会自动优化成专业的分镜图\n支持中文或英文输入,系统会自动翻译并优化为专业的分镜图描述',
- tip1: '💡 AI会根据您的描述自动生成专业分镜图',
- tip2: '🎬 支持多种画面构图和镜头类型描述',
+ tipWarning: '⚠️ 图片不能上传真人、涉及动漫IP等类型的图片',
+ tip1: '🎬 支持描述镜头推拉、人物动作、场景变化等',
+ imageCost: '生成分镜图消耗30积分',
+ videoCost: '生成视频消耗30积分',
videoPromptLabel: '视频描述',
videoPromptPlaceholder: '描述视频中的动作、镜头运动等,例如:镜头缓慢推进,人物转身微笑',
- videoTip1: '💡 描述画面中的动作和镜头运动,AI将生成更生动的视频',
+ videoTip1: '⚠️ 图片不能上传真人、涉及动漫IP等类型的图片',
videoTip2: '🎬 支持描述镜头推拉、人物动作、场景变化等',
storyboardReadyHint: '分镜图已准备就绪,输入视频描述后点击生成视频',
optimizing: '优化中...',
@@ -494,14 +498,14 @@ export default {
free: '免费版',
standard: '标准版',
professional: '专业版',
- perMonth: '/月',
+ perMonth: '/年',
subscribe: '立即订阅',
renew: '续费',
upgrade: '升级',
features: '功能特性',
unlimited: '无限',
limited: '有限',
- pointsPerMonth: '积分/月',
+ pointsPerMonth: '积分/年',
videoQuality: '视频质量',
support: '客服支持',
priorityQueue: '优先队列',
@@ -520,8 +524,8 @@ export default {
currentPackage: '当前套餐',
firstPurchaseDiscount: '首购低至8.5折',
bestValue: '超值之选',
- standardPoints: '每月200积分',
- premiumPoints: '每月1000积分',
+ standardPoints: '6000积分/年',
+ premiumPoints: '12000积分/年',
freeNewUserBonus: '新用户首次登陆免费获得50积分',
fastGeneration: '快速通道生成',
superFastGeneration: '极速通道生成',
@@ -657,6 +661,7 @@ export default {
cancelled: '已取消',
refunded: '已退款',
unpaid: '未支付',
+ allPaymentMethods: '全部支付方式',
alipay: '支付宝',
wechat: '微信支付',
paypal: 'PayPal',
@@ -780,6 +785,12 @@ export default {
promptOptimizationModelTip: '输入用于优化提示词的模型名称,如 gpt-4o、gemini-pro 等',
storyboardSystemPrompt: '分镜图系统引导词',
storyboardSystemPromptTip: '此引导词会自动添加到用户提示词前面,用于统一分镜图生成风格',
- storyboardSystemPromptPlaceholder: '例如:高质量电影级画面,专业摄影,电影色调...'
+ storyboardSystemPromptPlaceholder: '例如:高质量电影级画面,专业摄影,电影色调...',
+ membershipUpdateSuccess: '会员等级配置更新成功',
+ membershipUpdateFailed: '会员等级配置更新失败',
+ loadMembershipFailed: '加载会员配置失败',
+ usingDefaultConfig: '使用默认配置',
+ enterValidNumber: '请输入有效的数字',
+ unknown: '未知错误'
}
}
diff --git a/demo/frontend/src/router/index.js b/demo/frontend/src/router/index.js
index 5348998..fa16758 100644
--- a/demo/frontend/src/router/index.js
+++ b/demo/frontend/src/router/index.js
@@ -190,6 +190,12 @@ const routes = [
component: () => import('@/views/ApiManagement.vue'),
meta: { title: 'API管理', requiresAuth: true, requiresAdmin: true }
},
+ {
+ path: '/admin/error-statistics',
+ name: 'ErrorStatistics',
+ component: () => import('@/views/ErrorStatistics.vue'),
+ meta: { title: '错误统计', requiresAuth: true, requiresAdmin: true }
+ },
{
path: '/hello',
name: 'HelloWorld',
@@ -246,6 +252,13 @@ router.beforeEach(async (to, from, next) => {
try {
const userStore = useUserStore()
+ // 检查localStorage中的token是否被清除(例如JWT过期后被request.js清除)
+ // 如果token被清除但store中仍有用户信息,则同步清除store
+ const storedToken = localStorage.getItem('token')
+ if (!storedToken && userStore.isAuthenticated) {
+ userStore.clearUserData()
+ }
+
// 优化:只在首次访问时初始化用户状态
if (!userStore.initialized) {
await userStore.init()
diff --git a/demo/frontend/src/stores/user.js b/demo/frontend/src/stores/user.js
index 3214257..bfe2cd5 100644
--- a/demo/frontend/src/stores/user.js
+++ b/demo/frontend/src/stores/user.js
@@ -128,19 +128,18 @@ export const useUserStore = defineStore('user', () => {
return
}
- // 从localStorage恢复用户状态
+ // 从 localStorage 恢复用户状态
const savedToken = localStorage.getItem('token')
const savedUser = localStorage.getItem('user')
+ console.log('Store init - savedToken:', savedToken ? savedToken.substring(0, 30) + '...' : 'null')
+
if (savedToken && savedUser) {
try {
token.value = savedToken
user.value = JSON.parse(savedUser)
- // 只在开发环境输出详细日志
- if (process.env.NODE_ENV === 'development') {
- console.log('恢复用户状态:', user.value?.username, '角色:', user.value?.role)
- }
+ console.log('恢复用户状态:', user.value?.username)
// 刷新用户信息(确保角色等信息是最新的)
await fetchCurrentUser()
@@ -153,6 +152,11 @@ export const useUserStore = defineStore('user', () => {
initialized.value = true
}
+
+ // 重置初始化状态(登录成功后调用)
+ const resetInitialized = () => {
+ initialized.value = false
+ }
return {
// 状态
@@ -172,6 +176,7 @@ export const useUserStore = defineStore('user', () => {
fetchCurrentUser,
clearUserData,
init,
- initialized
+ initialized,
+ resetInitialized
}
})
diff --git a/demo/frontend/src/views/AdminDashboard.vue b/demo/frontend/src/views/AdminDashboard.vue
index c4006d9..72e2bcc 100644
--- a/demo/frontend/src/views/AdminDashboard.vue
+++ b/demo/frontend/src/views/AdminDashboard.vue
@@ -27,6 +27,10 @@
{{ t('video.imageToVideo.loginRequired') }}
@@ -237,7 +244,7 @@