dev #1

Merged
liuyonghe merged 4 commits from dev into main 2025-11-24 03:51:02 +00:00
7 changed files with 8810 additions and 12817 deletions
Showing only changes of commit cdf2db9798 - Show all commits

8
.npmrc Normal file
View File

@ -0,0 +1,8 @@
# 使用 pnpm
shamefully-hoist=true
strict-peer-dependencies=false
auto-install-peers=true
# 国内镜像加速(可选,如果需要的话)
# registry=https://registry.npmmirror.com

43
.prettierignore Normal file
View File

@ -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

10
.prettierrc Normal file
View File

@ -0,0 +1,10 @@
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "always",
"endOfLine": "lf",
"plugins": ["prettier-plugin-tailwindcss"]
}

12762
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,8 @@
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "eslint .", "lint": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check .",
"i18n:scan": "i18next-scanner", "i18n:scan": "i18next-scanner",
"i18n:scan-custom": "tsx scripts/i18n-scan.ts", "i18n:scan-custom": "tsx scripts/i18n-scan.ts",
"i18n:convert": "node scripts/convert-to-i18n.js" "i18n:convert": "node scripts/convert-to-i18n.js"
@ -79,9 +81,12 @@
"acorn-typescript": "^1.4.13", "acorn-typescript": "^1.4.13",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "16.0.3", "eslint-config-next": "16.0.3",
"eslint-config-prettier": "^9.1.0",
"globby": "^15.0.0", "globby": "^15.0.0",
"i18next-scanner": "^4.6.0", "i18next-scanner": "^4.6.0",
"msw": "^2.10.4", "msw": "^2.10.4",
"prettier": "^3.4.2",
"prettier-plugin-tailwindcss": "^0.6.11",
"tailwindcss": "^4", "tailwindcss": "^4",
"ts-morph": "^27.0.2", "ts-morph": "^27.0.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",

8692
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -35,21 +35,15 @@ const ChatMessageList = () => {
} = useMessageState(); } = useMessageState();
// 使用加载状态Hook // 使用加载状态Hook
const { const { loadingState, initializeLoading, fetchNextPage, completeInitialLoad } = useMessageLoading(
loadingState, {
initializeLoading,
fetchNextPage,
completeInitialLoad,
} = useMessageLoading({
selectedConversationId, selectedConversationId,
getHistoryMsgActive, getHistoryMsgActive,
}); }
);
// 使用滚动Hook // 使用滚动Hook
const { const { containerRef, handleInitialLoadComplete } = useMessageScrolling({
containerRef,
handleInitialLoadComplete,
} = useMessageScrolling({
messages, messages,
selectedConversationId, selectedConversationId,
isInitialLoad: loadingState.isInitialLoad, isInitialLoad: loadingState.isInitialLoad,
@ -85,10 +79,10 @@ const ChatMessageList = () => {
// 首次加载完成后的处理 // 首次加载完成后的处理
useEffect(() => { useEffect(() => {
if (loadingState.isInitialLoad && (messages.length > 0 || loadingState.apiCallCount > 0)) { if (loadingState.isInitialLoad && (messages.length > 0 || loadingState.apiCallCount > 0)) {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === "development") {
console.log('📨 检测到首批消息加载完成,准备隐藏骨架屏', { console.log("📨 检测到首批消息加载完成,准备隐藏骨架屏", {
messagesLength: messages.length, messagesLength: messages.length,
apiCallCount: loadingState.apiCallCount apiCallCount: loadingState.apiCallCount,
}); });
} }
@ -97,21 +91,29 @@ const ChatMessageList = () => {
completeInitialLoad(messages.length); completeInitialLoad(messages.length);
handleInitialLoadComplete(); handleInitialLoadComplete();
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === "development") {
console.log('🦴 隐藏骨架屏,显示真实消息'); console.log("🦴 隐藏骨架屏,显示真实消息");
} }
}, 200); }, 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 ( return (
<div <div ref={containerRef} className="min-h-0 flex-1 overflow-y-auto pt-12 pb-20">
ref={containerRef} <div className="mx-auto max-w-[752px]">
className="flex-1 min-h-0 overflow-y-auto pt-12 pb-20"
>
<div className="max-w-[752px] mx-auto">
{/* 用户信息头部始终显示在顶部 */} {/* 用户信息头部始终显示在顶部 */}
<div ref={userHeaderRef}> <div ref={userHeaderRef}>
<ChatMessageUserHeader /> <ChatMessageUserHeader />
@ -122,7 +124,7 @@ const ChatMessageList = () => {
<div <div
ref={loadMoreRef} ref={loadMoreRef}
className="h-1 w-full opacity-0" className="h-1 w-full opacity-0"
style={{ backgroundColor: 'red' }} // 临时调试样式,方便查看位置 style={{ backgroundColor: "red" }} // 临时调试样式,方便查看位置
/> />
)} )}
@ -136,23 +138,24 @@ const ChatMessageList = () => {
{/* CrushLevelAction - 使用visibility和opacity控制显示/隐藏,避免图片重复加载 */} {/* CrushLevelAction - 使用visibility和opacity控制显示/隐藏,避免图片重复加载 */}
<div <div
style={{ style={{
visibility: showCrushLevelAction ? 'visible' : 'hidden', visibility: showCrushLevelAction ? "visible" : "hidden",
opacity: showCrushLevelAction ? 1 : 0, opacity: showCrushLevelAction ? 1 : 0,
pointerEvents: showCrushLevelAction ? 'auto' : 'none', pointerEvents: showCrushLevelAction ? "auto" : "none",
transition: 'opacity 0.2s ease-in-out' transition: "opacity 0.2s ease-in-out",
}} }}
> >
<CrushLevelAction /> <CrushLevelAction />
</div> </div>
{/* 骨架屏 - 仅在首次访问且无数据且尚未完成加载时显示 */} {/* 骨架屏 - 仅在首次访问且无数据且尚未完成加载时显示 */}
{loadingState.showSkeleton && messages.length === 0 && loadingState.isInitialLoad && !loadingState.hasLoadedOnce && ( {loadingState.showSkeleton &&
<ChatMessageSkeleton /> messages.length === 0 &&
)} loadingState.isInitialLoad &&
!loadingState.hasLoadedOnce && <ChatMessageSkeleton />}
{/* 消息列表 */} {/* 消息列表 */}
{!loadingState.showSkeleton && ( {!loadingState.showSkeleton && (
<div className="space-y-4 mt-8"> <div className="mt-8 space-y-4">
{/* 持久化开场白 - 始终显示在消息列表顶部 */} {/* 持久化开场白 - 始终显示在消息列表顶部 */}
<ChatPrologueMessage /> <ChatPrologueMessage />
@ -163,10 +166,7 @@ const ChatMessageList = () => {
return ( return (
<div key={message.messageClientId || message.messageServerId}> <div key={message.messageClientId || message.messageServerId}>
<ChatMessageItems <ChatMessageItems isUser={isUser} message={message} />
isUser={isUser}
message={message}
/>
</div> </div>
); );
})} })}
@ -174,10 +174,7 @@ const ChatMessageList = () => {
{/* Loading消息始终显示在最后 */} {/* Loading消息始终显示在最后 */}
{loadingMessages.map((message) => ( {loadingMessages.map((message) => (
<div key={message.messageClientId}> <div key={message.messageClientId}>
<ChatMessageItems <ChatMessageItems isUser={false} message={message} />
isUser={false}
message={message}
/>
</div> </div>
))} ))}
</div> </div>
@ -185,6 +182,6 @@ const ChatMessageList = () => {
</div> </div>
</div> </div>
); );
} };
export default ChatMessageList; export default ChatMessageList;