| 1 | import { MessageSquare, Settings as SettingsIcon } from "lucide-react"; |
| 2 | import { CopyButton } from "../components/CopyButton"; |
| 3 | import { useT, type Translator } from "../lib/i18n"; |
| 4 | import { sidebarImScopeLabel, sidebarImSessionTarget, type SidebarImConnection } from "../app-runtime/sidebarImProjection"; |
| 5 | |
| 6 | type SidebarImConnectionDetailProps = { |
| 7 | connection: SidebarImConnection; |
| 8 | onClose: () => void; |
| 9 | onOpenSession: () => void; |
| 10 | onOpenSettings: () => void; |
| 11 | onManageAllowlist: () => void; |
| 12 | }; |
| 13 | |
| 14 | function sidebarImSessionLabel(connection: SidebarImConnection, translate: Translator): string { |
| 15 | const target = sidebarImSessionTarget(connection); |
| 16 | if (!target) { |
| 17 | return connection.remoteId ? translate("botDetail.readOnlyChannel") : translate("botDetail.noSession"); |
| 18 | } |
| 19 | if (connection.sessionSource === "auto") return translate("botDetail.readOnlyChannel"); |
| 20 | if (target.kind === "path") return target.value.split(/[\\/]/).pop() || target.value; |
| 21 | return target.value; |
| 22 | } |
| 23 | |
| 24 | function sidebarImAccessModeLabel(connection: SidebarImConnection, translate: Translator): string { |
| 25 | if (connection.allowAll) return translate("botDetail.accessAllowAll"); |
| 26 | if (connection.allowlistEnabled) return translate("botDetail.accessWhitelist"); |
| 27 | return translate("botDetail.accessDisabled"); |
| 28 | } |
| 29 | |
| 30 | function sidebarImAccessStatusLabel(connection: SidebarImConnection, translate: Translator): string { |
| 31 | if (connection.allowAll) return translate("botDetail.accessOpen"); |
| 32 | if (!connection.remoteId) return translate("botDetail.accessUnknown"); |
| 33 | return connection.allowlistMatched ? translate("botDetail.accessMatched") : translate("botDetail.accessMissing"); |
| 34 | } |
| 35 | |
| 36 | function sidebarImAccessStatusClass(connection: SidebarImConnection): string { |
| 37 | if (connection.allowAll || connection.allowlistMatched) return "ok"; |
| 38 | if (!connection.remoteId) return "muted"; |
| 39 | return "warn"; |
| 40 | } |
| 41 | |
| 42 | export function SidebarImConnectionDetail({ connection, onClose, onOpenSession, onOpenSettings, onManageAllowlist }: SidebarImConnectionDetailProps) { |
| 43 | const translate = useT(); |
| 44 | const target = sidebarImSessionTarget(connection); |
| 45 | const accessStatusClass = sidebarImAccessStatusClass(connection); |
| 46 | return ( |
| 47 | <div className="bot-detail"> |
| 48 | <section className="bot-detail__summary"> |
| 49 | <div className={`bot-detail__avatar bot-detail__avatar--${connection.platform}`} aria-hidden="true"> |
| 50 | {connection.platform === "qq" ? "Q" : connection.platform === "weixin" ? "微" : connection.platform === "lark" ? "L" : "飞"} |
| 51 | </div> |
| 52 | <div className="bot-detail__summary-main"> |
| 53 | <span>{translate("botDetail.subtitle")}</span> |
| 54 | <h2>{connection.title}</h2> |
| 55 | <div className="bot-detail__chips"> |
| 56 | <span>{connection.platformLabel}</span> |
| 57 | <span>{connection.statusLabel}</span> |
| 58 | <span>{sidebarImScopeLabel(connection, translate)}</span> |
| 59 | </div> |
| 60 | </div> |
| 61 | <div className="bot-detail__summary-actions"> |
| 62 | <button type="button" className="btn btn--primary btn--small bot-detail__primary" disabled={!target} title={target ? undefined : translate("botDetail.openDisabled")} onClick={onOpenSession}> |
| 63 | <MessageSquare size={14} /> |
| 64 | {translate("botDetail.openSession")} |
| 65 | </button> |
| 66 | <button type="button" className="btn btn--secondary btn--small" onClick={onOpenSettings}> |
| 67 | <SettingsIcon size={14} /> |
| 68 | {translate("botDetail.manage")} |
| 69 | </button> |
| 70 | <button type="button" className="btn btn--secondary btn--small" onClick={onClose}> |
| 71 | {translate("botDetail.close")} |
| 72 | </button> |
| 73 | </div> |
| 74 | </section> |
| 75 | |
| 76 | <section className="bot-detail__panel bot-detail__panel--access" aria-label={translate("botDetail.access")}> |
| 77 | <div className="bot-detail__section-head"> |
| 78 | <span>{translate("botDetail.access")}</span> |
| 79 | <div className="bot-detail__section-actions"> |
| 80 | {connection.remoteId ? ( |
| 81 | <CopyButton text={connection.remoteId} label={translate("botDetail.copyRemoteId")} /> |
| 82 | ) : null} |
| 83 | <button type="button" className="btn btn--secondary btn--small" onClick={onManageAllowlist}> |
| 84 | {translate("botDetail.manageAllowlist")} |
| 85 | </button> |
| 86 | </div> |
| 87 | </div> |
| 88 | <div className="bot-detail__access-grid"> |
| 89 | <div> |
| 90 | <span>{translate("botDetail.accessMode")}</span> |
| 91 | <strong>{sidebarImAccessModeLabel(connection, translate)}</strong> |
| 92 | </div> |
| 93 | <div> |
| 94 | <span>{translate("botDetail.accessCurrentUser")}</span> |
| 95 | <code title={connection.remoteId || undefined}>{connection.remoteId || "—"}</code> |
| 96 | </div> |
| 97 | <div> |
| 98 | <span>{translate("botDetail.accessStatus")}</span> |
| 99 | <strong className={`bot-detail__access-status bot-detail__access-status--${accessStatusClass}`}> |
| 100 | {sidebarImAccessStatusLabel(connection, translate)} |
| 101 | </strong> |
| 102 | </div> |
| 103 | </div> |
| 104 | <div className="bot-detail__allowlist"> |
| 105 | <span>{translate("botDetail.channelAllowlistUsers")}</span> |
| 106 | <div className="bot-detail__id-list"> |
| 107 | {connection.allowlistUsers.length > 0 ? ( |
| 108 | connection.allowlistUsers.map((id) => ( |
| 109 | <code |
| 110 | key={id} |
| 111 | className={id === connection.remoteId ? "bot-detail__id-list-item--active" : ""} |
| 112 | title={id} |
| 113 | > |
| 114 | {id} |
| 115 | </code> |
| 116 | )) |
| 117 | ) : ( |
| 118 | <em>{translate("botDetail.emptyAllowlistUsers")}</em> |
| 119 | )} |
| 120 | </div> |
| 121 | </div> |
| 122 | </section> |
| 123 | |
| 124 | <section className="bot-detail__panel bot-detail__panel--facts" aria-label={translate("botDetail.summary")}> |
| 125 | <div className="bot-detail__section-head"> |
| 126 | <span>{translate("botDetail.summary")}</span> |
| 127 | </div> |
| 128 | <div className="bot-detail__facts"> |
| 129 | <div> |
| 130 | <span>{translate("botDetail.remoteId")}</span> |
| 131 | <code>{connection.remoteId || "—"}</code> |
| 132 | </div> |
| 133 | <div> |
| 134 | <span>{translate("botDetail.localTopic")}</span> |
| 135 | <strong>{sidebarImSessionLabel(connection, translate)}</strong> |
| 136 | </div> |
| 137 | <div> |
| 138 | <span>{translate("botDetail.scope")}</span> |
| 139 | <strong>{sidebarImScopeLabel(connection, translate)}</strong> |
| 140 | </div> |
| 141 | </div> |
| 142 | </section> |
| 143 | </div> |
| 144 | ); |
| 145 | } |
| 146 |