diff --git a/src/app/share/[code]/page.tsx b/src/app/share/[code]/page.tsx new file mode 100644 index 0000000..2734b57 --- /dev/null +++ b/src/app/share/[code]/page.tsx @@ -0,0 +1,394 @@ +'use client'; + +import { useState, useEffect, use, useCallback, useRef } from 'react'; +import Link from 'next/link'; +import { + Loader2, + Eye, + MessageSquare, + Clock, + Sparkles, + AlertCircle, + Copy, + Check, + ExternalLink, +} from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { MarkdownRenderer } from '@/components/markdown/MarkdownRenderer'; +import { ShareNavigator } from '@/components/features/ShareNavigator'; + +interface PageProps { + params: Promise<{ code: string }>; +} + +interface ShareData { + share: { + title: string; + description?: string; + viewCount: number; + createdAt: string; + }; + conversation: { + id: string; + title: string; + model: string; + messageCount: number; + totalTokens: number; + createdAt: string; + }; + messages: Array<{ + id: string; + role: string; + content: string; + thinkingContent?: string | null; + toolCalls?: unknown[] | null; + images?: string[] | null; + searchImages?: Array<{ + title: string; + imageUrl: string; + width: number; + height: number; + }> | null; + uploadedImages?: string[] | null; + usedTools?: string[] | null; + inputTokens?: number; + outputTokens?: number; + createdAt: string; + }>; +} + +// 工具名称中文映射 +const TOOL_NAMES: Record = { + web_search: '网络搜索', + web_fetch: '网页读取', + mita_search: '秘塔搜索', + mita_reader: '秘塔阅读', + code_execution: '代码执行', + youdao_translate: '有道翻译', + image_search: '图片搜索', + video_search: '视频搜索', +}; + +// 格式化日期 +function formatDate(dateString: string): string { + const date = new Date(dateString); + return date.toLocaleDateString('zh-CN', { + year: 'numeric', + month: 'long', + day: 'numeric', + }); +} + +export default function SharePage({ params }: PageProps) { + const { code } = use(params); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [copied, setCopied] = useState(false); + + useEffect(() => { + const fetchShare = async () => { + try { + const response = await fetch(`/api/share/${code}`); + if (!response.ok) { + const errData = await response.json(); + throw new Error(errData.error || '加载失败'); + } + const shareData = await response.json(); + setData(shareData); + } catch (err) { + setError(err instanceof Error ? err.message : '加载失败'); + } finally { + setLoading(false); + } + }; + fetchShare(); + }, [code]); + + // 页面加载后处理 URL hash 定位 + useEffect(() => { + if (!data || loading) return; + + const hash = window.location.hash; + if (hash) { + // 支持 #msg-{id} 或 #msg-{index} 格式 + const targetId = hash.slice(1); // 移除 # + const element = document.getElementById(targetId); + if (element) { + // 延迟滚动以确保页面渲染完成 + setTimeout(() => { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }, 100); + } + } + }, [data, loading]); + + const handleCopyLink = async () => { + try { + await navigator.clipboard.writeText(window.location.href); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error('Copy error:', err); + } + }; + + // 加载中 + if (loading) { + return ( +
+
+ + 加载中... +
+
+ ); + } + + // 错误 + if (error) { + return ( +
+
+ +

{error}

+

此分享链接可能已失效或不存在

+ + + 开始使用 LionCode + +
+
+ ); + } + + if (!data) return null; + + return ( +
+ {/* 顶部导航 */} +
+
+ + LionCode + +
+ + + + 开始使用 + +
+
+
+ + {/* 分享信息头部 */} +
+
+

{data.share.title}

+ {data.share.description && ( +

{data.share.description}

+ )} +
+ + + {formatDate(data.share.createdAt)} + + + + {data.share.viewCount} 次查看 + + + + {data.conversation.messageCount} 条消息 + + + {data.conversation.model} + +
+
+
+ + {/* 消息列表 */} +
+
+ {data.messages.map((message, idx) => { + // 计算用户消息索引(用于目录导航) + const userMsgIndex = message.role === 'user' + ? data.messages.slice(0, idx + 1).filter(m => m.role === 'user').length + : null; + + return ( +
+ {message.role === 'user' ? ( + // 用户消息 +
+
+ {/* 用户上传的图片 */} + {message.uploadedImages && message.uploadedImages.length > 0 && ( +
+ {message.uploadedImages.map((img, idx) => ( + {`上传图片 + ))} +
+ )} +
+ {message.content} +
+
+
+ U +
+
+ ) : ( + // AI 消息 +
+ {/* 消息头部 */} +
+
+ +
+ LionCode AI + {message.usedTools && message.usedTools.length > 0 && ( +
+ {message.usedTools.map((tool, idx) => ( + + {TOOL_NAMES[tool] || tool} + + ))} +
+ )} +
+ + {/* 思考内容 */} + {message.thinkingContent && ( +
+ + 💭 思考过程 + +
+ {message.thinkingContent} +
+
+ )} + + {/* 消息内容 */} +
+ +
+ + {/* 代码执行图片 */} + {message.images && message.images.length > 0 && ( +
+
📊 代码执行结果
+
+ {message.images.map((img, idx) => ( + {`图表 + ))} +
+
+ )} + + {/* 搜索图片 */} + {message.searchImages && message.searchImages.length > 0 && ( +
+
🖼️ 搜索结果图片
+
+ {message.searchImages.slice(0, 8).map((img, idx) => ( + + {img.title + + ))} +
+
+ )} + + {/* Token 统计 */} + {(message.inputTokens || message.outputTokens) && ( +
+ Token: 输入 {message.inputTokens?.toLocaleString() || 0} / 输出 {message.outputTokens?.toLocaleString() || 0} +
+ )} +
+ )} +
+ ); + })} +
+
+ + {/* 分享导航组件 */} + + + {/* 底部 */} + +
+ ); +}