diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
index d6745d0..c1d772a 100644
--- a/src/app/admin/page.tsx
+++ b/src/app/admin/page.tsx
@@ -10327,6 +10327,7 @@ const AIConfigComponent = ({
const [enableHomepageEntry, setEnableHomepageEntry] = useState(true);
const [enableVideoCardEntry, setEnableVideoCardEntry] = useState(true);
const [enablePlayPageEntry, setEnablePlayPageEntry] = useState(true);
+ const [enableAIComments, setEnableAIComments] = useState(false);
// 权限控制
const [allowRegularUsers, setAllowRegularUsers] = useState(true);
@@ -10357,6 +10358,7 @@ const AIConfigComponent = ({
setEnableHomepageEntry(config.AIConfig.EnableHomepageEntry !== false);
setEnableVideoCardEntry(config.AIConfig.EnableVideoCardEntry !== false);
setEnablePlayPageEntry(config.AIConfig.EnablePlayPageEntry !== false);
+ setEnableAIComments(config.AIConfig.EnableAIComments || false);
setAllowRegularUsers(config.AIConfig.AllowRegularUsers !== false);
setTemperature(config.AIConfig.Temperature ?? 0.7);
setMaxTokens(config.AIConfig.MaxTokens ?? 1000);
@@ -10390,6 +10392,7 @@ const AIConfigComponent = ({
EnableHomepageEntry: enableHomepageEntry,
EnableVideoCardEntry: enableVideoCardEntry,
EnablePlayPageEntry: enablePlayPageEntry,
+ EnableAIComments: enableAIComments,
AllowRegularUsers: allowRegularUsers,
Temperature: temperature,
MaxTokens: maxTokens,
@@ -10659,6 +10662,7 @@ const AIConfigComponent = ({
{ key: 'homepage', label: '首页入口', desc: '在首页显示AI问片入口', state: enableHomepageEntry, setState: setEnableHomepageEntry },
{ key: 'videocard', label: '视频卡片入口', desc: '在视频卡片菜单中显示AI问片选项', state: enableVideoCardEntry, setState: setEnableVideoCardEntry },
{ key: 'playpage', label: '播放页入口', desc: '在视频播放页显示AI问片功能', state: enablePlayPageEntry, setState: setEnablePlayPageEntry },
+ { key: 'aicomments', label: 'AI评论功能', desc: '在播放页生成AI评论(独立于豆瓣评论)', state: enableAIComments, setState: setEnableAIComments },
].map((item) => (
diff --git a/src/app/api/admin/ai/route.ts b/src/app/api/admin/ai/route.ts
index 07e7425..7858cc4 100644
--- a/src/app/api/admin/ai/route.ts
+++ b/src/app/api/admin/ai/route.ts
@@ -57,6 +57,7 @@ export async function POST(request: NextRequest) {
EnableHomepageEntry,
EnableVideoCardEntry,
EnablePlayPageEntry,
+ EnableAIComments,
AllowRegularUsers,
Temperature,
MaxTokens,
@@ -93,6 +94,7 @@ export async function POST(request: NextRequest) {
EnableHomepageEntry: boolean;
EnableVideoCardEntry: boolean;
EnablePlayPageEntry: boolean;
+ EnableAIComments: boolean;
AllowRegularUsers: boolean;
Temperature?: number;
MaxTokens?: number;
@@ -132,6 +134,7 @@ export async function POST(request: NextRequest) {
typeof EnableHomepageEntry !== 'boolean' ||
typeof EnableVideoCardEntry !== 'boolean' ||
typeof EnablePlayPageEntry !== 'boolean' ||
+ typeof EnableAIComments !== 'boolean' ||
typeof AllowRegularUsers !== 'boolean' ||
(Temperature !== undefined && typeof Temperature !== 'number') ||
(MaxTokens !== undefined && typeof MaxTokens !== 'number') ||
@@ -181,6 +184,7 @@ export async function POST(request: NextRequest) {
EnableHomepageEntry,
EnableVideoCardEntry,
EnablePlayPageEntry,
+ EnableAIComments,
AllowRegularUsers,
Temperature,
MaxTokens,
diff --git a/src/app/api/ai-comments/route.ts b/src/app/api/ai-comments/route.ts
new file mode 100644
index 0000000..3e03452
--- /dev/null
+++ b/src/app/api/ai-comments/route.ts
@@ -0,0 +1,113 @@
+import { NextRequest, NextResponse } from 'next/server';
+
+import { generateAIComments, AIComment } from '@/lib/ai-comment-generator';
+import { getConfig } from '@/lib/config';
+
+export const runtime = 'nodejs';
+
+interface AICommentsResponse {
+ comments: AIComment[];
+ total: number;
+ movieName: string;
+ isAiGenerated: true;
+ generatedAt: string;
+}
+
+export async function GET(request: NextRequest) {
+ try {
+ const searchParams = request.nextUrl.searchParams;
+ const movieName = searchParams.get('name');
+ const movieInfo = searchParams.get('info');
+ const count = parseInt(searchParams.get('count') || '10');
+
+ // 参数验证
+ if (!movieName) {
+ return NextResponse.json(
+ { error: '缺少影片名称参数' },
+ { status: 400 }
+ );
+ }
+
+ if (count < 1 || count > 50) {
+ return NextResponse.json(
+ { error: '评论数量必须在1-50之间' },
+ { status: 400 }
+ );
+ }
+
+ // 读取AI配置
+ const config = await getConfig();
+ const aiConfig = config.AIConfig;
+
+ // 检查AI功能是否启用
+ if (!aiConfig?.Enabled) {
+ return NextResponse.json(
+ { error: 'AI功能未启用' },
+ { status: 403 }
+ );
+ }
+
+ // 检查AI评论功能是否启用
+ if (!aiConfig?.EnableAIComments) {
+ return NextResponse.json(
+ { error: 'AI评论功能未启用' },
+ { status: 403 }
+ );
+ }
+
+ // 检查必要的配置
+ if (!aiConfig.CustomApiKey || !aiConfig.CustomBaseURL || !aiConfig.CustomModel) {
+ return NextResponse.json(
+ { error: 'AI配置不完整,请在管理面板配置' },
+ { status: 500 }
+ );
+ }
+
+ // 生成AI评论
+ const comments = await generateAIComments({
+ movieName,
+ movieInfo: movieInfo || undefined,
+ count,
+ aiConfig: {
+ CustomApiKey: aiConfig.CustomApiKey,
+ CustomBaseURL: aiConfig.CustomBaseURL,
+ CustomModel: aiConfig.CustomModel,
+ Temperature: aiConfig.Temperature,
+ MaxTokens: aiConfig.MaxTokens,
+ EnableWebSearch: aiConfig.EnableWebSearch,
+ WebSearchProvider: aiConfig.WebSearchProvider,
+ TavilyApiKey: aiConfig.TavilyApiKey,
+ SerperApiKey: aiConfig.SerperApiKey,
+ SerpApiKey: aiConfig.SerpApiKey,
+ },
+ });
+
+ // 返回结果
+ const response: AICommentsResponse = {
+ comments,
+ total: comments.length,
+ movieName,
+ isAiGenerated: true,
+ generatedAt: new Date().toISOString(),
+ };
+
+ return NextResponse.json(response, {
+ headers: {
+ 'Cache-Control': 'public, max-age=3600, s-maxage=3600', // 缓存1小时
+ },
+ });
+ } catch (error) {
+ console.error('AI评论生成失败:', error);
+
+ // 返回友好的错误信息
+ const errorMessage = error instanceof Error ? error.message : 'AI评论生成失败';
+
+ return NextResponse.json(
+ {
+ error: errorMessage,
+ details: process.env.NODE_ENV === 'development' ? String(error) : undefined
+ },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 517cf73..e0eea95 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -84,6 +84,7 @@ export default async function RootLayout({
let aiEnableHomepageEntry = false;
let aiEnableVideoCardEntry = false;
let aiEnablePlayPageEntry = false;
+ let aiEnableComments = false;
let aiDefaultMessageNoVideo = '';
let aiDefaultMessageWithVideo = '';
let enableMovieRequest = true;
@@ -133,6 +134,7 @@ export default async function RootLayout({
aiEnableHomepageEntry = config.AIConfig?.EnableHomepageEntry || false;
aiEnableVideoCardEntry = config.AIConfig?.EnableVideoCardEntry || false;
aiEnablePlayPageEntry = config.AIConfig?.EnablePlayPageEntry || false;
+ aiEnableComments = config.AIConfig?.EnableAIComments || false;
aiDefaultMessageNoVideo = config.AIConfig?.DefaultMessageNoVideo || '';
aiDefaultMessageWithVideo = config.AIConfig?.DefaultMessageWithVideo || '';
// 求片功能配置
@@ -198,6 +200,9 @@ export default async function RootLayout({
AI_ENABLE_HOMEPAGE_ENTRY: aiEnableHomepageEntry,
AI_ENABLE_VIDEOCARD_ENTRY: aiEnableVideoCardEntry,
AI_ENABLE_PLAYPAGE_ENTRY: aiEnablePlayPageEntry,
+ AIConfig: {
+ EnableAIComments: aiEnableComments,
+ },
AI_DEFAULT_MESSAGE_NO_VIDEO: aiDefaultMessageNoVideo,
AI_DEFAULT_MESSAGE_WITH_VIDEO: aiDefaultMessageWithVideo,
ENABLE_MOVIE_REQUEST: enableMovieRequest,
diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx
index 64a7313..68c2df7 100644
--- a/src/app/play/page.tsx
+++ b/src/app/play/page.tsx
@@ -50,9 +50,11 @@ import { getTMDBImageUrl } from '@/lib/tmdb.search';
import { DanmakuFilterConfig, EpisodeFilterConfig, SearchResult } from '@/lib/types';
import { base58Decode, getVideoResolutionFromM3u8, processImageUrl } from '@/lib/utils';
import { useEnableComments } from '@/hooks/useEnableComments';
+import { useEnableAIComments } from '@/hooks/useEnableAIComments';
import { usePlaySync } from '@/hooks/usePlaySync';
import AIChatPanel from '@/components/AIChatPanel';
+import AIComments from '@/components/AIComments';
import CorrectDialog from '@/components/CorrectDialog';
import DanmakuFilterSettings from '@/components/DanmakuFilterSettings';
import DetailPanel from '@/components/DetailPanel';
@@ -87,6 +89,7 @@ function PlayPageClient() {
const router = useRouter();
const searchParams = useSearchParams();
const enableComments = useEnableComments();
+ const enableAIComments = useEnableAIComments();
const { addDownloadTask } = useDownload();
const { siteName } = useSite();
@@ -8719,6 +8722,28 @@ function PlayPageClient() {
)}
+
+ {/* AI评论区域 */}
+ {videoTitle && enableAIComments && (
+
+
+ {/* 标题 */}
+
+
+ {/* 评论内容 */}
+
+
+
+ )}
>
)}
diff --git a/src/components/AIComments.tsx b/src/components/AIComments.tsx
new file mode 100644
index 0000000..2e4935e
--- /dev/null
+++ b/src/components/AIComments.tsx
@@ -0,0 +1,275 @@
+'use client';
+
+import { useCallback, useEffect, useState } from 'react';
+
+interface AIComment {
+ id: string;
+ userName: string;
+ userAvatar: string;
+ rating: number | null;
+ content: string;
+ time: string;
+ votes: number;
+ isAiGenerated: true;
+}
+
+interface AICommentsProps {
+ movieName: string;
+ movieInfo?: string;
+}
+
+export default function AIComments({ movieName, movieInfo }: AICommentsProps) {
+ const [comments, setComments] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [hasStartedLoading, setHasStartedLoading] = useState(false);
+
+ const fetchComments = useCallback(async () => {
+ try {
+ console.log('正在生成AI评论...');
+ setLoading(true);
+ setError(null);
+
+ const params = new URLSearchParams({
+ name: movieName,
+ count: '10',
+ });
+
+ if (movieInfo) {
+ params.append('info', movieInfo);
+ }
+
+ const response = await fetch(`/api/ai-comments?${params.toString()}`);
+
+ if (!response.ok) {
+ const data = await response.json();
+ throw new Error(data.error || '生成AI评论失败');
+ }
+
+ const data = await response.json();
+ console.log('AI评论生成成功:', data.comments.length);
+
+ setComments(data.comments);
+ } catch (err) {
+ console.error('生成AI评论失败:', err);
+ setError(err instanceof Error ? err.message : '生成AI评论失败');
+ } finally {
+ setLoading(false);
+ }
+ }, [movieName, movieInfo]);
+
+ useEffect(() => {
+ // 重置状态当 movieName 变化时
+ setHasStartedLoading(false);
+ setComments([]);
+ setLoading(false);
+ setError(null);
+ }, [movieName]);
+
+ const startLoading = () => {
+ console.log('开始生成AI评论');
+ setHasStartedLoading(true);
+ fetchComments();
+ };
+
+ const regenerate = () => {
+ console.log('重新生成AI评论');
+ fetchComments();
+ };
+
+ // 星级渲染
+ const renderStars = (rating: number | null) => {
+ if (rating === null) return null;
+
+ return (
+
+ {[1, 2, 3, 4, 5].map((star) => (
+
+ ))}
+
+ );
+ };
+
+ // 初始状态:显示生成按钮
+ if (!hasStartedLoading) {
+ return (
+
+
+
+
点击生成AI评论
+
+ 基于影片信息和网络资料生成
+
+
+
+
+ );
+ }
+
+ if (loading && comments.length === 0) {
+ return (
+
+
+
AI正在生成评论...
+
+ 这可能需要几秒钟
+
+
+ );
+ }
+
+ if (error && comments.length === 0) {
+ return (
+
+
❌
+
{error}
+
+ 请检查管理面板的AI配置是否正确
+
+
+
+ );
+ }
+
+ return (
+
+ {/* 头部统计和操作 */}
+
+
+ 已生成 {comments.length} 条AI评论
+
+
+
+
+ {/* 评论列表 */}
+
+ {comments.map((comment) => (
+
+ {/* 用户信息 */}
+
+ {/* 头像 */}
+
+

+
+
+ {/* 用户名和评分 */}
+
+
+
+ {comment.userName}
+
+ {renderStars(comment.rating)}
+ {/* AI标识 */}
+
+
+ AI生成
+
+
+
+ {/* 时间 */}
+
+ {comment.time}
+
+
+
+ {/* 有用数 */}
+ {comment.votes > 0 && (
+
+
+
{comment.votes}
+
+ )}
+
+
+ {/* 评论内容 */}
+
+ {comment.content}
+
+
+ ))}
+
+
+ {/* 提示信息 */}
+
+ 以上评论由AI基于影片信息和网络资料生成,仅供参考
+
+
+ );
+}
diff --git a/src/hooks/useEnableAIComments.ts b/src/hooks/useEnableAIComments.ts
new file mode 100644
index 0000000..dd4ab0c
--- /dev/null
+++ b/src/hooks/useEnableAIComments.ts
@@ -0,0 +1,21 @@
+import { useEffect, useState } from 'react';
+
+interface RuntimeConfig {
+ AIConfig?: {
+ EnableAIComments?: boolean;
+ };
+}
+
+export function useEnableAIComments(): boolean {
+ const [enableAIComments, setEnableAIComments] = useState(false);
+
+ useEffect(() => {
+ // 在客户端获取运行时配置
+ if (typeof window !== 'undefined') {
+ const runtimeConfig = (window as any).RUNTIME_CONFIG as RuntimeConfig;
+ setEnableAIComments(runtimeConfig?.AIConfig?.EnableAIComments ?? false);
+ }
+ }, []);
+
+ return enableAIComments;
+}
diff --git a/src/lib/ai-comment-generator.ts b/src/lib/ai-comment-generator.ts
new file mode 100644
index 0000000..2f3a3d4
--- /dev/null
+++ b/src/lib/ai-comment-generator.ts
@@ -0,0 +1,284 @@
+// AI评论生成核心逻辑
+
+export interface AIComment {
+ id: string;
+ userName: string;
+ userAvatar: string;
+ rating: number | null;
+ content: string;
+ time: string;
+ votes: number;
+ isAiGenerated: true;
+}
+
+interface GenerateCommentsParams {
+ movieName: string;
+ movieInfo?: string;
+ count?: number;
+ aiConfig: {
+ CustomApiKey: string;
+ CustomBaseURL: string;
+ CustomModel: string;
+ Temperature?: number;
+ MaxTokens?: number;
+ EnableWebSearch?: boolean;
+ WebSearchProvider?: 'tavily' | 'serper' | 'serpapi';
+ TavilyApiKey?: string;
+ SerperApiKey?: string;
+ SerpApiKey?: string;
+ };
+}
+
+interface CommentData {
+ content: string;
+ rating: number | null;
+ sentiment: 'positive' | 'neutral' | 'negative';
+}
+
+// 生成评论的Prompt
+function buildCommentPrompt(
+ movieName: string,
+ movieInfo?: string,
+ searchResults?: string,
+ count: number = 10
+): string {
+ return `你是一个影评生成助手。请生成真实自然的观众评论。
+
+影片:${movieName}
+${movieInfo ? `简介:${movieInfo}` : ''}
+${searchResults ? `\n网络评价参考:\n${searchResults}` : ''}
+
+任务要求:
+1. 生成${count}条观众评论
+2. 每条评论50-200字,口语化、自然
+3. 观点多样化:有好评、中评、差评,比例大约6:3:1
+4. 可以包含:
+ - 个人观影感受和情感共鸣
+ - 对演员演技的评价
+ - 对剧情、节奏、画面的看法
+ - 与其他作品的对比
+ - 推荐或不推荐的理由
+5. 避免:
+ - 过于专业的影评术语
+ - 千篇一律的表达
+ - 明显的AI痕迹
+ - 重复的内容
+
+请直接输出JSON数组格式,不要有其他文字:
+[
+ {
+ "content": "评论内容",
+ "rating": 4,
+ "sentiment": "positive"
+ }
+]
+
+注意:rating为1-5的整数或null(表示未评分),sentiment为positive/neutral/negative之一。`;
+}
+
+// 联网搜索影片资料
+async function searchMovieInfo(
+ movieName: string,
+ aiConfig: GenerateCommentsParams['aiConfig']
+): Promise {
+ if (!aiConfig.EnableWebSearch) {
+ return '';
+ }
+
+ try {
+ const provider = aiConfig.WebSearchProvider || 'tavily';
+ let searchResults = '';
+
+ if (provider === 'tavily' && aiConfig.TavilyApiKey) {
+ const response = await fetch('https://api.tavily.com/search', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ api_key: aiConfig.TavilyApiKey,
+ query: `${movieName} 影评 评价`,
+ max_results: 5,
+ }),
+ });
+
+ if (response.ok) {
+ const data = await response.json();
+ searchResults = data.results
+ ?.map((r: any) => r.content)
+ .join('\n')
+ .slice(0, 1000);
+ }
+ } else if (provider === 'serper' && aiConfig.SerperApiKey) {
+ const response = await fetch('https://google.serper.dev/search', {
+ method: 'POST',
+ headers: {
+ 'X-API-KEY': aiConfig.SerperApiKey,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ q: `${movieName} 影评 评价`,
+ num: 5,
+ }),
+ });
+
+ if (response.ok) {
+ const data = await response.json();
+ searchResults = data.organic
+ ?.map((r: any) => r.snippet)
+ .join('\n')
+ .slice(0, 1000);
+ }
+ } else if (provider === 'serpapi' && aiConfig.SerpApiKey) {
+ const response = await fetch(
+ `https://serpapi.com/search?q=${encodeURIComponent(movieName + ' 影评 评价')}&api_key=${aiConfig.SerpApiKey}&num=5`
+ );
+
+ if (response.ok) {
+ const data = await response.json();
+ searchResults = data.organic_results
+ ?.map((r: any) => r.snippet)
+ .join('\n')
+ .slice(0, 1000);
+ }
+ }
+
+ return searchResults;
+ } catch (error) {
+ console.error('搜索影片资料失败:', error);
+ return '';
+ }
+}
+
+// 调用AI生成评论
+export async function generateAIComments(
+ params: GenerateCommentsParams
+): Promise {
+ const { movieName, movieInfo, count = 10, aiConfig } = params;
+
+ try {
+ // 1. 联网搜索影片资料(如果启用)
+ const searchResults = await searchMovieInfo(movieName, aiConfig);
+
+ // 2. 构建Prompt
+ const prompt = buildCommentPrompt(movieName, movieInfo, searchResults, count);
+
+ // 3. 调用AI API
+ const response = await fetch(`${aiConfig.CustomBaseURL}/chat/completions`, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${aiConfig.CustomApiKey}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ model: aiConfig.CustomModel,
+ messages: [
+ {
+ role: 'system',
+ content:
+ '你是一个专业的影评生成助手,擅长生成真实自然的观众评论。',
+ },
+ {
+ role: 'user',
+ content: prompt,
+ },
+ ],
+ temperature: aiConfig.Temperature ?? 0.8,
+ max_tokens: aiConfig.MaxTokens ?? 2000,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`AI API调用失败: ${response.status}`);
+ }
+
+ const data = await response.json();
+ const content = data.choices?.[0]?.message?.content;
+
+ if (!content) {
+ throw new Error('AI返回内容为空');
+ }
+
+ // 4. 解析AI返回的JSON
+ let commentsData: CommentData[];
+ try {
+ // 尝试提取JSON(可能被markdown代码块包裹)
+ const jsonMatch = content.match(/\[[\s\S]*\]/);
+ if (jsonMatch) {
+ commentsData = JSON.parse(jsonMatch[0]);
+ } else {
+ commentsData = JSON.parse(content);
+ }
+ } catch (parseError) {
+ console.error('解析AI返回的JSON失败:', content);
+ throw new Error('AI返回格式错误');
+ }
+
+ // 5. 转换为AIComment格式
+ const aiComments: AIComment[] = commentsData.map((comment, index) => {
+ const timestamp = Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000; // 随机过去30天内
+ const date = new Date(timestamp);
+
+ return {
+ id: `ai-${Date.now()}-${index}`,
+ userName: generateUserName(index),
+ userAvatar: generateAvatar(index),
+ rating: comment.rating,
+ content: comment.content,
+ time: formatTime(date),
+ votes: generateVotes(comment.sentiment),
+ isAiGenerated: true,
+ };
+ });
+
+ return aiComments;
+ } catch (error) {
+ console.error('AI评论生成失败:', error);
+ throw error;
+ }
+}
+
+// 生成虚拟用户名
+function generateUserName(index: number): string {
+ const prefixes = [
+ '影迷',
+ '观众',
+ '电影爱好者',
+ '剧迷',
+ '路人',
+ '网友',
+ '看客',
+ ];
+ const prefix = prefixes[index % prefixes.length];
+ return `${prefix}${Math.floor(Math.random() * 9000) + 1000}`;
+}
+
+// 生成头像URL(使用DiceBear API)
+function generateAvatar(seed: number): string {
+ const styles = ['avataaars', 'bottts', 'personas', 'micah'];
+ const style = styles[seed % styles.length];
+ return `https://api.dicebear.com/7.x/${style}/svg?seed=${seed}`;
+}
+
+// 格式化时间
+function formatTime(date: Date): string {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ const hours = String(date.getHours()).padStart(2, '0');
+ const minutes = String(date.getMinutes()).padStart(2, '0');
+ const seconds = String(date.getSeconds()).padStart(2, '0');
+
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+}
+
+// 根据情感生成点赞数
+function generateVotes(sentiment: string): number {
+ if (sentiment === 'positive') {
+ return Math.floor(Math.random() * 100) + 20; // 20-120
+ } else if (sentiment === 'neutral') {
+ return Math.floor(Math.random() * 50) + 5; // 5-55
+ } else {
+ return Math.floor(Math.random() * 30); // 0-30
+ }
+}