mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-06 05:27:48 +08:00
feat: 重写agent hub连接器,新增对话记录和微信输入状态
- 新增 Agent Hub 对话记录面板,按会话回看机器人与微信用户的完整收发内容 - 新增微信原生正在输入状态,长任务维持 typing,异常路径强制收尾
This commit is contained in:
@@ -2087,7 +2087,7 @@ function App(): React.ReactElement {
|
||||
/>
|
||||
)
|
||||
case 'agent-hub':
|
||||
return <AgentHubWorkspace />
|
||||
return <AgentHubWorkspace selfInfo={selfInfo} />
|
||||
case 'api':
|
||||
return (
|
||||
<ApiWorkspace
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
import React from 'react'
|
||||
import type {
|
||||
AgentHubConversationMessage,
|
||||
AgentHubConversationSummary
|
||||
} from '../../../../shared/agent-hub-conversation'
|
||||
import { agentHubKindPlaceholder } from '../../../../shared/agent-hub-conversation'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
Button
|
||||
} from '../../components/ui'
|
||||
|
||||
export interface AgentHubConversationPanelSelfInfo {
|
||||
wxid: string
|
||||
nickname: string
|
||||
avatar?: string
|
||||
}
|
||||
|
||||
interface AgentHubConversationPanelProps {
|
||||
selfInfo: AgentHubConversationPanelSelfInfo | null
|
||||
/** 机器人账号对应的微信用户标识;与入站 from_user_id 相同即为"本机自己"。 */
|
||||
selfUserId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 机器人头像。
|
||||
*
|
||||
* iLink 只给 openid,拿不到真实头像,所以这里用固定的品牌色矢量头像,
|
||||
* 让"右侧是机器人"一眼可辨,且不依赖任何外部资源。
|
||||
*/
|
||||
function AgentHubBotAvatar({ size = 32 }: { size?: number }): React.ReactElement {
|
||||
return (
|
||||
<span className="agent-hub-bot-avatar" style={{ width: size, height: size }} aria-hidden>
|
||||
<svg viewBox="0 0 24 24" width={size * 0.62} height={size * 0.62} role="presentation">
|
||||
<rect x="4.5" y="8" width="15" height="11" rx="3" fill="currentColor" />
|
||||
<rect x="11" y="3.6" width="2" height="3.4" rx="1" fill="currentColor" />
|
||||
<circle cx="12" cy="3.2" r="1.4" fill="currentColor" />
|
||||
<circle cx="9.2" cy="12.6" r="1.5" fill="var(--wxex-bg-elevated)" />
|
||||
<circle cx="14.8" cy="12.6" r="1.5" fill="var(--wxex-bg-elevated)" />
|
||||
<rect x="9.4" y="16" width="5.2" height="1.4" rx="0.7" fill="var(--wxex-bg-elevated)" />
|
||||
</svg>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function pad(value: number): string {
|
||||
return String(value).padStart(2, '0')
|
||||
}
|
||||
|
||||
function formatClock(timestamp: number): string {
|
||||
const date = new Date(timestamp)
|
||||
return `${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||||
}
|
||||
|
||||
function formatConversationTime(timestamp: number): string {
|
||||
if (!Number.isFinite(timestamp) || timestamp <= 0) return ''
|
||||
const date = new Date(timestamp)
|
||||
const now = new Date()
|
||||
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()
|
||||
if (timestamp >= startOfToday) return `今天 ${formatClock(timestamp)}`
|
||||
if (timestamp >= startOfToday - 24 * 60 * 60 * 1000) return `昨天 ${formatClock(timestamp)}`
|
||||
return `${date.getMonth() + 1}月${date.getDate()}日 ${formatClock(timestamp)}`
|
||||
}
|
||||
|
||||
/** 去掉 @im.wechat 之类的后缀并截断,避免把完整 openid 铺在界面上。 */
|
||||
function shortenUserId(userId: string): string {
|
||||
const bare = userId.split('@')[0]
|
||||
if (bare.length <= 10) return bare
|
||||
return `${bare.slice(0, 4)}…${bare.slice(-4)}`
|
||||
}
|
||||
|
||||
interface ConversationIdentity {
|
||||
name: string
|
||||
avatar?: string
|
||||
initial: string
|
||||
isSelf: boolean
|
||||
}
|
||||
|
||||
function resolveIdentity(
|
||||
userId: string,
|
||||
selfInfo: AgentHubConversationPanelSelfInfo | null,
|
||||
selfUserId?: string
|
||||
): ConversationIdentity {
|
||||
// 扫码登录的那个人 == 跟机器人聊天的这个人:直接复用左下角账号的头像与昵称。
|
||||
if (selfUserId && userId === selfUserId) {
|
||||
const name = selfInfo?.nickname?.trim() || '我自己'
|
||||
return {
|
||||
name,
|
||||
...(selfInfo?.avatar ? { avatar: selfInfo.avatar } : {}),
|
||||
initial: name.charAt(0),
|
||||
isSelf: true
|
||||
}
|
||||
}
|
||||
return { name: `微信用户 ${shortenUserId(userId)}`, initial: '微', isSelf: false }
|
||||
}
|
||||
|
||||
function ConversationAvatar({
|
||||
identity,
|
||||
size = 36
|
||||
}: {
|
||||
identity: ConversationIdentity
|
||||
size?: number
|
||||
}): React.ReactElement {
|
||||
if (identity.avatar) {
|
||||
return (
|
||||
<img
|
||||
className="agent-hub-conversation-avatar"
|
||||
style={{ width: size, height: size }}
|
||||
src={identity.avatar}
|
||||
alt=""
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<span
|
||||
className={`agent-hub-conversation-avatar is-fallback ${identity.isSelf ? 'is-self' : ''}`}
|
||||
style={{ width: size, height: size }}
|
||||
aria-hidden
|
||||
>
|
||||
{identity.initial}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function messageBody(message: AgentHubConversationMessage): string {
|
||||
return message.text.trim() || agentHubKindPlaceholder(message.kind)
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent Hub 对话记录。
|
||||
*
|
||||
* 左侧是会话列表(默认就是本机账号自己),右侧是仿微信的收发气泡:
|
||||
* 对方在左、机器人在右。数据来自本机 `conversations.json`,不经过任何外部服务。
|
||||
*/
|
||||
export function AgentHubConversationPanel({
|
||||
selfInfo,
|
||||
selfUserId
|
||||
}: AgentHubConversationPanelProps): React.ReactElement {
|
||||
const [summaries, setSummaries] = React.useState<AgentHubConversationSummary[]>([])
|
||||
const [selectedUserId, setSelectedUserId] = React.useState<string | null>(null)
|
||||
const [messages, setMessages] = React.useState<AgentHubConversationMessage[]>([])
|
||||
const [confirmingClear, setConfirmingClear] = React.useState(false)
|
||||
const bodyRef = React.useRef<HTMLDivElement>(null)
|
||||
// 用 ref 跟踪当前选中会话:推送回调里不能依赖闭包里的 selectedUserId,
|
||||
// 也不能在 setState 的 updater 里塞副作用(StrictMode 下会执行两次)。
|
||||
const selectedRef = React.useRef<string | null>(null)
|
||||
|
||||
React.useEffect(() => {
|
||||
let mounted = true
|
||||
void window.api.getAgentHubConversations().then((items) => {
|
||||
if (!mounted) return
|
||||
setSummaries(items)
|
||||
setSelectedUserId((current) => current ?? items[0]?.userId ?? null)
|
||||
})
|
||||
const unsubscribe = window.api.onAgentHubConversation(({ summary, message }) => {
|
||||
if (!mounted) return
|
||||
setSummaries((current) => {
|
||||
const withoutThis = current.filter((item) => item.userId !== summary.userId)
|
||||
return [summary, ...withoutThis].sort((left, right) => right.lastAt - left.lastAt)
|
||||
})
|
||||
if (selectedRef.current === null) setSelectedUserId(summary.userId)
|
||||
else if (selectedRef.current === summary.userId) setMessages((list) => [...list, message])
|
||||
})
|
||||
const unsubscribeCleared = window.api.onAgentHubConversationsCleared(() => {
|
||||
if (!mounted) return
|
||||
setSummaries([])
|
||||
setMessages([])
|
||||
setSelectedUserId(null)
|
||||
})
|
||||
return () => {
|
||||
mounted = false
|
||||
unsubscribe()
|
||||
unsubscribeCleared()
|
||||
}
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
selectedRef.current = selectedUserId
|
||||
if (!selectedUserId) {
|
||||
setMessages([])
|
||||
return
|
||||
}
|
||||
let mounted = true
|
||||
void window.api.getAgentHubConversation(selectedUserId).then((conversation) => {
|
||||
if (mounted) setMessages(conversation?.messages ?? [])
|
||||
})
|
||||
return () => {
|
||||
mounted = false
|
||||
}
|
||||
}, [selectedUserId])
|
||||
|
||||
React.useEffect(() => {
|
||||
const body = bodyRef.current
|
||||
if (body) body.scrollTop = body.scrollHeight
|
||||
}, [messages.length, selectedUserId])
|
||||
|
||||
const totalCount = summaries.reduce((sum, item) => sum + item.messageCount, 0)
|
||||
const selectedIdentity = selectedUserId
|
||||
? resolveIdentity(selectedUserId, selfInfo, selfUserId)
|
||||
: null
|
||||
|
||||
const clearConversations = async (): Promise<void> => {
|
||||
await window.api.clearAgentHubConversations()
|
||||
setSummaries([])
|
||||
setMessages([])
|
||||
setSelectedUserId(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="agent-hub-card agent-hub-conversation-card">
|
||||
<div className="agent-hub-conversation-heading">
|
||||
<div>
|
||||
<span className="agent-hub-card-kicker">收发记录</span>
|
||||
<h2>对话记录</h2>
|
||||
</div>
|
||||
<div className="agent-hub-conversation-actions">
|
||||
<span className="agent-hub-conversation-count">
|
||||
{summaries.length} 个会话 · {totalCount} 条
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={totalCount === 0}
|
||||
onClick={() => setConfirmingClear(true)}
|
||||
>
|
||||
清空记录
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="agent-hub-conversation-body">
|
||||
<div className="agent-hub-conversation-list">
|
||||
{summaries.length === 0 ? (
|
||||
<div className="agent-hub-conversation-list-empty">
|
||||
还没有收发记录。机器人收到或发出消息后,这里会显示完整内容。
|
||||
</div>
|
||||
) : (
|
||||
summaries.map((summary) => {
|
||||
const identity = resolveIdentity(summary.userId, selfInfo, selfUserId)
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={summary.userId}
|
||||
className={`agent-hub-conversation-item ${
|
||||
summary.userId === selectedUserId ? 'is-active' : ''
|
||||
}`}
|
||||
onClick={() => setSelectedUserId(summary.userId)}
|
||||
>
|
||||
<ConversationAvatar identity={identity} />
|
||||
<span className="agent-hub-conversation-item-copy">
|
||||
<span className="agent-hub-conversation-item-name">{identity.name}</span>
|
||||
<span className="agent-hub-conversation-item-preview">
|
||||
{summary.lastDirection === 'out' ? '机器人:' : ''}
|
||||
{summary.lastPreview || '(空消息)'}
|
||||
</span>
|
||||
</span>
|
||||
<span className="agent-hub-conversation-item-time">
|
||||
{formatConversationTime(summary.lastAt)}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="agent-hub-conversation-thread" ref={bodyRef}>
|
||||
{!selectedIdentity || messages.length === 0 ? (
|
||||
<div className="agent-hub-conversation-thread-empty">
|
||||
{summaries.length === 0
|
||||
? '左侧出现会话后,这里会显示完整对话。'
|
||||
: '这个会话还没有消息。'}
|
||||
</div>
|
||||
) : (
|
||||
messages.map((message) => (
|
||||
<div
|
||||
className={`agent-hub-message ${message.direction === 'out' ? 'is-out' : 'is-in'}`}
|
||||
key={message.id}
|
||||
>
|
||||
{message.direction === 'out' ? (
|
||||
<AgentHubBotAvatar />
|
||||
) : (
|
||||
<ConversationAvatar identity={selectedIdentity} size={32} />
|
||||
)}
|
||||
<div className="agent-hub-message-main">
|
||||
<div className="agent-hub-message-bubble">{messageBody(message)}</div>
|
||||
<div className="agent-hub-message-meta">
|
||||
<span>{formatConversationTime(message.createdAt)}</span>
|
||||
{message.direction === 'out' && (
|
||||
<span className={message.status === 'failed' ? 'is-failed' : ''}>
|
||||
{message.status === 'failed'
|
||||
? `发送失败${message.errorCode ? `(${message.errorCode})` : ''}`
|
||||
: '已发送'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AlertDialog open={confirmingClear} onOpenChange={setConfirmingClear}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>清空对话记录?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
将删除本机保存的全部机器人收发记录。这不影响微信里的原始消息,也不影响运行日志。
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
onClick={() => void clearConversations()}
|
||||
>
|
||||
确认清空
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from '../../components/ui'
|
||||
import { AgentHubConversationPanel } from './AgentHubConversationPanel'
|
||||
|
||||
const STATUS_LABELS: Record<WechatConnectorStatus, string> = {
|
||||
checking: '正在检查',
|
||||
@@ -30,7 +31,14 @@ const LOG_SOURCE_LABELS: Record<AgentHubLogSource, string> = {
|
||||
'wechat-connector': '微信连接器'
|
||||
}
|
||||
|
||||
export function AgentHubWorkspace(): React.ReactElement {
|
||||
export interface AgentHubWorkspaceProps {
|
||||
/** 软件左下角那个账号:用于在对话记录左侧显示"对方是谁"。 */
|
||||
selfInfo?: { wxid: string; nickname: string; avatar?: string } | null
|
||||
}
|
||||
|
||||
export function AgentHubWorkspace({
|
||||
selfInfo = null
|
||||
}: AgentHubWorkspaceProps = {}): React.ReactElement {
|
||||
const [status, setStatus] = React.useState<AgentHubStatus>({
|
||||
hub: 'offline',
|
||||
connector: 'checking',
|
||||
@@ -283,6 +291,8 @@ export function AgentHubWorkspace(): React.ReactElement {
|
||||
</div>
|
||||
<p className="agent-hub-log-note">日志会隐藏 Token 和二维码数据,不记录你的微信密码。</p>
|
||||
</section>
|
||||
|
||||
<AgentHubConversationPanel selfInfo={selfInfo} selfUserId={status.wechatUserId} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1849,3 +1849,206 @@
|
||||
overflow-y: auto;
|
||||
padding: 0 10px 12px;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Agent Hub 对话记录(收发回看) */
|
||||
/* 与运行日志明确分工:日志只记流程与故障,这里保存完整收发内容。 */
|
||||
/* 全部使用主题 token,因此浅色 / 深色自动适配,不需要单独覆写。 */
|
||||
/* ------------------------------------------------------------------ */
|
||||
.agent-hub-conversation-card {
|
||||
max-width: 1080px;
|
||||
margin: 16px auto 0;
|
||||
padding: 20px 22px;
|
||||
}
|
||||
.agent-hub-conversation-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.agent-hub-conversation-heading h2 {
|
||||
margin-top: 3px;
|
||||
}
|
||||
.agent-hub-conversation-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.agent-hub-conversation-count {
|
||||
color: var(--wxex-text-muted);
|
||||
font: 500 12px/18px var(--wxex-font);
|
||||
}
|
||||
.agent-hub-conversation-body {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 220px) minmax(0, 1fr);
|
||||
min-height: 340px;
|
||||
border: 1px solid hsl(var(--tm-border-subtle));
|
||||
border-radius: var(--wxex-radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
.agent-hub-conversation-list {
|
||||
min-width: 0;
|
||||
max-height: 420px;
|
||||
overflow: auto;
|
||||
padding: 8px;
|
||||
border-right: 1px solid hsl(var(--tm-border-subtle));
|
||||
}
|
||||
.agent-hub-conversation-list-empty {
|
||||
padding: 12px 8px;
|
||||
color: var(--wxex-text-muted);
|
||||
font: 13px/20px var(--wxex-font);
|
||||
}
|
||||
.agent-hub-conversation-item {
|
||||
display: grid;
|
||||
grid-template-columns: 36px minmax(0, 1fr);
|
||||
grid-template-areas: 'avatar copy' 'avatar time';
|
||||
gap: 2px 10px;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 0;
|
||||
border-radius: var(--wxex-radius-md);
|
||||
background: transparent;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
font-family: var(--wxex-font);
|
||||
}
|
||||
.agent-hub-conversation-item:hover {
|
||||
background: var(--wxex-bg-sidebar);
|
||||
}
|
||||
.agent-hub-conversation-item.is-active {
|
||||
background: var(--wxex-brand-soft);
|
||||
}
|
||||
.agent-hub-conversation-avatar {
|
||||
grid-area: avatar;
|
||||
align-self: center;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
flex: none;
|
||||
}
|
||||
.agent-hub-conversation-avatar.is-fallback {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--wxex-bg-sidebar);
|
||||
color: var(--wxex-text-secondary);
|
||||
font: 600 13px/1 var(--wxex-font);
|
||||
}
|
||||
.agent-hub-conversation-avatar.is-fallback.is-self {
|
||||
background: var(--wxex-brand-soft);
|
||||
color: var(--wxex-brand);
|
||||
}
|
||||
.agent-hub-conversation-item-copy {
|
||||
grid-area: copy;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.agent-hub-conversation-item-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--wxex-text-primary);
|
||||
font: 700 13px/18px var(--wxex-font);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.agent-hub-conversation-item-preview {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--wxex-text-muted);
|
||||
font: 12px/17px var(--wxex-font);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.agent-hub-conversation-item-time {
|
||||
grid-area: time;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--wxex-text-muted);
|
||||
font: 11px/16px var(--wxex-font);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.agent-hub-conversation-note {
|
||||
margin: 10px 4px 0;
|
||||
color: var(--wxex-text-muted);
|
||||
font: 11px/16px var(--wxex-font);
|
||||
}
|
||||
.agent-hub-conversation-thread {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
max-height: 420px;
|
||||
overflow: auto;
|
||||
padding: 14px 16px;
|
||||
background: var(--wxex-bg-main);
|
||||
}
|
||||
.agent-hub-conversation-thread-empty {
|
||||
margin: auto;
|
||||
color: var(--wxex-text-muted);
|
||||
font: 13px/20px var(--wxex-font);
|
||||
text-align: center;
|
||||
}
|
||||
.agent-hub-message {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
.agent-hub-message.is-out {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.agent-hub-message-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
max-width: min(78%, 560px);
|
||||
}
|
||||
.agent-hub-message.is-out .agent-hub-message-main {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.agent-hub-message-bubble {
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid hsl(var(--tm-border-subtle));
|
||||
border-radius: var(--wxex-radius-md);
|
||||
background: var(--wxex-bg-elevated);
|
||||
color: var(--wxex-text-primary);
|
||||
font: 13px/20px var(--wxex-font);
|
||||
overflow-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.agent-hub-message.is-out .agent-hub-message-bubble {
|
||||
border-color: transparent;
|
||||
background: var(--wxex-brand-soft);
|
||||
}
|
||||
.agent-hub-message-meta {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
color: var(--wxex-text-muted);
|
||||
font: 11px/16px var(--wxex-font);
|
||||
}
|
||||
.agent-hub-message-meta .is-failed {
|
||||
color: var(--wxex-danger);
|
||||
}
|
||||
.agent-hub-bot-avatar {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: none;
|
||||
border-radius: 50%;
|
||||
background: var(--wxex-ai-soft);
|
||||
color: var(--wxex-ai);
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.agent-hub-conversation-body {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
.agent-hub-conversation-list {
|
||||
max-height: 220px;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid hsl(var(--tm-border-subtle));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user