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",
"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",

8692
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

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