dev #1
|
|
@ -0,0 +1,8 @@
|
|||
# 使用 pnpm
|
||||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
||||
auto-install-peers=true
|
||||
|
||||
# 国内镜像加速(可选,如果需要的话)
|
||||
# registry=https://registry.npmmirror.com
|
||||
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# 依赖
|
||||
node_modules
|
||||
.pnpm-store
|
||||
pnpm-lock.yaml
|
||||
|
||||
# 构建产物
|
||||
.next
|
||||
out
|
||||
dist
|
||||
build
|
||||
|
||||
# 配置文件
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# 日志
|
||||
*.log
|
||||
|
||||
# 环境变量
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# 文档和报告
|
||||
docs/copy-audit.xlsx
|
||||
docs/i18n-scan-report.xlsx
|
||||
scripts/translates.xlsx
|
||||
scripts/translation-conflicts.xlsx
|
||||
|
||||
# 字体文件
|
||||
*.ttf
|
||||
*.woff
|
||||
*.woff2
|
||||
|
||||
# 公共静态资源
|
||||
public/mockServiceWorker.js
|
||||
public/font/
|
||||
|
||||
# 其他
|
||||
.vscode
|
||||
.idea
|
||||
*.min.js
|
||||
*.min.css
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf",
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -7,6 +7,8 @@
|
|||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"i18n:scan": "i18next-scanner",
|
||||
"i18n:scan-custom": "tsx scripts/i18n-scan.ts",
|
||||
"i18n:convert": "node scripts/convert-to-i18n.js"
|
||||
|
|
@ -79,9 +81,12 @@
|
|||
"acorn-typescript": "^1.4.13",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.0.3",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"globby": "^15.0.0",
|
||||
"i18next-scanner": "^4.6.0",
|
||||
"msw": "^2.10.4",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"tailwindcss": "^4",
|
||||
"ts-morph": "^27.0.2",
|
||||
"ts-node": "^10.9.2",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -20,7 +20,7 @@ const ChatMessageList = () => {
|
|||
const { nim } = useNimChat();
|
||||
const { getHistoryMsgActive } = useContext(NimMsgContext);
|
||||
const { aiId } = useChatConfig();
|
||||
|
||||
|
||||
// 用于引用ChatMessageUserHeader的ref
|
||||
const userHeaderRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
|
@ -35,21 +35,15 @@ const ChatMessageList = () => {
|
|||
} = useMessageState();
|
||||
|
||||
// 使用加载状态Hook
|
||||
const {
|
||||
loadingState,
|
||||
initializeLoading,
|
||||
fetchNextPage,
|
||||
completeInitialLoad,
|
||||
} = useMessageLoading({
|
||||
selectedConversationId,
|
||||
getHistoryMsgActive,
|
||||
});
|
||||
const { loadingState, initializeLoading, fetchNextPage, completeInitialLoad } = useMessageLoading(
|
||||
{
|
||||
selectedConversationId,
|
||||
getHistoryMsgActive,
|
||||
}
|
||||
);
|
||||
|
||||
// 使用滚动Hook
|
||||
const {
|
||||
containerRef,
|
||||
handleInitialLoadComplete,
|
||||
} = useMessageScrolling({
|
||||
const { containerRef, handleInitialLoadComplete } = useMessageScrolling({
|
||||
messages,
|
||||
selectedConversationId,
|
||||
isInitialLoad: loadingState.isInitialLoad,
|
||||
|
|
@ -85,33 +79,41 @@ const ChatMessageList = () => {
|
|||
// 首次加载完成后的处理
|
||||
useEffect(() => {
|
||||
if (loadingState.isInitialLoad && (messages.length > 0 || loadingState.apiCallCount > 0)) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('📨 检测到首批消息加载完成,准备隐藏骨架屏', {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("📨 检测到首批消息加载完成,准备隐藏骨架屏", {
|
||||
messagesLength: messages.length,
|
||||
apiCallCount: loadingState.apiCallCount
|
||||
apiCallCount: loadingState.apiCallCount,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 延迟处理,确保平滑过渡
|
||||
setTimeout(() => {
|
||||
completeInitialLoad(messages.length);
|
||||
handleInitialLoadComplete();
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('🦴 隐藏骨架屏,显示真实消息');
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("🦴 隐藏骨架屏,显示真实消息");
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
}, [messages.length, loadingState.isInitialLoad, loadingState.apiCallCount, completeInitialLoad, handleInitialLoadComplete]);
|
||||
}, [
|
||||
messages.length,
|
||||
loadingState.isInitialLoad,
|
||||
loadingState.apiCallCount,
|
||||
completeInitialLoad,
|
||||
handleInitialLoadComplete,
|
||||
]);
|
||||
|
||||
console.log('messages', messages, selectedConversationId, nim?.V2NIMConversationIdUtil.parseConversationTargetId(selectedConversationId || ''));
|
||||
console.log(
|
||||
"messages",
|
||||
messages,
|
||||
selectedConversationId,
|
||||
nim?.V2NIMConversationIdUtil.parseConversationTargetId(selectedConversationId || "")
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="flex-1 min-h-0 overflow-y-auto pt-12 pb-20"
|
||||
>
|
||||
<div className="max-w-[752px] mx-auto">
|
||||
<div ref={containerRef} className="min-h-0 flex-1 overflow-y-auto pt-12 pb-20">
|
||||
<div className="mx-auto max-w-[752px]">
|
||||
{/* 用户信息头部始终显示在顶部 */}
|
||||
<div ref={userHeaderRef}>
|
||||
<ChatMessageUserHeader />
|
||||
|
|
@ -122,7 +124,7 @@ const ChatMessageList = () => {
|
|||
<div
|
||||
ref={loadMoreRef}
|
||||
className="h-1 w-full opacity-0"
|
||||
style={{ backgroundColor: 'red' }} // 临时调试样式,方便查看位置
|
||||
style={{ backgroundColor: "red" }} // 临时调试样式,方便查看位置
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
@ -132,52 +134,47 @@ const ChatMessageList = () => {
|
|||
<ChatMessageSkeleton />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* CrushLevelAction - 使用visibility和opacity控制显示/隐藏,避免图片重复加载 */}
|
||||
<div
|
||||
style={{
|
||||
visibility: showCrushLevelAction ? 'visible' : 'hidden',
|
||||
<div
|
||||
style={{
|
||||
visibility: showCrushLevelAction ? "visible" : "hidden",
|
||||
opacity: showCrushLevelAction ? 1 : 0,
|
||||
pointerEvents: showCrushLevelAction ? 'auto' : 'none',
|
||||
transition: 'opacity 0.2s ease-in-out'
|
||||
pointerEvents: showCrushLevelAction ? "auto" : "none",
|
||||
transition: "opacity 0.2s ease-in-out",
|
||||
}}
|
||||
>
|
||||
<CrushLevelAction />
|
||||
</div>
|
||||
|
||||
|
||||
{/* 骨架屏 - 仅在首次访问且无数据且尚未完成加载时显示 */}
|
||||
{loadingState.showSkeleton && messages.length === 0 && loadingState.isInitialLoad && !loadingState.hasLoadedOnce && (
|
||||
<ChatMessageSkeleton />
|
||||
)}
|
||||
|
||||
{loadingState.showSkeleton &&
|
||||
messages.length === 0 &&
|
||||
loadingState.isInitialLoad &&
|
||||
!loadingState.hasLoadedOnce && <ChatMessageSkeleton />}
|
||||
|
||||
{/* 消息列表 */}
|
||||
{!loadingState.showSkeleton && (
|
||||
<div className="space-y-4 mt-8">
|
||||
<div className="mt-8 space-y-4">
|
||||
{/* 持久化开场白 - 始终显示在消息列表顶部 */}
|
||||
<ChatPrologueMessage />
|
||||
|
||||
|
||||
{/* 普通消息 */}
|
||||
{normalMessages.map((message) => {
|
||||
// 判断是否是用户发送的消息(根据发送者ID)
|
||||
const isUser = !message.senderId.includes(aiId.toString());
|
||||
|
||||
|
||||
return (
|
||||
<div key={message.messageClientId || message.messageServerId}>
|
||||
<ChatMessageItems
|
||||
isUser={isUser}
|
||||
message={message}
|
||||
/>
|
||||
<ChatMessageItems isUser={isUser} message={message} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
|
||||
{/* Loading消息,始终显示在最后 */}
|
||||
{loadingMessages.map((message) => (
|
||||
<div key={message.messageClientId}>
|
||||
<ChatMessageItems
|
||||
isUser={false}
|
||||
message={message}
|
||||
/>
|
||||
<ChatMessageItems isUser={false} message={message} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -185,6 +182,6 @@ const ChatMessageList = () => {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChatMessageList;
|
||||
};
|
||||
|
||||
export default ChatMessageList;
|
||||
|
|
|
|||
Loading…
Reference in New Issue