Files
ai-agent/internal/web/supportchat/assets/index.html
T
t 18c9354095 refactor: 将客服后端重构为宿主可嵌入模块
- 注入数据库、运行时配置、统一响应、文件存储和平台 AI 能力,补充业务读写工具与客户快捷操作契约。

- 移除模块内重复的组织、客户、工单、标签、技能、旧工作流、MCP 和迁移实现,将身份权限与业务主体交由宿主管理。

- 使用 libSQL 重构向量存储,并完善图片消息、访客身份、排队调度、企业微信和支持聊天页面。

- 统一 HTTP、DTO 与 WebSocket 的 snake_case 协议,补齐模块初始化、业务动作和公共载荷等回归测试。
2026-08-28 22:23:13 +08:00

170 lines
9.8 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#2475fc" />
<title>在线客服</title>
<link rel="stylesheet" href="./chat.css" />
</head>
<body>
<main id="support-app" class="support-app" aria-busy="true">
<section class="chat-shell">
<header class="chat-header">
<div class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M4 13v-1a8 8 0 0 1 16 0v1"/><path d="M5 11h2a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H5a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2Z"/><path d="M19 11h-2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h2a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2Z"/><path d="M16 19c-1 1-2.2 1.5-4 1.5"/></svg>
<span id="header-status-dot" class="header-status-dot connecting"></span>
</div>
<div class="brand-copy">
<h1 id="channel-title">在线客服</h1>
<p id="channel-subtitle">欢迎咨询</p>
</div>
<div class="header-actions">
<div id="connection-badge" class="connection-badge connecting" role="status">
<span></span><strong id="connection-label">连接中</strong>
</div>
<button id="retry-button" class="icon-button" type="button" aria-label="重新连接" title="重新连接">
<svg viewBox="0 0 24 24"><path d="M21 12a9 9 0 1 1-2.64-6.36L21 8"/><path d="M21 3v5h-5"/></svg>
</button>
<button id="close-button" class="icon-button close-button" type="button" aria-label="关闭聊天窗口" title="关闭聊天窗口">
<svg viewBox="0 0 24 24"><path d="m6 6 12 12M18 6 6 18"/></svg>
</button>
</div>
</header>
<div class="chat-content">
<main id="message-scroller" class="message-scroller" aria-live="polite" aria-label="Messages">
<button id="load-more" class="load-more" type="button" hidden>加载更早的消息</button>
<div id="loading-state" class="center-state">
<span class="loading-ring" aria-hidden="true"></span>
<p>正在连接客服…</p>
</div>
<div id="empty-state" class="center-state" hidden>
<p>请描述你的问题,我们会尽快为你处理。</p>
</div>
<div id="message-list" class="message-list" role="log"></div>
</main>
<div class="composer-area">
<section id="queue-status" class="queue-status" role="status" aria-live="polite" hidden>
<div class="queue-status-icon" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M19 8v6M22 11h-6"/></svg>
</div>
<div class="queue-status-copy">
<div class="queue-status-heading">
<strong id="queue-title">人工客服排队中</strong>
<span id="queue-position" class="queue-position">第 1 位</span>
</div>
<p id="queue-detail">已为你保留排队顺序,请耐心等候。</p>
<div class="queue-status-meta">
<span id="queue-wait">已等待不到 1 分钟</span>
<span id="queue-eta">预计很快接入</span>
</div>
</div>
</section>
<div id="quick-section" class="quick-section" hidden>
<button id="quick-button" class="quick-button" type="button">
<svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
<span>快捷服务</span>
</button>
</div>
<section class="composer-shell">
<div class="composer">
<div id="pending-uploads" class="pending-uploads" hidden></div>
<textarea id="message-input" rows="1" maxlength="2000" enterkeyhint="enter" placeholder="输入消息,换行键换行" aria-label="消息内容"></textarea>
<div class="composer-toolbar">
<div class="attachment-actions">
<input id="image-input" type="file" accept="image/*" multiple hidden />
<input id="file-input" type="file" hidden />
<button id="image-button" class="icon-button" type="button" aria-label="选择图片" title="选择图片">
<svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="8.5" cy="9" r="1.5"/><path d="m4 17 5-5 4 4 2-2 5 5"/></svg>
</button>
<button id="file-button" class="icon-button" type="button" aria-label="选择附件" title="选择附件">
<svg viewBox="0 0 24 24"><path d="m20 12.5-7.5 7.5a5 5 0 0 1-7-7l8.2-8.2a3.5 3.5 0 1 1 5 5L10.5 18a2 2 0 1 1-3-3l7.2-7.2"/></svg>
</button>
</div>
<span class="send-hint">点击按钮发送</span>
<button id="send-button" class="send-button" type="button" aria-label="发送" title="发送">
<svg viewBox="0 0 24 24"><path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/></svg>
</button>
</div>
</div>
</section>
<div id="status-bar" class="status-bar" hidden></div>
</div>
</div>
</section>
</main>
<div id="quick-overlay" class="dialog-overlay" hidden>
<section class="dialog-card quick-dialog" role="dialog" aria-modal="true" aria-labelledby="quick-title">
<button id="quick-close" class="dialog-close icon-button" type="button" aria-label="关闭快捷服务">
<svg viewBox="0 0 24 24"><path d="m6 6 12 12M18 6 6 18"/></svg>
</button>
<header>
<h2 id="quick-title">快捷服务</h2>
<p>请选择需要查询或办理的服务,点击后将直接发送。</p>
</header>
<div id="quick-list" class="quick-list"></div>
<div id="quick-empty" class="quick-empty" hidden>暂无可用的快捷服务</div>
</section>
</div>
<div id="access-overlay" class="dialog-overlay access-overlay" hidden>
<section class="dialog-card access-dialog" role="dialog" aria-modal="true" aria-labelledby="access-title">
<div class="access-icon" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M12 3 4.5 6v5.2c0 4.7 3.2 8.9 7.5 9.8 4.3-.9 7.5-5.1 7.5-9.8V6Z"/><path d="m9 12 2 2 4-4"/></svg>
</div>
<header>
<h2 id="access-title">验证当前设备</h2>
<p id="access-target">为保护套餐、WiFi 密码和设备操作,请先完成身份验证。</p>
</header>
<div class="access-methods" role="tablist" aria-label="验证方式">
<button id="access-password-tab" class="access-method active" type="button" role="tab" aria-selected="true">设备密码</button>
<button id="access-sms-tab" class="access-method" type="button" role="tab" aria-selected="false">短信验证</button>
</div>
<div id="access-password-panel" class="access-panel">
<label for="access-password">卡板/设备密码</label>
<input id="access-password" type="password" autocomplete="current-password" maxlength="64" placeholder="请输入密码" />
<p class="access-help">还没有设置密码?可切换到短信验证。</p>
</div>
<div id="access-sms-panel" class="access-panel" hidden>
<label for="access-phone">已绑定手机号</label>
<input id="access-phone" type="tel" inputmode="numeric" autocomplete="tel" maxlength="11" placeholder="请输入完整手机号" />
<label for="access-code">短信验证码</label>
<div class="access-code-row">
<input id="access-code" type="text" inputmode="numeric" autocomplete="one-time-code" maxlength="8" placeholder="请输入验证码" />
<button id="access-send-code" class="secondary-button" type="button">获取验证码</button>
</div>
<p id="access-sms-help" class="access-help">验证码只会发送到该卡板或设备已绑定的手机号。</p>
</div>
<p id="access-error" class="access-error" role="alert" hidden></p>
<footer class="dialog-actions access-actions">
<button id="access-cancel" class="secondary-button" type="button">返回</button>
<button id="access-submit" class="primary-button" type="button">验证并进入客服</button>
</footer>
</section>
</div>
<div id="close-overlay" class="dialog-overlay" hidden>
<section class="dialog-card close-dialog" role="dialog" aria-modal="true" aria-labelledby="close-title">
<button id="close-dialog-x" class="dialog-close icon-button" type="button" aria-label="关闭">
<svg viewBox="0 0 24 24"><path d="m6 6 12 12M18 6 6 18"/></svg>
</button>
<header>
<h2 id="close-title">结束当前对话?</h2>
<p>结束会话,客服将无法再查看您的消息记录,如需再次联系请重新发起对话。</p>
</header>
<footer class="dialog-actions">
<button id="continue-button" class="secondary-button" type="button">继续咨询</button>
<button id="confirm-close-button" class="danger-button" type="button">结束对话</button>
</footer>
</section>
</div>
<div id="toast-region" class="toast-region" aria-live="assertive"></div>
<script src="./chat.js" defer></script>
</body>
</html>