返回 AiToEarn
AiSocialPageContent.tsx
根目录 / project / aitoearn-web / src / app / [lng] / ai-social / AiSocialPageContent.tsx
1 /**
2 * AI 社媒内容组件 - AI social media Page Content
3 * 客户端组件,包含所有交互逻辑
4 */
5 'use client'
6
7 import type { IHomeChatRef } from './components/HomeChat'
8 import { ArrowUp, Upload } from 'lucide-react'
9 import { useParams, useRouter, useSearchParams } from 'next/navigation'
10 import { useCallback, useEffect, useRef, useState } from 'react'
11 import { Button } from '@/components/ui/button'
12 import { useAgentStore } from '@/store/agent'
13 import { cn } from '@/utils/className'
14 import { toast } from '@/utils/ui/toast'
15 import { useTransClient } from '../../i18n/client'
16 import AgentFeatures from './components/AgentFeatures'
17 import EcosystemDiagram from './components/EcosystemDiagram'
18 import { HomeChat } from './components/HomeChat'
19 import PromptGallery from './components/PromptGallery'
20 import TaskPreview from './components/TaskPreview'
21
22 export function AiSocialPageContent() {
23 const { t } = useTransClient('home')
24
25 // Store 方法
26 const { setDebugFiles } = useAgentStore()
27
28 // 拖拽上传状态
29 const [isDragging, setIsDragging] = useState(false)
30 const dragCounterRef = useRef(0)
31 const homeChatRef = useRef<IHomeChatRef>(null)
32
33 // 拖拽事件处理
34 const handleDragEnter = useCallback((e: React.DragEvent) => {
35 e.preventDefault()
36 e.stopPropagation()
37 dragCounterRef.current++
38 if (e.dataTransfer.types.includes('Files')) {
39 setIsDragging(true)
40 }
41 }, [])
42
43 const handleDragLeave = useCallback((e: React.DragEvent) => {
44 e.preventDefault()
45 e.stopPropagation()
46 dragCounterRef.current--
47 if (dragCounterRef.current === 0) {
48 setIsDragging(false)
49 }
50 }, [])
51
52 const handleDragOver = useCallback((e: React.DragEvent) => {
53 e.preventDefault()
54 e.stopPropagation()
55 }, [])
56
57 const handleDrop = useCallback((e: React.DragEvent) => {
58 e.preventDefault()
59 e.stopPropagation()
60 setIsDragging(false)
61 dragCounterRef.current = 0
62
63 const files = e.dataTransfer.files
64 if (files.length > 0) {
65 homeChatRef.current?.handleFileDrop(files)
66 }
67 }, [])
68
69 /**
70 * 回到顶部按钮组件
71 * @param position 按钮位置,'left' 或 'right'
72 */
73 function BackToTop({ position = 'left' }: { position?: 'left' | 'right' }) {
74 const [isVisible, setIsVisible] = useState(false)
75
76 // 监听滚动显示/隐藏按钮
77 useEffect(() => {
78 const handleScroll = () => {
79 // 滚动超过 400px 显示按钮
80 setIsVisible(window.scrollY > 400)
81 }
82
83 window.addEventListener('scroll', handleScroll, { passive: true })
84 return () => window.removeEventListener('scroll', handleScroll)
85 }, [])
86
87 // 点击回到顶部
88 const scrollToTop = useCallback(() => {
89 window.scrollTo({ top: 0, behavior: 'smooth' })
90 }, [])
91
92 return (
93 <Button
94 size="icon"
95 onClick={scrollToTop}
96 className={cn(
97 'fixed bottom-8 z-50 w-12 h-12 rounded-full',
98 'shadow-lg transition-all duration-300 transform',
99 position === 'left' ? 'left-8' : 'right-8',
100 isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4 pointer-events-none',
101 )}
102 aria-label="Back to top"
103 >
104 <ArrowUp className="w-5 h-5" />
105 </Button>
106 )
107 }
108
109 const [appliedPrompt, setAppliedPrompt] = useState<string>('')
110 const [appliedMaterials, setAppliedMaterials] = useState<string[]>([])
111
112 // 处理提示词应用
113 const handleApplyPrompt = useCallback(
114 (data: { prompt: string, materials?: string[], mode: 'edit' | 'generate' }) => {
115 setAppliedPrompt(data.prompt)
116 setAppliedMaterials(data.materials || [])
117 // 滚动到顶部
118 window.scrollTo({ top: 0, behavior: 'smooth' })
119 toast.success('Prompt applied!')
120 },
121 [],
122 )
123
124 // 从 URL query 读取 agentExternalPrompt 和 agentTaskId(由任务页通过 query 传参)
125 const searchParams = useSearchParams()
126 const router = useRouter()
127 const [agentTaskId, setAgentTaskId] = useState<string>('')
128 const params = useParams()
129
130 // 解析 debug URL 参数,设置 debug 模式
131 useEffect(() => {
132 try {
133 const debugParam = searchParams.get('debug')
134 if (debugParam) {
135 // 解析 debug=[file1.txt,file2.txt] 或 debug=file1.txt,file2.txt 格式
136 const cleanedParam = debugParam.replace(/^\[|\]$/g, '')
137 const files = cleanedParam
138 .split(',')
139 .map(f => f.trim())
140 .filter(Boolean)
141
142 if (files.length > 0) {
143 setDebugFiles(files)
144
145 // 清理 URL 上的 debug 参数
146 const url = new URL(window.location.href)
147 url.searchParams.delete('debug')
148 router.replace(url.pathname + url.search)
149 }
150 }
151 }
152 catch (e) {
153 console.warn('[AiSocialPageContent] Failed to parse debug param:', e)
154 }
155 }, [searchParams, router, setDebugFiles])
156
157 useEffect(() => {
158 try {
159 const prompt = searchParams.get('agentExternalPrompt')
160 const id = searchParams.get('agentTaskId')
161 if (prompt) {
162 setAppliedPrompt(prompt)
163 }
164 if (id) {
165 setAgentTaskId(id)
166 }
167 // 清理 URL 上的 query,避免重复
168 if (prompt || id) {
169 router.replace(`/${params.lng}/ai-social`)
170 }
171 }
172 catch (e) {
173 // ignore
174 }
175 }, [searchParams, router, params.lng])
176
177 // 清除外部提示词
178 const handleClearExternalPrompt = useCallback(() => {
179 setAppliedPrompt('')
180 setAppliedMaterials([])
181 }, [])
182
183 return (
184 <div
185 className="bg-background"
186 onDragEnter={handleDragEnter}
187 onDragLeave={handleDragLeave}
188 onDragOver={handleDragOver}
189 onDrop={handleDrop}
190 >
191 {/* 拖拽遮罩层 */}
192 {isDragging && (
193 <div className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm flex items-center justify-center pointer-events-none">
194 <div className="flex flex-col items-center gap-4 p-8 rounded-2xl border-2 border-dashed border-primary bg-card">
195 <Upload className="w-12 h-12 text-primary" />
196 <p className="text-lg font-medium text-foreground">{t('dropToUpload')}</p>
197 </div>
198 </div>
199 )}
200
201 {/* 首屏 Chat 区域 */}
202 <section className="min-h-[60vh] flex items-center justify-center px-4 pt-16 pb-8 md:pt-24 md:pb-12">
203 <HomeChat
204 ref={homeChatRef}
205 externalPrompt={appliedPrompt}
206 externalMaterials={appliedMaterials}
207 onClearExternalPrompt={handleClearExternalPrompt}
208 agentTaskId={agentTaskId}
209 />
210 </section>
211
212 {/* 任务预览区域 - 无数据时自动隐藏 */}
213 <TaskPreview limit={4} className="px-4 py-8" />
214
215 {/* /!* 提示词画廊区域 *!/ */}
216 <PromptGallery onApplyPrompt={handleApplyPrompt} />
217
218 {/* AI Agent 功能亮点(独立展示) */}
219 <AgentFeatures />
220
221 {/* 一图解读 AiToEarn 生态 */}
222 <EcosystemDiagram />
223
224 {/* 回到顶部按钮 - 右侧 */}
225 <BackToTop position="right" />
226 </div>
227 )
228 }
229
229 lines Plain Text