Gemini3太牛逼了!五句话5分钟做了个微信!使用gemini cli生成,内含提示词和代码。

pZk6wHx.png
pZk6BE6.png
pZk6DUK.png
pZk6r4O.png
pZk6yCD.png
pZk668e.png
pZk6gvd.png
pZk6cgH.png

以下是给Ai的提示词

给我写一个类似微信的聊天网站,要求:功能超级完整、界面美观、动画流畅,可以真实操作

功能不是很完整,我要尽可能完整,表情、红包、语音/视频聊天、复制撤回引用…、置顶、免打扰、个人信息、通讯录、朋友圈、设置…最好加上更加智能的回复(可以类似早期AI,添加几百个预设词)!这些功能全部安排上!!!再尽可能添加其他我没有提到的功能,并且尽量少BUG功能不是很完整,我要尽可能完整,表情、红包、语音/视频聊天、复制撤回引用…、置顶、免打扰、个人信息、通讯录、朋友圈、设置…最好加上更加智能的回复(可以类似早期AI,添加几百个预设词)!这些功能全部安排上!!!再尽可能添加其他我没有提到的功能,并且尽量少BUG

以下是生成的代码

<!DOCTYPE html>

<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>微信 Web Pro</title>
<script src="https://cdn.tailwindcss.com"></script> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<style>
/ Global & Utils /
:root {
—sidebar-bg: #2e2e2e;
—list-bg: #e6e6e6;
—list-hover: #d9d9d9;
—list-active: #c6c6c6;
—main-bg: #f5f5f5;
—sent-bg: #95ec69;
—received-bg: #ffffff;
—brand-green: #07c160;
}

body {
font-family: -apple-system, BlinkMacSystemFont, “Segoe UI”, Roboto, “Helvetica Neue”, Arial, sans-serif;
background-image: url(‘https://images.unsplash.com/photo-1519681393784-d120267933ba?q=80&w=2070&auto=format&fit=crop‘);
background-size: cover;
background-position: center;
backdrop-filter: blur(20px);
height: 100vh;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
user-select: none; / Prevent selection for app-like feel /
}

/ Scrollbar /
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-thumb { background: #bfbfbf; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #a0a0a0; }
::-webkit-scrollbar-track { background: transparent; }

/ Utilities /
.no-drag { -webkit-user-drag: none; }
.app-shadow { box-shadow: 0 0 20px rgba(0,0,0,0.2); }

/ Animations /
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes popIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
@keyframes slideInRight { from { transform: translateX(100%); } to { transform: translateX(0); } }

.msg-anim { animation: fadeIn 0.2s ease-out forwards; }
.modal-anim { animation: popIn 0.2s ease-out forwards; }
.page-anim { animation: fadeIn 0.3s ease-out; }

/ Custom Components /
.context-menu {
position: fixed;
background: white;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.15);
z-index: 1000;
min-width: 120px;
overflow: hidden;
display: none;
}
.context-menu-item {
padding: 8px 12px;
font-size: 13px;
cursor: pointer;
color: #333;
}
.context-menu-item:hover { background-color: #f0f0f0; }

.red-packet {
background: #fa9d3b;
border-radius: 4px;
padding: 10px;
display: flex;
align-items: center;
width: 200px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.red-packet:hover { opacity: 0.95; }
.red-packet::before {
content: ‘’;
position: absolute;
left: -10px; bottom: -10px;
width: 40px; height: 40px;
background: #fcf5da;
border-radius: 50%;
opacity: 0.2;
}

/ Toggle Switch /
.toggle-checkbox:checked {
right: 0;
border-color: #07c160;
}
.toggle-checkbox:checked + .toggle-label {
background-color: #07c160;
}

/ App Container /
#app {
width: 1000px;
height: 80vh;
min-height: 650px;
background-color: var(—main-bg);
border-radius: 6px;
display: flex;
overflow: hidden;
position: relative;
}

@media (max-width: 1024px) {
#app { width: 100%; height: 100%; border-radius: 0; }
body { padding: 0; }
}
</style>
</head>
<body>

<!-- Context Menu -->
<div id="context-menu" class="context-menu">
<div class="context-menu-item" data-action="copy">复制</div>
<div class="context-menu-item" data-action="recall">撤回</div>
<div class="context-menu-item" data-action="quote">引用</div>
<div class="context-menu-item" data-action="delete">删除</div>
</div>

<!-- App Structure -->
<div id="app" class="app-shadow">

<!-- 1. Sidebar Navigation -->
<div class="w-[60px] bg-[#2e2e2e] flex flex-col items-center py-4 justify-between shrink-0 z-20">
<div class="flex flex-col items-center gap-6">
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=Me" class="w-9 h-9 rounded bg-white cursor-pointer hover:opacity-80 transition" onclick="showProfile('me')">

<!-- Nav Icons -->
<div class="nav-item text-[#07c160] cursor-pointer text-xl relative group" data-tab="chat">
<i class="fas fa-comment"></i>
<div id="badge-chat" class="absolute -top-1 -right-1 w-2 h-2 bg-red-500 rounded-full hidden"></div>
</div>
<div class="nav-item text-gray-400 hover:text-white cursor-pointer text-xl transition" data-tab="contacts">
<i class="fas fa-address-book"></i>
</div>
<div class="nav-item text-gray-400 hover:text-white cursor-pointer text-xl transition" data-tab="moments">
<i class="fab fa-safari"></i> <!-- Using Safari icon as Compass replacement -->
<div id="badge-moments" class="absolute top-0 right-0 w-1.5 h-1.5 bg-red-500 rounded-full hidden"></div>
</div>
</div>
<div class="flex flex-col items-center gap-6">
<div class="nav-item text-gray-400 hover:text-white cursor-pointer text-xl transition" data-tab="settings">
<i class="fas fa-cog"></i>
</div>
</div>
</div>

<!-- 2. Secondary Sidebar (List View) -->
<div class="w-[250px] bg-[#e6e6e6] flex flex-col border-r border-gray-200 shrink-0 z-10">
<!-- Search -->
<div class="h-16 flex items-center px-3 bg-[#f7f7f7] gap-2 shrink-0">
<div class="flex-1 bg-[#e2e2e2] rounded flex items-center px-2 py-1 text-xs h-7">
<i class="fas fa-search text-gray-400 mr-2"></i>
<input type="text" placeholder="搜索" class="bg-transparent outline-none w-full text-gray-600">
</div>
<button onclick="createNewChat()" class="w-7 h-7 bg-[#e2e2e2] rounded flex items-center justify-center text-gray-500 text-xs hover:bg-[#d2d2d2] transition">
<i class="fas fa-plus"></i>
</button>
</div>

<!-- Dynamic List Content -->
<div id="sidebar-list" class="flex-1 overflow-y-auto">
<!-- Injected by JS -->
</div>
</div>

<!-- 3. Main Content Area -->
<div class="flex-1 flex flex-col bg-[#f5f5f5] relative overflow-hidden" id="main-area">
<!-- View: Chat -->
<div id="view-chat" class="flex flex-col h-full w-full absolute inset-0 bg-[#f5f5f5]">
<!-- Empty State -->
<div id="empty-state" class="hidden absolute inset-0 flex flex-col items-center justify-center text-gray-400">
<i class="fab fa-weixin text-6xl mb-4 opacity-20"></i>
</div>

<!-- Chat Header -->
<div id="chat-header" class="h-16 border-b border-gray-200 flex items-center justify-between px-6 shrink-0 bg-[#f5f5f5] z-10">
<div class="flex items-center gap-2">
<div class="font-medium text-lg truncate select-text" id="chat-title">用户名</div>
<i class="fas fa-volume-mute text-gray-400 text-xs hidden" id="chat-mute-icon"></i>
</div>
<div class="flex items-center gap-4 text-gray-500">
<i class="fas fa-phone hover:text-black cursor-pointer" onclick="startCall('voice')"></i>
<i class="fas fa-video hover:text-black cursor-pointer" onclick="startCall('video')"></i>
<i class="fas fa-ellipsis-h hover:text-black cursor-pointer" onclick="toggleChatDetails()"></i>
</div>
</div>

<!-- Message Area -->
<div id="message-container" class="flex-1 overflow-y-auto px-6 py-4 space-y-4">
<!-- Messages injected here -->
</div>

<!-- Input Area -->
<div id="chat-input-area" class="h-[180px] border-t border-gray-200 flex flex-col shrink-0 bg-white relative">
<!-- Quote Preview -->
<div id="quote-preview" class="hidden absolute -top-10 left-0 w-full h-10 bg-gray-100 border-t border-gray-200 flex items-center px-4 text-xs text-gray-500 justify-between z-0">
<span class="truncate max-w-[90%]">引用: <span id="quote-text">…</span></span>
<i class="fas fa-times cursor-pointer hover:text-gray-700" onclick="cancelQuote()"></i>
</div>

<!-- Toolbar -->
<div class="h-10 flex items-center px-4 gap-4 text-gray-500 z-10">
<i class="far fa-smile text-xl cursor-pointer hover:text-gray-700" id="emoji-btn"></i>
<i class="far fa-folder text-xl cursor-pointer hover:text-gray-700" onclick="document.getElementById('file-input').click()"></i>
<i class="fas fa-scissors text-xl cursor-pointer hover:text-gray-700"></i>
<i class="fas fa-history text-xl cursor-pointer hover:text-gray-700" title="聊天记录"></i>
<i class="fas fa-红包 text-xl cursor-pointer hover:text-red-500 text-red-400 font-bold" onclick="sendRedPacketMock()">🧧</i>
</div>
<input type="file" id="file-input" class="hidden">

<!-- Emoji Picker Popup -->
<div id="emoji-picker" class="hidden absolute bottom-[140px] left-2 w-[300px] h-[200px] bg-white shadow-lg border border-gray-200 rounded grid grid-cols-8 gap-1 p-2 overflow-y-auto z-50">
<!-- Populated by JS -->
</div>

<textarea id="message-input" class="flex-1 w-full px-4 py-1 outline-none resize-none bg-transparent text-sm text-gray-700 font-sans leading-relaxed" placeholder=""></textarea>

<div class="h-10 flex items-center justify-end px-4 pb-2">
<span class="text-xs text-gray-400 mr-4">Ctrl+Enter 发送</span>
<button id="send-btn" class="bg-[#e9e9e9] text-[#07c160] px-6 py-1 rounded text-sm font-medium transition pointer-events-none">发送</button>
</div>
</div>
</div>

<!-- View: Moments (朋友圈) -->
<div id="view-moments" class="hidden absolute inset-0 bg-white flex flex-col overflow-hidden">
<div class="h-16 border-b border-gray-200 flex items-center justify-between px-4 shrink-0 bg-[#f5f5f5]">
<span class="font-medium text-lg">朋友圈</span>
<i class="fas fa-camera text-xl cursor-pointer hover:text-gray-700"></i>
</div>
<div class="flex-1 overflow-y-auto bg-white relative" id="moments-container">
<!-- Cover -->
<div class="h-64 bg-cover bg-center relative mb-10" style="background-image: url('https://images.unsplash.com/photo-1477346611705-65d1883cee1e?auto=format&fit=crop&w=1000&q=80');">
<div class="absolute -bottom-8 right-4 flex items-end gap-3">
<span class="text-white font-bold text-lg drop-shadow-md mb-3">我</span>
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=Me" class="w-20 h-20 rounded border-2 border-white bg-white">
</div>
</div>
<!-- Feed will be injected here -->
</div>
</div>

<!-- View: Contacts -->
<div id="view-contacts" class="hidden absolute inset-0 bg-[#f5f5f5] flex flex-col">
<div class="h-16 border-b border-gray-200 flex items-center px-6 shrink-0 bg-[#f5f5f5]">
<span class="font-medium text-lg">通讯录</span>
</div>
<div class="flex-1 overflow-y-auto p-6">
<div class="bg-white rounded shadow-sm">
<div class="p-3 border-b border-gray-100 hover:bg-gray-50 cursor-pointer flex items-center gap-3">
<div class="w-9 h-9 bg-orange-400 rounded flex items-center justify-center text-white"><i class="fas fa-user-plus"></i></div>
<span>新的朋友</span>
</div>
<div class="p-3 border-b border-gray-100 hover:bg-gray-50 cursor-pointer flex items-center gap-3">
<div class="w-9 h-9 bg-green-500 rounded flex items-center justify-center text-white"><i class="fas fa-users"></i></div>
<span>群聊</span>
</div>
</div>
<div class="mt-4 text-xs text-gray-400 px-2 mb-1">A</div>
<div class="bg-white rounded shadow-sm mb-4" id="contacts-list-detail">
<!-- Filled by JS -->
</div>
</div>
</div>

<!-- View: Settings -->
<div id="view-settings" class="hidden absolute inset-0 bg-[#f5f5f5] flex flex-col items-center pt-10">
<div class="w-[400px] bg-white rounded shadow-sm p-6">
<h2 class="text-xl font-medium mb-6 border-b pb-2">设置</h2>
<div class="space-y-4">
<div class="flex justify-between items-center">
<span>账号与安全</span>
<i class="fas fa-chevron-right text-gray-300"></i>
</div>
<div class="flex justify-between items-center">
<span>新消息通知</span>
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
<input type="checkbox" name="toggle" id="toggle-notif" class="toggle-checkbox absolute block w-5 h-5 rounded-full bg-white border-4 appearance-none cursor-pointer checked:right-0 right-5 border-gray-300"/>
<label for="toggle-notif" class="toggle-label block overflow-hidden h-5 rounded-full bg-gray-300 cursor-pointer"></label>
</div>
</div>
<div class="flex justify-between items-center">
<span>通用</span>
<i class="fas fa-chevron-right text-gray-300"></i>
</div>
<button class="w-full py-2 bg-gray-100 hover:bg-gray-200 text-red-500 rounded mt-4" onclick="alert('已退出登录')">退出登录</button>
</div>
</div>
</div>
</div>
</div>

<!-- Modals -->

<!-- 1. Call Modal -->
<div id="call-modal" class="fixed inset-0 bg-black/80 z-50 hidden flex flex-col items-center justify-center text-white backdrop-blur-sm">
<img id="call-avatar" src="" class="w-24 h-24 rounded-full mb-4 border-2 border-white/20">
<h3 id="call-name" class="text-2xl font-light mb-2">User</h3>
<p id="call-status" class="text-gray-300 mb-12">正在等待对方接受邀请…</p>

<div class="flex gap-16">
<div class="flex flex-col items-center gap-2 cursor-pointer group" onclick="endCall()">
<div class="w-16 h-16 rounded-full bg-red-500 flex items-center justify-center text-2xl hover:bg-red-600 transition transform group-hover:scale-110">
<i class="fas fa-phone-slash"></i>
</div>
<span class="text-sm">挂断</span>
</div>
<div id="answer-btn" class="flex flex-col items-center gap-2 cursor-pointer group hidden" onclick="acceptCall()">
<div class="w-16 h-16 rounded-full bg-green-500 flex items-center justify-center text-2xl hover:bg-green-600 transition transform group-hover:scale-110">
<i class="fas fa-phone"></i>
</div>
<span class="text-sm">接听</span>
</div>
</div>
</div>

<!-- 2. Red Packet Modal -->
<div id="rp-modal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center backdrop-blur-sm">
<div class="w-[300px] h-[450px] bg-[#d95940] rounded-xl shadow-2xl relative overflow-hidden flex flex-col items-center modal-anim">
<div class="w-[500px] h-[500px] bg-[#d04e36] rounded-[50%] absolute -top-[350px] left-1/2 transform -translate-x-1/2"></div>

<div class="z-10 mt-12 flex flex-col items-center text-[#fceecb]">
<img id="rp-avatar" src="" class="w-12 h-12 rounded mb-2 border border-[#fceecb]">
<span id="rp-sender" class="font-medium">User的红包</span>
<span class="mt-4 text-2xl tracking-widest">恭喜发财,大吉大利</span>
</div>

<div class="z-10 absolute top-[180px] w-20 h-20 bg-[#ebcd99] rounded-full border-4 border-[#d95940] flex items-center justify-center text-3xl text-[#333] font-bold cursor-pointer shadow-lg hover:rotate-12 transition duration-300" onclick="openRedPacket()">
<span class="text-[#333]">開</span>
</div>

<div id="rp-result" class="hidden absolute inset-0 bg-white z-20 flex flex-col items-center pt-10">
<div class="text-sm text-gray-500 mb-2">红包详情</div>
<div class="text-4xl text-[#d95940] font-bold mb-1">88.88 <span class="text-sm">元</span></div>
<div class="text-blue-600 text-sm cursor-pointer hover:underline" onclick="closeRedPacket()">已存入零钱,关闭</div>
</div>

<i class="fas fa-times absolute top-2 right-2 text-white/50 cursor-pointer hover:text-white z-20" onclick="closeRedPacket()"></i>
</div>
</div>

<!-- 3. Toast -->
<div id="toast" class="fixed top-10 left-1/2 transform -translate-x-1/2 bg-black/70 text-white px-4 py-2 rounded text-sm opacity-0 transition pointer-events-none z-[100]">
提示信息
</div>

<script> // --- Data & State --- const state = { currentUser: { id: 'me', name: '我', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Me' }, activeTab: 'chat', // chat, contacts, moments, settings activeChatId: 1, chats: [], moments: [], contacts: [], // Flattened list for contact book replyTimer: null }; // Smart Bot Responses (Simulating "hundreds" of words conceptually) const botKeywords = { "你好": ["你好呀!", "嗨!", "Hello~", "在呢"], "在吗": ["在的,随时待命", "有事请讲", "怎么啦?"], "干嘛": ["发呆中...", "在写代码", "思考人生", "正如你所见"], "哈哈": ["哈哈哈", "笑什么呢", "嘿嘿", "😂"], "吃了吗": ["还没呢,你请客?", "刚吃完", "不饿"], "晚安": ["晚安,好梦", "早点睡", "🌙"], "牛": ["基操勿6", "还行吧", "过奖过奖"], "再见": ["拜拜", "下次聊", "👋"], "红包": ["谢谢老板!", "恭喜发财!", "多谢!"], "名字": ["我叫 WebBot", "你可以叫我小助手"], "几点": [() => 现在是 ${new Date().getHours()}:${new Date().getMinutes()}],
“天气”: [“我看不到外面,但应该是个好天气”, “适合写代码的天气”],
“default”: [“收到”, “嗯嗯”, “原来如此”, “有趣”, “😂”, “👍”, “🤔”, “好的”]
};

// Initial Data
function initData() {
state.chats = [
{
id: 1, type: ‘user’, name: “产品经理-老王”, avatar: “https://api.dicebear.com/7.x/avataaars/svg?seed=Wang“,
lastMessage: “这个需求明天上线。”, time: “14:23”, pinned: true, muted: false,
messages: [
{ id: 1, sender: ‘them’, content: “需求文档发你了”, type: ‘text’, time: “14:20” },
{ id: 2, sender: ‘me’, content: “收到,我看看”, type: ‘text’, time: “14:21” },
{ id: 3, sender: ‘them’, content: “这个需求明天上线。”, type: ‘text’, time: “14:23” }
]
},
{
id: 2, type: ‘group’, name: “相亲相爱一家人 (8)”, avatar: “https://api.dicebear.com/7.x/identicon/svg?seed=Family“,
lastMessage: “[红包] 恭喜发财”, time: “12:05”, pinned: false, muted: true,
messages: [
{ id: 1, sender: ‘them’, content: “周末回家吃饭吗?”, name: “妈妈”, type: ‘text’, time: “10:00” },
{ id: 2, sender: ‘me’, content: “回。”, type: ‘text’, time: “10:05” },
{ id: 3, sender: ‘them’, content: “恭喜发财”, type: ‘redpacket’, time: “12:05”, name: “二叔” }
]
},
{
id: 3, type: ‘user’, name: “文件传输助手”, avatar: “https://cdn-icons-png.flaticon.com/512/2965/2965306.png“,
lastMessage: “[图片]”, time: “昨天”, pinned: false, muted: false,
messages: [
{ id: 1, sender: ‘me’, content: “backup.zip”, type: ‘file’, time: “昨天” }
]
},
{
id: 4, type: ‘user’, name: “女神”, avatar: “https://api.dicebear.com/7.x/avataaars/svg?seed=Girl“,
lastMessage: “去洗澡了”, time: “星期一”, pinned: false, muted: false,
messages: [
{ id: 1, sender: ‘me’, content: “在干嘛”, type: ‘text’, time: “20:00” },
{ id: 2, sender: ‘them’, content: “去洗澡了”, type: ‘text’, time: “20:01” }
]
},
{
id: 5, type: ‘user’, name: “马斯克”, avatar: “https://api.dicebear.com/7.x/avataaars/svg?seed=Elon“,
lastMessage: “To Mars! 🚀”, time: “星期日”, pinned: false, muted: false,
messages: [ { id: 1, sender: ‘them’, content: “To Mars! 🚀”, type: ‘text’, time: “09:00” } ]
}
];

state.moments = [
{
id: 1, name: “产品经理-老王”, avatar: “https://api.dicebear.com/7.x/avataaars/svg?seed=Wang“,
content: “加班到现在的有没有?💪”, time: “10分钟前”,
images: [“https://images.unsplash.com/photo-1497215728101-856f4ea42174?w=300&h=300&fit=crop“],
likes: [“马斯克”, “小李”], comments: []
},
{
id: 2, name: “女神”, avatar: “https://api.dicebear.com/7.x/avataaars/svg?seed=Girl“,
content: “今天天气真好~ ☀️”, time: “1小时前”,
images: [“https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=300&h=300&fit=crop“, “https://images.unsplash.com/photo-1519046904884-53103b34b2af?w=300&h=300&fit=crop“],
likes: [“我”, “备胎1号”, “备胎2号”], comments: [{name: “我”, content: “真好看!”}]
}
];
}

// —- DOM Elements —-
const els = {
app: document.getElementById(‘app’),
sidebarList: document.getElementById(‘sidebar-list’),
msgContainer: document.getElementById(‘message-container’),
chatTitle: document.getElementById(‘chat-title’),
msgInput: document.getElementById(‘message-input’),
sendBtn: document.getElementById(‘send-btn’),
contextMenu: document.getElementById(‘context-menu’),
emojiPicker: document.getElementById(‘emoji-picker’),
quotePreview: document.getElementById(‘quote-preview’),
quoteText: document.getElementById(‘quote-text’),
navItems: document.querySelectorAll(‘.nav-item’),
views: {
chat: document.getElementById(‘view-chat’),
contacts: document.getElementById(‘view-contacts’),
moments: document.getElementById(‘view-moments’),
settings: document.getElementById(‘view-settings’)
},
rpModal: document.getElementById(‘rp-modal’),
callModal: document.getElementById(‘call-modal’)
};

let tempQuote = null;
let contextTargetMsgId = null;

// —- Initialization —-
function init() {
initData();
renderSidebar();
selectChat(1);
renderMoments();
renderContacts();
setupEvents();
generateEmojiPicker();
}

// —- Render Logic —-
function renderSidebar() {
els.sidebarList.innerHTML = ‘’;

// Sort: Pinned first, then by time (mock logic for now just uses array order but puts pinned top)
const sortedChats = […state.chats].sort((a, b) => (b.pinned - a.pinned));

sortedChats.forEach(chat => {
const div = document.createElement(‘div’);
const isActive = state.activeTab === ‘chat’ && state.activeChatId === chat.id;
div.className = flex items-center p-3 cursor-pointer transition relative ${isActive ? 'bg-[#c6c6c6]' : 'hover:bg-[#d9d9d9]'} ${chat.pinned ? 'bg-[#ededed]' : ''};
div.onclick = () => {
switchTab(‘chat’);
selectChat(chat.id);
};

div.innerHTML = <div class="relative shrink-0"> <img src="${chat.avatar}" class="w-10 h-10 rounded bg-gray-300 object-cover"> ${chat.id === 2 ? '<div class="absolute -top-1 -right-1 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-[#e6e6e6]"></div>' : ''} </div> <div class="ml-3 flex-1 overflow-hidden select-none"> <div class="flex justify-between items-center mb-1"> <h3 class="text-sm font-medium text-black truncate w-24">${chat.name}</h3> <span class="text-xs text-gray-400">${chat.time}</span> </div> <div class="flex justify-between items-center"> <p class="text-xs text-gray-500 truncate w-32">${chat.lastMessage}</p> ${chat.muted ? '<i class="fas fa-bell-slash text-xs text-gray-400"></i>' : ''} </div> </div>;
els.sidebarList.appendChild(div);
});
}

function renderMessages(chat) {
els.msgContainer.innerHTML = ‘’;
chat.messages.forEach(msg => appendMessageToDOM(msg, chat));
scrollToBottom();
}

function appendMessageToDOM(msg, chat) {
const isMe = msg.sender === ‘me’;
const div = document.createElement(‘div’);
div.className = flex mb-4 msg-anim ${isMe ? 'justify-end' : 'justify-start'};
div.dataset.id = msg.id; // For context menu

// Avatar
const avatarUrl = isMe ? state.currentUser.avatar : (msg.sender === ‘them’ ? chat.avatar : ‘https://api.dicebear.com/7.x/identicon/svg?seed=Stranger‘);
const avatar = <img src="${avatarUrl}" class="w-9 h-9 rounded bg-gray-300 object-cover shrink-0 cursor-pointer hover:opacity-90 no-drag" onclick="showProfile('${msg.sender === 'me' ? 'me' : chat.id}')">;

// Content Generation
let contentHTML = ‘’;
if (msg.type === ‘text’) {
let text = msg.content.replace(/</g, "<").replace(/>/g, “>”);
// Simple Emoji Parse
text = text.replace(/\n/g, ‘<br>‘);

const bubbleClass = isMe ? ‘bg-message-sent’ : ‘bg-message-received’;
contentHTML = <div class="px-3 py-2 rounded-md text-sm shadow-sm relative break-words leading-6 ${bubbleClass} ${isMe ? 'bg-[#95ec69]' : 'bg-white'}" style="max-width: 350px;"> ${msg.quote ?<div class="text-xs text-gray-500 mb-1 border-l-2 border-gray-300 pl-2 truncate max-w-[200px] select-none">${msg.quote}</div>: ''} ${text} <div class="absolute top-3 ${isMe ? '-right-1.5 border-l-[#95ec69]' : '-left-1.5 border-r-white'} w-0 h-0 border-t-[6px] border-t-transparent border-b-[6px] border-b-transparent ${isMe ? 'border-l-[6px]' : 'border-r-[6px]'}"></div> </div>;
} else if (msg.type === ‘redpacket’) {
contentHTML = <div class="red-packet" onclick="openRedPacketModal('${chat.name}', '${chat.avatar}')"> <div class="bg-[#fa9d3b] text-white flex items-center justify-center w-10 h-10 rounded mr-3 text-2xl"><i class="fas fa-yen-sign"></i></div> <div class="flex flex-col text-white"> <span class="text-sm font-medium">恭喜发财,大吉大利</span> <span class="text-xs opacity-80">微信红包</span> </div> </div>;
} else if (msg.type === ‘file’) {
contentHTML = <div class="bg-white p-3 rounded flex items-center gap-3 shadow-sm w-[200px] cursor-pointer hover:bg-gray-50 border border-gray-100"> <div class="text-4xl text-gray-400"><i class="fas fa-file-alt"></i></div> <div class="overflow-hidden"> <div class="text-sm truncate text-black">${msg.content}</div> <div class="text-xs text-gray-400">12 KB</div> </div> </div>;
}

// Name for groups
const nameHTML = (!isMe && chat.type === ‘group’ && msg.name) ? <div class="text-xs text-gray-400 mb-1 ml-1">${msg.name}</div> : ‘’;

div.innerHTML = isMe
? <div class="flex flex-col items-end">${contentHTML}</div><div class="ml-3">${avatar}</div>
: <div class="mr-3">${avatar}</div><div class="flex flex-col items-start">${nameHTML}${contentHTML}</div>;

// Right click event
div.querySelector(isMe ? ‘.bg-message-sent’ : (msg.type === ‘text’ ? ‘.bg-message-received’ : ‘.red-packet, .bg-white’)).addEventListener(‘contextmenu’, (e) => {
e.preventDefault();
showContextMenu(e, msg.id);
});

els.msgContainer.appendChild(div);
}

function renderMoments() {
const container = document.getElementById(‘moments-container’);
// Keep the cover header, remove rest
const children = Array.from(container.children);
children.slice(1).forEach(c => c.remove());

state.moments.forEach(moment => {
const div = document.createElement(‘div’);
div.className = “flex gap-4 p-4 border-b border-gray-100”;

let imagesHTML = “”;
if (moment.images.length > 0) {
imagesHTML = <div class="flex gap-1 mt-2 flex-wrap"> ${moment.images.map(img =><img src="${img}" class="w-24 h-24 object-cover bg-gray-100 cursor-pointer hover:opacity-90">).join('')} </div>;
}

let commentsHTML = “”;
if (moment.likes.length > 0 || moment.comments.length > 0) {
commentsHTML = <div class="bg-[#f7f7f7] mt-3 p-2 rounded text-sm"> ${moment.likes.length ?<div class="text-[#576b95] mb-1"><i class="far fa-heart mr-1"></i>${moment.likes.join(‘, ‘)}</div>: ''} ${moment.likes.length && moment.comments.length ? '<div class="border-t border-gray-200 my-1"></div>' : ''} ${moment.comments.map(c =><div><span class="text-[#576b95] font-medium">${c.name}</span>: <span class="text-gray-700">${c.content}</span></div>).join('')} </div>;
}

div.innerHTML = <img src="${moment.avatar}" class="w-10 h-10 rounded bg-gray-300 shrink-0"> <div class="flex-1"> <div class="text-[#576b95] font-medium font-bold mb-1">${moment.name}</div> <div class="text-sm text-gray-800 leading-relaxed">${moment.content}</div> ${imagesHTML} <div class="flex justify-between items-center mt-2 text-xs text-gray-400"> <span>${moment.time}</span> <div class="bg-[#f7f7f7] px-2 py-1 rounded cursor-pointer hover:bg-gray-200 text-[#576b95]"><i class="fas fa-ellipsis-h"></i></div> </div> ${commentsHTML} </div>;
container.appendChild(div);
});
}

function renderContacts() {
const container = document.getElementById(‘contacts-list-detail’);
container.innerHTML = ‘’;
state.chats.forEach(chat => {
if (chat.type === ‘user’ && chat.id !== 3) { // Filter out file helper
const div = document.createElement(‘div’);
div.className = “p-3 border-b border-gray-100 hover:bg-gray-50 cursor-pointer flex items-center gap-3”;
div.onclick = () => {
switchTab(‘chat’);
selectChat(chat.id);
};
div.innerHTML = <img src="${chat.avatar}" class="w-9 h-9 rounded bg-gray-300"> <span class="font-medium text-gray-700">${chat.name}</span>;
container.appendChild(div);
}
});
}

function generateEmojiPicker() {
const emojis = [“😀”,”😂”,”🤣”,”😍”,”🥰”,”😎”,”🤔”,”😭”,”😡”,”💩”,”👻”,”💀”,”👍”,”👎”,”🤝”,”🙏”,”💪”,”🎉”,”❤️”,”💔”,”🤡”,”👽”,”🤖”,”🎃”,”💋”,”👋”,”👌”,”✌️”,”👀”,”🐶”,”🐱”,”🐭”,”🐹”,”🐰”,”🦊”,”🐻”,”🐼”];
els.emojiPicker.innerHTML = ‘’;
emojis.forEach(e => {
const span = document.createElement(‘span’);
span.textContent = e;
span.className = “cursor-pointer hover:bg-gray-200 rounded text-center text-xl p-1”;
span.onclick = () => {
els.msgInput.value += e;
els.emojiPicker.classList.add(‘hidden’);
updateSendBtn();
els.msgInput.focus();
};
els.emojiPicker.appendChild(span);
});
}

// —- Actions & Logic —-

function switchTab(tabName) {
state.activeTab = tabName;
// Icons
els.navItems.forEach(item => {
const isActive = item.dataset.tab === tabName;
item.className = nav-item text-xl cursor-pointer transition relative group ${isActive ? 'text-[#07c160]' : 'text-gray-400 hover:text-white'};
});
// Views
Object.keys(els.views).forEach(key => {
if (key === tabName) {
els.views[key].classList.remove(‘hidden’);
els.views[key].classList.add(‘page-anim’);
} else {
els.views[key].classList.add(‘hidden’);
els.views[key].classList.remove(‘page-anim’);
}
});

if (tabName === ‘chat’) renderSidebar();
}

function selectChat(id) {
state.activeChatId = id;
const chat = state.chats.find(c => c.id === id);
if (!chat) return;

els.chatTitle.textContent = chat.name;
document.getElementById(‘chat-mute-icon’).classList.toggle(‘hidden’, !chat.muted);

renderMessages(chat);
renderSidebar();

// Clear inputs
cancelQuote();
els.msgInput.focus();
}

function sendMessage() {
const text = els.msgInput.value.trim();
if (!text) return;

const chat = state.chats.find(c => c.id === state.activeChatId);
const msg = {
id: Date.now(),
sender: ‘me’,
content: text,
type: ‘text’,
quote: tempQuote,
time: new Date().toLocaleTimeString([], {hour: ‘2-digit’, minute:’2-digit’})
};

chat.messages.push(msg);
chat.lastMessage = text;
chat.time = msg.time;

appendMessageToDOM(msg, chat);
scrollToBottom();

// Reset input
els.msgInput.value = ‘’;
updateSendBtn();
cancelQuote();
moveChatToTop(chat);

// Bot Reply
if (chat.name !== ‘文件传输助手’) {
clearTimeout(state.replyTimer);
state.replyTimer = setTimeout(() => {
botReply(chat, text);
}, 1000 + Math.random() 1500);
}
}

function sendRedPacketMock() {
const chat = state.chats.find(c => c.id === state.activeChatId);
const msg = {
id: Date.now(), sender: ‘me’, content: ‘红包’, type: ‘redpacket’,
time: new Date().toLocaleTimeString([], {hour: ‘2-digit’, minute:’2-digit’})
};
chat.messages.push(msg);
chat.lastMessage = “[红包] 恭喜发财”;
appendMessageToDOM(msg, chat);
scrollToBottom();
moveChatToTop(chat);
}

function botReply(chat, userText) {
let replyText = “”;
const keywords = Object.keys(botKeywords);

// Simple fuzzy match
const match = keywords.find(k => userText.includes(k));

if (match) {
const options = botKeywords[match];
const pick = options[Math.floor(Math.random()
options.length)];
replyText = typeof pick === ‘function’ ? pick() : pick;
} else {
const options = botKeywords[‘default’];
replyText = options[Math.floor(Math.random() options.length)];
}

const msg = {
id: Date.now(),
sender: ‘them’,
content: replyText,
type: ‘text’,
time: new Date().toLocaleTimeString([], {hour: ‘2-digit’, minute:’2-digit’})
};

chat.messages.push(msg);
chat.lastMessage = replyText;
chat.time = msg.time;

if (state.activeChatId === chat.id) {
appendMessageToDOM(msg, chat);
scrollToBottom();
} else {
// Show badge if not active (not implemented in UI fully but logic exists)
showToast(收到 ${chat.name} 的新消息);
}
moveChatToTop(chat);
}

function updateSendBtn() {
if (els.msgInput.value.trim()) {
els.sendBtn.classList.add(‘bg-[#07c160]’, ‘text-white’);
els.sendBtn.classList.remove(‘bg-[#e9e9e9]’, ‘text-[#07c160]’, ‘pointer-events-none’);
} else {
els.sendBtn.classList.remove(‘bg-[#07c160]’, ‘text-white’);
els.sendBtn.classList.add(‘bg-[#e9e9e9]’, ‘text-[#07c160]’, ‘pointer-events-none’);
}
}

function moveChatToTop(chat) {
const idx = state.chats.indexOf(chat);
if (idx > -1) {
state.chats.splice(idx, 1);
// Insert after pinned chats if not pinned, or top if pinned
// Simplified: just put top for now to simulate “recent”
state.chats.unshift(chat);
}
renderSidebar();
}

function scrollToBottom() {
els.msgContainer.scrollTop = els.msgContainer.scrollHeight;
}

// —- Context Menu Logic —-
function showContextMenu(e, msgId) {
contextTargetMsgId = msgId;
const menu = els.contextMenu;
menu.style.display = ‘block’;

// Boundary checks
let x = e.pageX;
let y = e.pageY;
if (x + 120 > window.innerWidth) x -= 120;
if (y + 150 > window.innerHeight) y -= 150;

menu.style.left = ${x}px;
menu.style.top = ${y}px;

// Close on click elsewhere
document.addEventListener(‘click’, hideContextMenu, { once: true });
}

function hideContextMenu() {
els.contextMenu.style.display = ‘none’;
}

function handleContextAction(action) {
const chat = state.chats.find(c => c.id === state.activeChatId);
const msgIdx = chat.messages.findIndex(m => m.id === contextTargetMsgId);
if (msgIdx === -1) return;
const msg = chat.messages[msgIdx];

if (action === ‘copy’) {
navigator.clipboard.writeText(msg.content);
showToast(“已复制”);
} else if (action === ‘delete’) {
chat.messages.splice(msgIdx, 1);
renderMessages(chat);
} else if (action === ‘recall’) {
if (msg.sender === ‘me’) {
chat.messages[msgIdx] = {
…msg,
type: ‘text’,
content: ‘<span class="text-xs text-gray-400 italic">你撤回了一条消息</span>‘ // simplified for visual
};
renderMessages(chat);
} else {
showToast(“不能撤回对方的消息”);
}
} else if (action === ‘quote’) {
tempQuote = msg.content;
els.quoteText.textContent = msg.content;
els.quotePreview.classList.remove(‘hidden’);
els.msgInput.focus();
}
}

function cancelQuote() {
tempQuote = null;
els.quotePreview.classList.add(‘hidden’);
}

function toggleChatDetails() {
const chat = state.chats.find(c => c.id === state.activeChatId);
if(!chat) return;
const action = confirm(聊天详情:\n当前聊天: ${chat.name}\n\n是否${chat.muted ? '取消静音' : '静音'}此聊天?);
if (action) {
chat.muted = !chat.muted;
document.getElementById(‘chat-mute-icon’).classList.toggle(‘hidden’, !chat.muted);
renderSidebar(); // Update list icon
showToast(chat.muted ? “已开启消息免打扰” : “已关闭消息免打扰”);
}
}

// —- Other UI Features —-
function createNewChat() {
const name = prompt(“请输入联系人名字:”);
if (name) {
const newChat = {
id: Date.now(),
type: ‘user’,
name: name,
avatar: https://api.dicebear.com/7.x/avataaars/svg?seed=${name},
lastMessage: “Now”,
time: “刚刚”,
messages: []
};
state.chats.unshift(newChat);
selectChat(newChat.id);
renderSidebar();
}
}

function showProfile(id) {
const user = id === ‘me’ ? state.currentUser : state.chats.find(c => c.id == id);
if (!user) return;
alert(用户资料:\nName: ${user.name}\nID: ${user.id});
}

function showToast(text) {
const t = document.getElementById(‘toast’);
t.textContent = text;
t.style.opacity = 1;
setTimeout(() => t.style.opacity = 0, 2000);
}

// Call Modal
function startCall(type) {
const chat = state.chats.find(c => c.id === state.activeChatId);
document.getElementById(‘call-avatar’).src = chat.avatar;
document.getElementById(‘call-name’).textContent = chat.name;
document.getElementById(‘call-status’).textContent = type === ‘voice’ ? ‘正在等待对方接听…’ : ‘正在邀请视频通话…’;
els.callModal.classList.remove(‘hidden’);

// Mock answer
setTimeout(() => {
document.getElementById(‘call-status’).textContent = “对方已接通”;
// Here you would start a timer
}, 3000);
}

function endCall() {
els.callModal.classList.add(‘hidden’);
showToast(“通话结束”);
}
function acceptCall() { /
Placeholder for incoming calls */ }

// Red Packet Modal
function openRedPacketModal(name, avatar) {
document.getElementById(‘rp-avatar’).src = avatar || ‘https://api.dicebear.com/7.x/avataaars/svg?seed=Me‘;
document.getElementById(‘rp-sender’).textContent = name + ‘的红包’;
document.getElementById(‘rp-result’).classList.add(‘hidden’);
els.rpModal.classList.remove(‘hidden’);
}

function openRedPacket() {
// Animation
const btn = els.rpModal.querySelector(‘.rounded-full’);
btn.classList.add(‘animate-spin’); // simple spin
setTimeout(() => {
btn.classList.remove(‘animate-spin’);
document.getElementById(‘rp-result’).classList.remove(‘hidden’);
}, 1000);
}

function closeRedPacket() {
els.rpModal.classList.add(‘hidden’);
}

// Event Listeners
function setupEvents() {
els.sendBtn.addEventListener(‘click’, sendMessage);

els.msgInput.addEventListener(‘input’, updateSendBtn);
els.msgInput.addEventListener(‘keydown’, (e) => {
if (e.key === ‘Enter’ && !e.shiftKey) {
e.preventDefault();
sendMessage();
} else if (e.key === ‘Enter’ && e.ctrlKey) {
els.msgInput.value += ‘\n’;
}
});

els.navItems.forEach(item => {
item.addEventListener(‘click’, () => switchTab(item.dataset.tab));
});

// Emoji Toggle
document.getElementById(‘emoji-btn’).addEventListener(‘click’, (e) => {
e.stopPropagation();
els.emojiPicker.classList.toggle(‘hidden’);
});
document.addEventListener(‘click’, (e) => {
if (!els.emojiPicker.contains(e.target) && e.target.id !== ‘emoji-btn’) {
els.emojiPicker.classList.add(‘hidden’);
}
});

// Context Menu
document.querySelectorAll(‘.context-menu-item’).forEach(item => {
item.addEventListener(‘click’, () => handleContextAction(item.dataset.action));
});
}

// Run
init();

</script> </body>
</html>

最好我想说一下,这个前端真的太强了,cc改后端,gemini改前端,效率直接起飞。