workbuddy-mobile.html 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
  6. <title>WorkBuddy 移动端 UI 复刻 · 交互演示</title>
  7. <script src="https://cdn.tailwindcss.com/3.4.16"></script>
  8. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.7.2/css/all.min.css">
  9. <script>
  10. tailwind.config = {
  11. theme: {
  12. extend: {
  13. colors: {
  14. soft: '#f5f5f7', // 次级底色 / 胶囊轨道 / 三方登录圆底
  15. fill: '#ececee', // 用户气泡
  16. line: '#ebebef', // 分割线
  17. ink: '#1a1a1a', // 主文字
  18. ink2: '#4a4a4f',
  19. muted: '#8e8e93', // 次级文字
  20. faint: '#b8b8bd', // 占位符 / 折叠步骤文字
  21. brand: '#17b58b', // 品牌薄荷绿
  22. brandsoft: '#e6f7f1',
  23. reasoning: '#5b6cf0', // “推理”标签蓝
  24. vision: '#3fae72', // “视觉”标签绿
  25. },
  26. fontFamily: {
  27. sans: ['"PingFang SC"', '"HarmonyOS Sans SC"', '"Noto Sans SC"', 'system-ui', '-apple-system', 'sans-serif'],
  28. },
  29. boxShadow: {
  30. float: '0 6px 18px -8px rgba(0,0,0,.18), 0 1px 2px rgba(0,0,0,.04)',
  31. dock: '0 10px 40px -12px rgba(0,0,0,.16)',
  32. sheet: '0 -12px 40px -20px rgba(0,0,0,.25)',
  33. },
  34. keyframes: {
  35. pop: { '0%': { opacity: 0, transform: 'translateY(10px) scale(.98)' }, '100%': { opacity: 1, transform: 'none' } },
  36. up: { '0%': { transform: 'translateY(100%)' }, '100%': { transform: 'none' } },
  37. },
  38. animation: { pop: 'pop .28s cubic-bezier(.2,0,0,1) both', up: 'up .32s cubic-bezier(.2,0,0,1) both' },
  39. },
  40. },
  41. };
  42. </script>
  43. <style>
  44. html, body { background: #e9e9ec; }
  45. body { font-family: "PingFang SC","HarmonyOS Sans SC","Noto Sans SC",system-ui,-apple-system,sans-serif; -webkit-font-smoothing: antialiased; }
  46. /* 截图基准 1170×2532@3x → 390×844 容器 */
  47. .phone { width: 390px; height: 844px; border-radius: 44px; overflow: hidden; position: relative; background: #fff;
  48. box-shadow: 0 30px 60px -20px rgba(0,0,0,.35), 0 0 0 10px #111, 0 0 0 11px #333; }
  49. .screen { position: absolute; inset: 0; display: none; flex-direction: column; background: #fff; }
  50. .screen.on { display: flex; }
  51. .hide { display: none !important; }
  52. /* 吉祥物素材源自白底截图:用 multiply 叠到任意底色上,去掉白色方块 */
  53. .blend { mix-blend-mode: multiply; }
  54. ::-webkit-scrollbar { width: 0; height: 0; }
  55. @media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
  56. </style>
  57. </head>
  58. <body class="min-h-screen text-ink">
  59. <header class="max-w-[1500px] mx-auto px-8 pt-10 pb-6 flex items-center gap-4">
  60. <img src="assets/app-logo.png" class="w-11 h-11 rounded-xl" alt="WorkBuddy">
  61. <div class="flex-1">
  62. <h1 class="text-2xl font-semibold">WorkBuddy 移动端 UI 复刻</h1>
  63. <p class="text-sm text-ink2/70 mt-1">腾讯云 AI 工作台 · 依据 uinotes.com 收录截图还原 · HTML + Tailwind CSS 3 + Font Awesome 6</p>
  64. </div>
  65. <div class="flex gap-2 text-sm" id="filters">
  66. <button onclick="filter('all')" data-f="all" class="px-4 py-2 rounded-full">全部</button>
  67. <button onclick="filter('basic')" data-f="basic" class="px-4 py-2 rounded-full">基础版</button>
  68. <button onclick="filter('full')" data-f="full" class="px-4 py-2 rounded-full">完整版</button>
  69. </div>
  70. </header>
  71. <main id="wall" class="max-w-[1500px] mx-auto px-8 pb-20 flex flex-wrap gap-12 items-start justify-center"></main>
  72. <template id="tpl-phone">
  73. <div class="phone">
  74. <div class="h-[54px] shrink-0 flex items-center justify-between px-8 pt-1.5 text-[15px] font-semibold relative z-30">
  75. <span>03:25</span>
  76. <div class="absolute left-1/2 -translate-x-1/2 top-[6px] bg-[#141414] text-white text-[10px] rounded-full px-3 py-[3px] flex items-center gap-1">
  77. <i class="fa-brands fa-unsplash text-[9px]"></i> uinotes.com
  78. </div>
  79. <span class="flex items-center gap-1.5 text-[12px]">
  80. <i class="fa-solid fa-signal"></i><i class="fa-solid fa-wifi"></i>
  81. <span class="w-7 h-[13px] rounded-[3px] border border-black/80 relative"><span class="absolute inset-[2px] right-[9px] bg-black rounded-[1px]"></span></span>
  82. </span>
  83. </div>
  84. <!-- 01 引导首页 -->
  85. <section class="screen on" data-screen="intro">
  86. <div class="flex-1 relative pt-14">
  87. <button class="absolute left-4 top-1 w-10 h-10 rounded-full bg-white shadow-float flex items-center justify-center z-20" onclick="openSideIn(this)"><i class="fa-solid fa-bars-staggered"></i></button>
  88. <div class="absolute inset-x-0 top-[40%] -translate-y-1/2 flex flex-col items-center">
  89. <img src="assets/mascot.png" class="w-[190px] blend bg-[#f4f4f6] rounded-xl" alt="">
  90. <h2 class="mt-3 text-[30px] font-bold">Workbuddy 我帮你</h2>
  91. <div class="mt-5 p-1 rounded-full bg-soft flex text-[16px] w-[252px]">
  92. <button class="flex-1 py-2.5 rounded-full bg-white shadow-sm font-medium">云端工作</button>
  93. <button class="flex-1 py-2.5 rounded-full text-muted">连接电脑</button>
  94. </div>
  95. </div>
  96. <div class="absolute left-4 right-4 bottom-4">
  97. <div class="flex gap-2 text-[15px] mb-3">
  98. <button class="flex-1 h-11 rounded-xl border border-line flex items-center justify-center gap-1.5"><i class="fa-regular fa-images"></i>幻灯片</button>
  99. <button class="flex-1 h-11 rounded-xl border border-line flex items-center justify-center gap-1.5"><i class="fa-regular fa-circle-play"></i>视频生成</button>
  100. <button class="flex-1 h-11 rounded-xl border border-line flex items-center justify-center gap-1.5 text-[14px]"><i class="fa-solid fa-magnifying-glass-arrow-right text-[12px]"></i>深度研究</button>
  101. <button class="w-[74px] h-11 rounded-xl border border-line">更多</button>
  102. </div>
  103. <div class="rounded-[26px] bg-white shadow-dock p-4">
  104. <div class="h-7 text-[17px] text-faint">发消息...</div>
  105. <div class="flex items-center mt-2 gap-3">
  106. <i class="fa-solid fa-signal text-[15px] -rotate-45"></i>
  107. <button class="flex items-center gap-1 text-[15px]" onclick="openModelsIn(this)">选择模型 <i class="fa-solid fa-chevron-down text-[11px] text-faint"></i></button>
  108. <button class="ml-auto w-9 h-9 rounded-full flex items-center justify-center" onclick="toast(this, '添加附件 / 文件')"><i class="fa-solid fa-plus text-lg"></i></button>
  109. </div>
  110. </div>
  111. </div>
  112. </div>
  113. </section>
  114. <!-- 02 登录页 -->
  115. <section class="screen" data-screen="login">
  116. <div class="flex-1 flex flex-col">
  117. <div class="mt-[22%] flex flex-col items-center">
  118. <img src="assets/mascot.png" class="w-[196px]" alt="">
  119. <h2 class="mt-3 text-[28px] font-medium">WorkBuddy,我帮你</h2>
  120. <p class="mt-2 text-[17px] text-[#b3b3b8]">深度研究</p>
  121. </div>
  122. <div class="mt-auto px-6 pb-6">
  123. <button class="w-full h-[56px] rounded-2xl bg-[#1c1c1e] text-white text-[18px] font-medium" onclick="toast(this, '验证码已发送')">手机号登录</button>
  124. <div class="flex justify-between px-6 mt-7">
  125. <button class="flex flex-col items-center gap-2"><span class="w-14 h-14 rounded-full bg-soft flex items-center justify-center text-[22px]"><i class="fa-brands fa-weixin text-[#1aad19]"></i></span><span class="text-[13px]">微信</span></button>
  126. <button class="flex flex-col items-center gap-2"><span class="w-14 h-14 rounded-full bg-soft flex items-center justify-center text-[22px]"><i class="fa-brands fa-qq"></i></span><span class="text-[13px]">QQ</span></button>
  127. <button class="flex flex-col items-center gap-2"><span class="w-14 h-14 rounded-full bg-soft flex items-center justify-center text-[20px]"><i class="fa-brands fa-dyalog"></i></span><span class="text-[13px]">企业版</span></button>
  128. <button class="flex flex-col items-center gap-2" onclick="openMoreIn(this)"><span class="w-14 h-14 rounded-full bg-soft flex items-center justify-center text-[20px]"><i class="fa-solid fa-ellipsis"></i></span><span class="text-[13px]">其他</span></button>
  129. </div>
  130. <label class="flex items-center justify-center gap-2 mt-6 text-[13px] text-ink2">
  131. <span class="w-[18px] h-[18px] rounded-full border-[1.5px] border-ink"></span>
  132. 已阅读并同意<span class="underline">服务协议</span>和<span class="underline">隐私政策</span>
  133. </label>
  134. </div>
  135. </div>
  136. </section>
  137. <!-- 03 新对话(登录落地态) -->
  138. <section class="screen" data-screen="home">
  139. <div class="flex-1 flex flex-col relative pt-14">
  140. <button class="absolute left-4 top-1 w-10 h-10 rounded-full bg-white shadow-float flex items-center justify-center z-20" onclick="openSideIn(this)"><i class="fa-solid fa-bars-staggered"></i></button>
  141. <div class="flex-1 flex flex-col items-center justify-center">
  142. <h2 class="text-[30px] font-bold">Workbuddy 我帮你</h2>
  143. <div class="mt-5 p-1 rounded-full bg-soft flex text-[16px] w-[252px]">
  144. <button class="flex-1 py-2.5 rounded-full bg-white shadow-sm font-medium">云端工作</button>
  145. <button class="flex-1 py-2.5 rounded-full text-muted">连接电脑</button>
  146. </div>
  147. <div class="mt-3 flex items-center gap-1.5 text-[13px] text-ink2"><i class="fa-regular fa-clock text-[11px]"></i>定时任务<i class="fa-solid fa-xmark text-[10px] text-muted ml-1"></i></div>
  148. </div>
  149. <div class="px-4 pb-4">
  150. <div class="rounded-[26px] bg-white shadow-dock p-4">
  151. <textarea rows="1" class="w-full resize-none outline-none text-[17px] placeholder:text-faint" placeholder="发消息..." oninput="composerInput(this)"></textarea>
  152. <div class="flex items-center mt-2 gap-3">
  153. <i class="fa-solid fa-signal text-[15px] -rotate-45"></i>
  154. <button class="flex items-center gap-1.5 text-[15px]" onclick="openModelsIn(this)"><span data-model-icon></span><span data-model-name>选择模型</span><i class="fa-solid fa-chevron-down text-[11px] text-faint"></i></button>
  155. <button class="ml-auto w-9 h-9 rounded-full flex items-center justify-center" onclick="toast(this, '添加附件 / 文件')"><i class="fa-solid fa-plus text-lg"></i></button>
  156. <button data-send class="w-9 h-9 rounded-full bg-[#1c1c1e] text-white flex items-center justify-center hide" onclick="enterChat(this, '运动推荐')"><i class="fa-solid fa-arrow-up text-[15px]"></i></button>
  157. </div>
  158. </div>
  159. </div>
  160. </div>
  161. </section>
  162. <!-- 07~10 任务对话(由脚本按页面填充 #chat-scroll) -->
  163. <section class="screen" data-screen="chat">
  164. <div class="h-[70px] shrink-0 flex items-end pb-2.5 px-4 gap-3">
  165. <button class="w-11 h-11 rounded-full bg-white shadow-float flex items-center justify-center" onclick="openSideIn(this)"><i class="fa-solid fa-bars-staggered"></i></button>
  166. <div class="flex-1 text-center text-[18px] font-medium truncate pr-10" data-chat-title>运动推荐</div>
  167. <button class="w-10 h-10 shrink-0 rounded-full bg-white shadow-float flex items-center justify-center" onclick="toast(this, '新对话')"><i class="fa-regular fa-square-plus text-[18px]"></i></button>
  168. </div>
  169. <div class="flex-1 overflow-y-auto px-4 pt-2 pb-4" data-chat-scroll></div>
  170. <div class="px-4 pb-4" data-composer>
  171. <div class="rounded-[26px] bg-white shadow-dock p-4">
  172. <textarea rows="1" class="w-full resize-none outline-none text-[17px] placeholder:text-faint" placeholder="发消息或按住说话"></textarea>
  173. <div class="flex items-center mt-2 gap-3">
  174. <i class="fa-solid fa-signal text-[15px] -rotate-45"></i>
  175. <button class="flex items-center gap-1.5 text-[15px]" onclick="openModelsIn(this)"><img src="assets/model-kimi.png" class="w-5 h-5 rounded-md" alt=""><span class="font-medium">Kimi-K3</span><i class="fa-solid fa-chevron-down text-[11px] text-faint ml-0.5"></i></button>
  176. <button class="ml-auto w-9 h-9 rounded-full flex items-center justify-center" onclick="toast(this, '添加附件 / 文件')"><i class="fa-solid fa-plus text-lg"></i></button>
  177. </div>
  178. </div>
  179. </div>
  180. </section>
  181. <div class="pointer-events-none absolute bottom-2 left-1/2 -translate-x-1/2 w-[134px] h-[5px] rounded-full bg-black z-[70]"></div>
  182. <!-- 侧栏 -->
  183. <div class="absolute inset-0 z-30 hide" data-layer="side">
  184. <div class="absolute inset-0 bg-black/5" onclick="closeSideIn(this)"></div>
  185. <aside class="absolute inset-y-0 left-0 w-[292px] bg-white shadow-[20px_0_40px_-20px_rgba(0,0,0,.25)] rounded-r-[26px] px-5 pt-[60px] pb-6 flex flex-col animate-pop">
  186. <div class="flex justify-end -mt-1 mb-3"><span class="w-10 h-10 rounded-full bg-soft flex items-center justify-center"><i class="fa-brands fa-codepen"></i></span></div>
  187. <div class="flex items-center gap-2 px-1.5">
  188. <span class="w-8 h-8 rounded-full bg-brandsoft text-brand flex items-center justify-center text-[13px] font-semibold">T</span>
  189. <span class="text-[16px]">Tony</span>
  190. </div>
  191. <div class="mt-4 p-1 rounded-full bg-soft flex text-[15px]">
  192. <button class="flex-1 py-2 rounded-full bg-white shadow-sm font-medium">云端工作</button>
  193. <button class="flex-1 py-2 rounded-full text-muted">连接电脑</button>
  194. </div>
  195. <nav class="mt-6 flex flex-col text-[17px]">
  196. <button class="flex items-center gap-3 px-1.5 py-3.5 text-left" onclick="closeSideIn(this)"><i class="fa-regular fa-clock text-[18px] w-5"></i>自动化<i class="fa-solid fa-chevron-right ml-auto text-[12px] text-faint"></i></button>
  197. <button class="flex items-center gap-3 px-1.5 py-3.5 text-left" onclick="closeSideIn(this)"><i class="fa-solid fa-diagram-project text-[16px] w-5"></i>项目<i class="fa-solid fa-chevron-right ml-auto text-[12px] text-faint"></i></button>
  198. <button class="flex items-center gap-3 px-1.5 py-3.5 text-left" onclick="closeSideIn(this)"><i class="fa-solid fa-user-astronaut text-[17px] w-5"></i>专家<i class="fa-solid fa-chevron-right ml-auto text-[12px] text-faint"></i></button>
  199. </nav>
  200. <div class="border-t border-line my-3"></div>
  201. <div class="flex items-center justify-between text-[15px] text-muted px-1.5"><span>任务</span><button class="text-brand">编辑</button></div>
  202. <button class="flex items-center gap-2 text-[17px] mt-4 px-1.5"><i class="fa-solid fa-plus"></i><b>新建任务</b></button>
  203. <div class="mt-1 flex flex-col">
  204. <button class="text-left px-3 py-3 rounded-lg bg-[#f4f4f6] text-[16px] flex items-center justify-between" onclick="openChat(this, '生成AI发展历程PPT')">生成AI发展历程PPT <i class="fa-solid fa-circle-notch fa-spin text-brand text-[14px]"></i></button>
  205. <button class="text-left px-3 py-3 text-[16px]" onclick="openChat(this, '收集今日AI资讯')">收集今日AI资讯</button>
  206. <button class="text-left px-3 py-3 text-[16px]" onclick="openChat(this, '运动推荐')">运动推荐</button>
  207. </div>
  208. </aside>
  209. </div>
  210. <!-- 选择模型 -->
  211. <div class="absolute inset-0 z-40 hide" data-layer="models">
  212. <div class="absolute inset-0 bg-black/25" onclick="closeModelsIn(this)"></div>
  213. <div class="absolute left-0 right-0 bottom-0 animate-up">
  214. <img src="assets/mascot-head.png" class="w-[168px] mx-auto -mb-1 relative z-10 blend" alt="">
  215. <div class="relative bg-white rounded-t-[26px] shadow-sheet pt-2 pb-8 px-6">
  216. <div class="w-10 h-1 rounded-full bg-[#e5e5e7] mx-auto"></div>
  217. <h3 class="text-center text-[19px] font-semibold mt-3 mb-3">选择模型</h3>
  218. <div data-model-list class="divide-y divide-line"></div>
  219. </div>
  220. </div>
  221. </div>
  222. <!-- 其他登录方式 -->
  223. <div class="absolute inset-0 z-40 hide" data-layer="more">
  224. <div class="absolute inset-0 bg-black/25" onclick="closeMoreIn(this)"></div>
  225. <div class="absolute inset-x-3 bottom-9 animate-up flex flex-col gap-2">
  226. <div class="bg-white rounded-2xl overflow-hidden text-[17px] divide-y divide-line">
  227. <button class="w-full py-4" onclick="toast(this, '其他登录')">其他登录</button>
  228. <button class="w-full py-4" onclick="toast(this, '扫码登录')">扫码登录</button>
  229. <button class="w-full py-4" onclick="closeMoreIn(this);toast(this, '已选择免登录使用')">免登录使用</button>
  230. </div>
  231. <button class="w-full bg-white rounded-2xl py-4 text-[17px]" onclick="closeMoreIn(this)">取消</button>
  232. </div>
  233. </div>
  234. </div>
  235. </template>
  236. <script>
  237. /* ============ 数据(对齐 uinotes 截图 #025 模型面板、#052 侧栏、#054 对话) ============ */
  238. const MODELS = [
  239. { icon: 'auto', name: 'Auto', tags: ['推理', '视觉'] },
  240. { icon: 'assets/model-hy3.png', name: 'Hy3', tags: ['推理', '视觉'] },
  241. { icon: 'assets/model-glm.png', name: 'GLM-5.2', tags: ['推理', '视觉'] },
  242. { icon: 'assets/model-glm.png', name: 'GLM-5.1', tags: ['推理'] },
  243. { icon: 'assets/model-glm.png', name: 'GLM-5v-Turbo', tags: ['推理', '视觉'] },
  244. { icon: 'assets/model-kimi.png', name: 'Kimi-K3', tags: ['推理', '视觉'] },
  245. { icon: 'assets/model-kimi.png', name: 'Kimi-K2.7-Code', tags: ['推理', '视觉'] },
  246. { icon: 'assets/model-kimi.png', name: 'Kimi-K2.6', tags: ['推理', '视觉'] },
  247. ];
  248. const PAGES = [
  249. { title: '01 · 引导首页', tag: 'basic', screen: 'intro', desc: '品牌主视觉、云端/本地切换、快捷能力胶囊、大输入框' },
  250. { title: '02 · 登录页', tag: 'basic', screen: 'login', desc: '吉祥物 + 手机号登录 / 三方登录 / 协议勾选' },
  251. { title: '03 · 新对话', tag: 'basic', screen: 'home', desc: '登录落地态:定时任务快捷胶囊、有输入时出现发送按钮' },
  252. { title: '04 · 选择模型', tag: 'basic', custom: 'models', desc: '底部面板,猫头从面板上沿探出;推理/视觉标签 + 勾选态' },
  253. { title: '05 · 其他登录方式', tag: 'basic', custom: 'more', desc: 'iOS 风格操作表:其他登录 / 扫码登录 / 免登录使用' },
  254. { title: '06 · 侧栏任务列表', tag: 'full', screen: 'home', open: ['side'], desc: '用户信息、功能入口、历史任务(进行中任务带 spinner)' },
  255. { title: '07 · 任务对话', tag: 'full', screen: 'chat', custom: 'preview', desc: '用户气泡、过程分段折叠、参数表格、AI 操作预览' },
  256. { title: '08 · 执行中 / 追问', tag: 'full', screen: 'chat', custom: 'typing', desc: '已折叠过程 + 思考中 + 编号选项追问卡片' },
  257. { title: '09 · 完成态', tag: 'full', screen: 'chat', custom: 'done', desc: '任务创建成功表格、💡 提示块、朗读/复制/分享操作栏' },
  258. { title: '10 · 选项式追问', tag: 'full', screen: 'chat', custom: 'ask', desc: '2/2 分页追问:单选编号选项 + 自定义输入' },
  259. ];
  260. /* ============ 片段 ============ */
  261. const USER_BUBBLE = (text = '收集今日ai资讯') => `
  262. <div class="flex justify-end mb-3">
  263. <div class="max-w-[78%] bg-fill rounded-[20px] rounded-tr-md px-4 py-3 text-[16px]">
  264. <span class="inline-flex items-center gap-1.5 bg-white rounded-full px-3 py-1 text-[13px] mb-2"><i class="fa-regular fa-clock text-[11px]"></i>定时任务</span>
  265. <div>${text}</div>
  266. </div>
  267. </div>`;
  268. const STEP = status => `
  269. <div class="mt-2 rounded-lg bg-[#f6f8f7] px-3 py-3 flex items-center justify-between text-[15px]">
  270. <span class="text-faint">拆解为 1 个步骤</span>
  271. <span class="${status === 'done' ? 'text-[#7cc3a8]' : 'text-muted'}">${status === 'done' ? '已完成' : '执行中'} <i class="fa-solid fa-chevron-right text-[10px]"></i></span>
  272. </div>`;
  273. const DONE_ROW = `
  274. <div class="flex items-center gap-2 text-[14px] text-muted py-1 mt-2"><span class="w-[7px] h-[7px] rounded-full bg-brand"></span>已完成</div>
  275. <div class="border-t border-line"></div>`;
  276. const FOLD = (n, openText) => `
  277. <div class="flex items-center justify-between text-[14px] text-muted mt-3">
  278. <span class="flex items-center gap-2"><span class="w-[7px] h-[7px] rounded-full bg-[#8fcfb9]"></span>${n}</span>
  279. <button class="border border-[#bfe6d8] rounded-full px-3 py-[3px] text-[13px] text-brand">${openText}</button>
  280. </div>`;
  281. const TABLE = (head, rows) => `
  282. <div class="mt-2 rounded-[10px] overflow-hidden border border-line">
  283. <table class="w-full text-[15px] table-fixed">
  284. <thead><tr class="bg-[#f2f6f9] text-left">
  285. <th class="px-3 py-2.5 font-semibold w-[92px] border-r border-white">${head[0]}</th><th class="px-3 py-2.5 font-semibold">${head[1]}</th>
  286. </tr></thead>
  287. <tbody>${rows.map(([k, v]) => `<tr class="border-t border-line"><th class="px-3 py-3 text-left font-normal text-ink2 align-top">${k}</th><td class="px-3 py-3 break-words">${v}</td></tr>`).join('')}</tbody>
  288. </table>
  289. </div>`;
  290. const AI_FOOT = `
  291. <p class="mt-2 text-[13px] text-faint">内容由 AI 生成</p>
  292. <div class="flex gap-5 mt-2 text-muted text-[17px]"><i class="fa-solid fa-volume-low"></i><i class="fa-regular fa-copy"></i><i class="fa-solid fa-arrow-up-from-bracket"></i></div>`;
  293. const ASK_CARD = `
  294. <div class="rounded-t-2xl bg-[#f5f7f9] pt-3 pb-2">
  295. <div class="flex items-center justify-between px-4 text-[15px] text-muted">
  296. <span class="flex items-center gap-1.5"><i class="fa-solid fa-chevron-left text-[12px]"></i>2 / 2<i class="fa-solid fa-chevron-right text-[12px]"></i></span>
  297. <span class="w-9 h-9 rounded-full bg-[#e6edf2] flex items-center justify-center text-ink2"><i class="fa-solid fa-xmark text-[13px]"></i></span>
  298. </div>
  299. <div class="px-4 mt-1">
  300. <div class="text-[14px] font-semibold text-[#6b5ce7]">执行时间</div>
  301. <div class="text-[20px] font-semibold mt-1">希望每天什么时间执行?</div>
  302. </div>
  303. </div>
  304. <div class="bg-white">
  305. ${['早上 9:00', '早上 8:00', '晚上 21:00'].map((t, i) => `
  306. <div class="flex items-center gap-3 px-5 py-[18px] border-t border-line">
  307. <span class="w-[26px] h-[26px] rounded-full flex items-center justify-center text-[13px] ${i === 0 ? 'bg-brand text-white' : 'border border-[#d6d6da] text-muted'}">${i + 1}</span>
  308. <span class="text-[18px]">${t}</span>
  309. </div>`).join('')}
  310. </div>
  311. <div class="px-4 pt-3 pb-4 bg-white">
  312. <div class="h-[52px] rounded-2xl bg-[#f5f5f7] flex items-center pl-4 pr-1.5 text-[17px] text-faint">或输入其他选项
  313. <span class="ml-auto w-10 h-10 rounded-full bg-[#1c1c1e] text-white flex items-center justify-center"><i class="fa-solid fa-arrow-up text-[16px]"></i></span>
  314. </div>
  315. </div>`;
  316. const CHAT_BODIES = {
  317. preview: USER_BUBBLE() + DONE_ROW + FOLD('共 3 段过程', '收起') + STEP('done') + `
  318. <p class="mt-3 text-[17px] leading-[1.65]">我来帮你设置这个自动化任务。先确认一下关键参数:</p>
  319. ${STEP('done')}
  320. <p class="mt-3 text-[17px] leading-[1.65]">明白了:仅今天执行一次,时间早上 9:00。当前是凌晨 3:31,今天 9:00 还没到,可以正常预约。</p>
  321. <p class="mt-3 text-[17px] leading-[1.65]">按照流程,先给你看一下操作预览:</p>
  322. <div class="font-semibold text-[17px] mt-3 mb-2">- 操作预览 - 创建任务</div>
  323. ${TABLE(['项目', '内容'], [
  324. ['名称', '今日AI资讯收集'],
  325. ['频率类型', '<b>once</b>(单次执行)'],
  326. ['执行时间', '2026-08-03 09:00(Cron: 0 0 9 ...'],
  327. ['时区', 'Asia/Shanghai'],
  328. ['生效范围', '仅 2026-08-03 当天'],
  329. ['超时/重试', '300 秒 / 3 次'],
  330. ])}
  331. <p class="mt-3 text-[17px] leading-[1.65]">确认创建请回复"确认",需要调整请直接说明。</p>
  332. ${AI_FOOT}`,
  333. typing: USER_BUBBLE() + FOLD('已折叠 2 段过程', '展开') + STEP('running') + `
  334. <div class="mt-4 flex items-center gap-2 text-[16px] text-muted"><span class="w-[7px] h-[7px] rounded-full bg-[#8fcfb9]"></span>正在整理答案...</div>
  335. <div class="mt-4 flex gap-1"><span class="w-[6px] h-[6px] rounded-full bg-[#b9b9be]"></span><span class="w-[6px] h-[6px] rounded-full bg-[#c6c6cb]"></span><span class="w-[6px] h-[6px] rounded-full bg-[#d4d4d8]"></span></div>`,
  336. done: `
  337. <div class="flex justify-end mb-3"><div class="bg-fill rounded-[20px] rounded-tr-md px-4 py-3 text-[16px]">确认</div></div>
  338. ${DONE_ROW}
  339. ${FOLD('已折叠 1 段过程', '展开')}
  340. <div class="flex items-center gap-2 mt-3 text-[17px] font-semibold"><span class="text-[#12b76a] text-[18px]"><i class="fa-solid fa-circle-check"></i></span>任务创建成功!</div>
  341. ${TABLE(['项目', '内容'], [
  342. ['任务 ID', '4631744'],
  343. ['名称', '今日AI资讯收集'],
  344. ['状态', '已启用'],
  345. ['执行时间', '今天 09:00(2026-08-03 09:00:00 +'],
  346. ])}
  347. <p class="mt-3 text-[17px] leading-[1.65]">到点后我会自动搜索今日 AI 领域的重要动态(大模型发布、行业新闻、政策监管、融资、技术突破等),并整理成一份中文简报推送给你。</p>
  348. <div class="mt-3 border-l-[3px] border-[#e6e6ea] pl-3 text-[16px] leading-[1.65] text-ink2">💡 如果之后觉得每天都需要这份资讯,随时跟我说"改成每天 9 点执行",我可以帮你调整为长期任务。</div>
  349. ${AI_FOOT}`,
  350. ask: USER_BUBBLE() + DONE_ROW + FOLD('共 3 段过程', '收起') + `
  351. <p class="mt-3 text-[17px] leading-[1.65]">我来帮你设置这个自动化任务。先确认一下关键参数:</p>
  352. <p class="mt-3 text-[17px] leading-[1.65]">明白了:仅今天执行一次,时间早上 9:00。当前是凌晨 3:31,今天 9:00 还没到,可以正常预约。</p>`,
  353. };
  354. /* ============ 渲染 ============ */
  355. const wall = document.getElementById('wall');
  356. const tpl = document.getElementById('tpl-phone');
  357. const P = el => el && el.closest ? el.closest('.phone') : null;
  358. PAGES.forEach(page => {
  359. const wrap = document.createElement('section');
  360. wrap.className = 'flex flex-col gap-3';
  361. wrap.dataset.tag = page.tag;
  362. wrap.innerHTML = `
  363. <div class="flex items-baseline justify-between px-1 w-[390px]">
  364. <div class="text-[15px] font-semibold">${page.title}</div>
  365. <span class="text-[11px] px-2 py-[2px] rounded-full ${page.tag === 'full' ? 'bg-brandsoft text-brand' : 'bg-white text-ink2 border border-line'}">${page.tag === 'full' ? '完整版' : '基础版'}</span>
  366. </div>
  367. <div>${tpl.innerHTML}</div>
  368. <p class="text-[12px] text-ink2/60 px-1 leading-relaxed w-[390px]">${page.desc}</p>`;
  369. wall.appendChild(wrap);
  370. const phone = wrap.querySelector('.phone');
  371. phone.querySelectorAll('.screen').forEach(s => s.classList.toggle('on', s.dataset.screen === (page.screen || 'intro')));
  372. (page.open || []).forEach(l => phone.querySelector(`[data-layer="${l}"]`).classList.remove('hide'));
  373. if (page.custom === 'models') phone.querySelector('[data-layer="models"]').classList.remove('hide');
  374. if (page.custom === 'more') phone.querySelector('[data-layer="more"]').classList.remove('hide');
  375. if (CHAT_BODIES[page.custom]) {
  376. phone.querySelector('[data-chat-title]').textContent = '收集今日AI资讯';
  377. phone.querySelector('[data-chat-scroll]').innerHTML = CHAT_BODIES[page.custom];
  378. }
  379. // 追问态:卡片贴在输入框上方,输入栏隐藏(截图中输入框被追问面板取代)
  380. if (page.custom === 'typing' || page.custom === 'ask') {
  381. const bar = phone.querySelector('[data-composer]');
  382. const card = document.createElement('div');
  383. card.className = 'px-3';
  384. card.innerHTML = ASK_CARD;
  385. bar.parentNode.insertBefore(card, bar);
  386. bar.classList.add('hidden');
  387. }
  388. renderModels(phone);
  389. });
  390. /* ============ 模型面板 ============ */
  391. function renderModels(phone) {
  392. if (phone._sel === undefined) phone._sel = 0;
  393. const list = phone.querySelector('[data-model-list]');
  394. list.innerHTML = '';
  395. MODELS.forEach((m, i) => {
  396. const sel = i === phone._sel;
  397. const row = document.createElement('button');
  398. row.className = 'w-full flex items-center gap-3 py-[15px] text-left';
  399. row.innerHTML = `
  400. ${m.icon === 'auto'
  401. ? '<span class="w-8 h-8 rounded-full border-[1.5px] border-ink flex items-center justify-center text-[15px] font-semibold">A</span>'
  402. : `<img src="${m.icon}" class="w-8 h-8 rounded-lg object-cover" alt="">`}
  403. <span class="text-[18px] ${sel ? 'text-brand' : ''}">${m.name}</span>
  404. <span class="flex gap-1">${m.tags.map(t => `<span class="text-[12px] px-1.5 py-[2px] rounded ${t === '推理' ? 'bg-[#eaeefc] text-reasoning' : 'bg-[#e6f5ec] text-vision'}">${t}</span>`).join('')}</span>
  405. ${sel ? '<i class="fa-solid fa-check ml-auto text-brand"></i>' : ''}`;
  406. row.onclick = () => {
  407. phone._sel = i;
  408. const name = phone.querySelector('[data-model-name]');
  409. if (name) name.textContent = m.name;
  410. const icon = phone.querySelector('[data-model-icon]');
  411. if (icon) icon.innerHTML = m.icon === 'auto' ? '<i class="fa-regular fa-circle-user text-[16px]"></i>' : `<img src="${m.icon}" class="w-5 h-5 rounded-md" alt="">`;
  412. renderModels(phone);
  413. const phoneRef = phone;
  414. setTimeout(() => { phoneRef.querySelector('[data-layer="models"]').classList.add('hide'); toast(phoneRef, '已切换到 ' + m.name); }, 160);
  415. };
  416. list.appendChild(row);
  417. });
  418. }
  419. /* ============ 交互 ============ */
  420. const openModelsIn = el => P(el).querySelector('[data-layer="models"]').classList.remove('hide');
  421. const closeModelsIn = el => P(el).querySelector('[data-layer="models"]').classList.add('hide');
  422. const openMoreIn = el => P(el).querySelector('[data-layer="more"]').classList.remove('hide');
  423. const closeMoreIn = el => P(el).querySelector('[data-layer="more"]').classList.add('hide');
  424. const openSideIn = el => P(el).querySelector('[data-layer="side"]').classList.remove('hide');
  425. const closeSideIn = el => P(el).querySelector('[data-layer="side"]').classList.add('hide');
  426. function enterChat(el, title) {
  427. const phone = P(el);
  428. if (phone.querySelector('.screen.on').dataset.screen === 'chat') {
  429. phone.querySelector('[data-chat-title]').textContent = title;
  430. }
  431. phone.querySelector('[data-layer="side"]').classList.add('hide');
  432. phone.querySelectorAll('.screen').forEach(s => s.classList.toggle('on', s.dataset.screen === 'chat'));
  433. phone.querySelector('[data-chat-scroll]').scrollTop = 0;
  434. }
  435. const openChat = enterChat;
  436. function composerInput(el) {
  437. P(el).querySelector('[data-send]').classList.toggle('hide', !el.value.trim());
  438. }
  439. function toast(el, msg) {
  440. const phone = P(el);
  441. if (!phone) return;
  442. let t = phone.querySelector('[data-toast]');
  443. if (!t) {
  444. t = document.createElement('div');
  445. t.dataset.toast = '';
  446. t.className = 'absolute z-[80] left-1/2 -translate-x-1/2 bottom-28 bg-black/80 text-white text-[13px] px-4 py-2 rounded-xl animate-pop';
  447. phone.appendChild(t);
  448. }
  449. t.textContent = msg;
  450. clearTimeout(t._t);
  451. t._t = setTimeout(() => t.remove(), 1500);
  452. }
  453. function filter(tag) {
  454. document.querySelectorAll('[data-f]').forEach(b => {
  455. const on = b.dataset.f === tag;
  456. b.className = `px-4 py-2 rounded-full ${on ? 'bg-ink text-white' : 'bg-white border border-line'}`;
  457. });
  458. wall.querySelectorAll('section').forEach(s => s.classList.toggle('hide', tag !== 'all' && s.dataset.tag !== tag));
  459. }
  460. filter('all');
  461. </script>
  462. </body>
  463. </html>