mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-06 05:27:48 +08:00
fix: 修复转换微信语音发送逻辑
This commit is contained in:
@@ -206,7 +206,7 @@ export function PersonalWechatChatComposer({
|
||||
|
||||
const sendGenerated = async (): Promise<void> => {
|
||||
if (!generatedVoiceRef.current) return
|
||||
const result = await send(
|
||||
await send(
|
||||
{
|
||||
type: 'voice',
|
||||
to: targetId,
|
||||
@@ -215,7 +215,20 @@ export function PersonalWechatChatComposer({
|
||||
},
|
||||
voiceText.trim() || '语音消息'
|
||||
)
|
||||
if (result) clearGeneratedVoice()
|
||||
}
|
||||
|
||||
const sendGeneratedAlternative = async (): Promise<void> => {
|
||||
if (!generatedVoiceRef.current) return
|
||||
await send(
|
||||
{
|
||||
type: 'voice',
|
||||
to: targetId,
|
||||
filePath: generatedVoiceRef.current.filePath,
|
||||
isGroup: isGroupChat,
|
||||
voiceSendMode: 'legacy'
|
||||
},
|
||||
voiceText.trim() || '语音消息'
|
||||
)
|
||||
}
|
||||
|
||||
const canSend = Boolean(
|
||||
@@ -341,7 +354,7 @@ export function PersonalWechatChatComposer({
|
||||
<span style={{ width: `${Math.min(100, previewProgress)}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="personal-wechat-generated-result-actions">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -362,6 +375,15 @@ export function PersonalWechatChatComposer({
|
||||
<Button size="sm" onClick={() => void sendGenerated()} disabled={!canSend}>
|
||||
{busy ? '发送中…' : '发送'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => void sendGeneratedAlternative()}
|
||||
disabled={!canSend}
|
||||
title="使用另一条语音发送路径"
|
||||
>
|
||||
{busy ? '发送中…' : '空白语音?重新处理'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -2,19 +2,29 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import type { Contact } from '../../../../shared/types'
|
||||
import type {
|
||||
PersonalWechatSendRequest,
|
||||
PersonalWechatSenderStatus
|
||||
PersonalWechatSenderStatus,
|
||||
PersonalWechatVoiceDiagnostic
|
||||
} from '../../../../shared/personal-wechat'
|
||||
import type {
|
||||
PersonalWechatRuntimeProgressEvent,
|
||||
PersonalWechatRuntimeStatus
|
||||
} from '../../../../shared/personal-wechat-runtime'
|
||||
import { Button, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../ui'
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
Switch
|
||||
} from '../ui'
|
||||
import {
|
||||
PersonalWechatChatComposer,
|
||||
type ChatMessage,
|
||||
type PersonalWechatComposerMode
|
||||
} from './PersonalWechatChatComposer'
|
||||
import { PersonalWechatSetupGuide } from './PersonalWechatSetupGuide'
|
||||
import { PersonalWechatVoiceDiagnosticDialog } from './PersonalWechatVoiceDiagnosticDialog'
|
||||
|
||||
type SelectedLocalFile = { path: string; name: string }
|
||||
|
||||
@@ -71,8 +81,15 @@ export function PersonalWechatSendDialog({
|
||||
const [runtimeBusy, setRuntimeBusy] = useState(false)
|
||||
const [sendBusy, setSendBusy] = useState(false)
|
||||
const [detectionAttempted, setDetectionAttempted] = useState(false)
|
||||
// Status can be reconstructed from a previous OneBot process/log. These
|
||||
// session gates ensure the user explicitly binds and detects after opening
|
||||
// the flow instead of inheriting stale readiness.
|
||||
const [sessionBound, setSessionBound] = useState(false)
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([])
|
||||
const [sendError, setSendError] = useState<string | null>(null)
|
||||
const [voiceDiagnostic, setVoiceDiagnostic] = useState<PersonalWechatVoiceDiagnostic | null>(null)
|
||||
const [voiceDiagnosticOpen, setVoiceDiagnosticOpen] = useState(false)
|
||||
const [keepOneBotProcess, setKeepOneBotProcess] = useState(false)
|
||||
const [composerStarted, setComposerStarted] = useState(Boolean(initialImage))
|
||||
const requestIdRef = useRef(0)
|
||||
const restoreFocusRef = useRef<HTMLElement | null>(null)
|
||||
@@ -81,7 +98,10 @@ export function PersonalWechatSendDialog({
|
||||
const targetId = contact.m_nsUsrName
|
||||
const isBusy = binding || runtimeBusy || sendBusy
|
||||
const setupReady = Boolean(
|
||||
senderStatus?.canSendText && (senderStatus?.canSendImage || senderStatus?.canSendVoice)
|
||||
sessionBound &&
|
||||
detectionAttempted &&
|
||||
senderStatus?.canSendText &&
|
||||
(senderStatus?.canSendImage || senderStatus?.canSendVoice)
|
||||
)
|
||||
|
||||
const refreshStatus = useCallback(async (): Promise<void> => {
|
||||
@@ -97,6 +117,15 @@ export function PersonalWechatSendDialog({
|
||||
setRuntimeStatus(nextRuntime)
|
||||
setRuntimeProgress(nextRuntime?.state === 'downloading' ? nextRuntime : null)
|
||||
setSenderStatus(nextSender)
|
||||
setSessionBound(
|
||||
nextSender.state === 'online' ||
|
||||
Boolean(
|
||||
nextSender.wechatPid &&
|
||||
nextSender.boundWechatPid === nextSender.wechatPid &&
|
||||
nextSender.attachReady &&
|
||||
nextSender.baseAddressReady
|
||||
)
|
||||
)
|
||||
} catch (error) {
|
||||
if (requestId === requestIdRef.current) setSenderStatus(fallbackStatus(error))
|
||||
} finally {
|
||||
@@ -114,6 +143,34 @@ export function PersonalWechatSendDialog({
|
||||
return unsubscribe
|
||||
}, [refreshStatus])
|
||||
|
||||
useEffect(() => {
|
||||
let active = true
|
||||
const readKeepProcess = window.api.getPersonalWechatKeepOneBotProcess
|
||||
if (!readKeepProcess) return undefined
|
||||
void readKeepProcess().then((keep) => {
|
||||
if (active && typeof keep === 'boolean') setKeepOneBotProcess(keep)
|
||||
})
|
||||
return () => {
|
||||
active = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleKeepOneBotProcessChange = async (keep: boolean): Promise<void> => {
|
||||
const saveKeepProcess = window.api.setPersonalWechatKeepOneBotProcess
|
||||
if (!saveKeepProcess) {
|
||||
setSendError('请重启 TraceMemo 后再使用“保留 OneBot 进程”')
|
||||
return
|
||||
}
|
||||
setKeepOneBotProcess(keep)
|
||||
try {
|
||||
const saved = await saveKeepProcess(keep)
|
||||
if (typeof saved === 'boolean') setKeepOneBotProcess(saved)
|
||||
} catch (error) {
|
||||
setKeepOneBotProcess(!keep)
|
||||
setSendError(error instanceof Error ? error.message : String(error))
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownloadRuntime = async (): Promise<void> => {
|
||||
if (runtimeBusy) return
|
||||
setRuntimeBusy(true)
|
||||
@@ -138,6 +195,15 @@ export function PersonalWechatSendDialog({
|
||||
try {
|
||||
const nextStatus = await window.api.rebindPersonalWechatSender()
|
||||
setSenderStatus(nextStatus)
|
||||
setSessionBound(
|
||||
nextStatus.state === 'online' ||
|
||||
Boolean(
|
||||
nextStatus.wechatPid &&
|
||||
nextStatus.boundWechatPid === nextStatus.wechatPid &&
|
||||
nextStatus.attachReady &&
|
||||
nextStatus.baseAddressReady
|
||||
)
|
||||
)
|
||||
if (nextStatus.state !== 'online' && nextStatus.message) setSendError(nextStatus.message)
|
||||
} catch (error) {
|
||||
setSendError(error instanceof Error ? error.message : String(error))
|
||||
@@ -185,103 +251,140 @@ export function PersonalWechatSendDialog({
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpenVoiceDiagnostic = async (): Promise<void> => {
|
||||
const diagnostic = await window.api.getPersonalWechatVoiceDiagnostic()
|
||||
setVoiceDiagnostic(diagnostic)
|
||||
setVoiceDiagnosticOpen(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && handleClose()}>
|
||||
<DialogContent
|
||||
className="personal-wechat-send-dialog max-h-[calc(100vh-2rem)] max-w-[720px] gap-0 overflow-y-auto p-0"
|
||||
onOpenAutoFocus={() => {
|
||||
restoreFocusRef.current =
|
||||
document.activeElement instanceof HTMLElement ? document.activeElement : null
|
||||
}}
|
||||
onEscapeKeyDown={(event) => isBusy && event.preventDefault()}
|
||||
onPointerDownOutside={(event) => isBusy && event.preventDefault()}
|
||||
>
|
||||
<DialogHeader className="personal-wechat-chat-header">
|
||||
<div className="personal-wechat-chat-avatar" aria-hidden>
|
||||
{displayName.slice(0, 1)}
|
||||
</div>
|
||||
<div className="personal-wechat-chat-heading">
|
||||
<DialogTitle>{displayName}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{isGroupChat ? '群聊' : '联系人'} · {setupReady ? '微信已连接' : '配置微信消息发送'}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
<span
|
||||
className={`personal-wechat-connection-dot ${setupReady ? 'is-online' : ''}`}
|
||||
aria-label={setupReady ? '微信已连接' : '微信尚未配置'}
|
||||
/>
|
||||
</DialogHeader>
|
||||
<>
|
||||
<Dialog open onOpenChange={(open) => !open && handleClose()}>
|
||||
<DialogContent
|
||||
className="personal-wechat-send-dialog max-h-[calc(100vh-2rem)] max-w-[720px] gap-0 overflow-y-auto p-0"
|
||||
onOpenAutoFocus={() => {
|
||||
restoreFocusRef.current =
|
||||
document.activeElement instanceof HTMLElement ? document.activeElement : null
|
||||
}}
|
||||
onEscapeKeyDown={(event) => isBusy && event.preventDefault()}
|
||||
onPointerDownOutside={(event) => isBusy && event.preventDefault()}
|
||||
>
|
||||
<DialogHeader className="personal-wechat-chat-header">
|
||||
<div className="personal-wechat-chat-avatar" aria-hidden>
|
||||
{displayName.slice(0, 1)}
|
||||
</div>
|
||||
<div className="personal-wechat-chat-heading">
|
||||
<DialogTitle>{displayName}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{isGroupChat ? '群聊' : '联系人'} · {setupReady ? '微信已连接' : '配置微信消息发送'}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
<span
|
||||
className={`personal-wechat-connection-dot ${setupReady ? 'is-online' : ''}`}
|
||||
aria-label={setupReady ? '微信已连接' : '微信尚未配置'}
|
||||
/>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="personal-wechat-chat-body">
|
||||
<div className="personal-wechat-chat-body">
|
||||
{setupReady && composerStarted && (
|
||||
<div className="personal-wechat-message-list" aria-label="消息列表">
|
||||
{messages.length === 0 ? (
|
||||
<div className="personal-wechat-empty-message">还没有发送消息。</div>
|
||||
) : (
|
||||
messages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`personal-wechat-message-bubble ${message.outgoing ? 'is-outgoing' : ''}`}
|
||||
>
|
||||
<span className="personal-wechat-message-kind">
|
||||
{message.type === 'text'
|
||||
? '文字'
|
||||
: message.type === 'image'
|
||||
? '图片'
|
||||
: '语音'}
|
||||
</span>
|
||||
<span>{message.text || message.fileName}</span>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(!setupReady || !composerStarted) && senderStatus && (
|
||||
<PersonalWechatSetupGuide
|
||||
runtimeStatus={runtimeStatus}
|
||||
senderStatus={senderStatus}
|
||||
runtimeProgress={runtimeProgress}
|
||||
runtimeBusy={runtimeBusy}
|
||||
binding={binding}
|
||||
detecting={detecting}
|
||||
sessionBound={sessionBound}
|
||||
onDownloadRuntime={() => void handleDownloadRuntime()}
|
||||
onBind={() => void handleBind()}
|
||||
detectionAttempted={detectionAttempted}
|
||||
onDetect={() => void handleDetect()}
|
||||
onStartSending={() => setComposerStarted(true)}
|
||||
onOpenTextToSpeechSettings={handleOpenSettings}
|
||||
/>
|
||||
)}
|
||||
|
||||
{setupReady && composerStarted && (
|
||||
<PersonalWechatChatComposer
|
||||
status={senderStatus!}
|
||||
targetId={targetId}
|
||||
isGroupChat={isGroupChat}
|
||||
initialMode={initialMode}
|
||||
initialImage={initialImage}
|
||||
onOpenTextToSpeechSettings={handleOpenSettings}
|
||||
onCancel={handleClose}
|
||||
onSend={handleSend}
|
||||
onMessage={(message) => setMessages((current) => [...current, message])}
|
||||
busy={sendBusy}
|
||||
/>
|
||||
)}
|
||||
{sendError && (
|
||||
<div className="personal-wechat-global-error" role="alert">
|
||||
{sendError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{setupReady && composerStarted && (
|
||||
<div className="personal-wechat-message-list" aria-label="消息列表">
|
||||
{messages.length === 0 ? (
|
||||
<div className="personal-wechat-empty-message">还没有发送消息。</div>
|
||||
) : (
|
||||
messages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`personal-wechat-message-bubble ${message.outgoing ? 'is-outgoing' : ''}`}
|
||||
>
|
||||
<span className="personal-wechat-message-kind">
|
||||
{message.type === 'text'
|
||||
? '文字'
|
||||
: message.type === 'image'
|
||||
? '图片'
|
||||
: '语音'}
|
||||
</span>
|
||||
<span>{message.text || message.fileName}</span>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
<div className="personal-wechat-chat-footer flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span>保留 OneBot 进程</span>
|
||||
<Switch
|
||||
checked={keepOneBotProcess}
|
||||
onCheckedChange={(checked) => void handleKeepOneBotProcessChange(checked)}
|
||||
aria-label="保留 OneBot 进程"
|
||||
/>
|
||||
</div>
|
||||
<Button variant="link" size="sm" onClick={() => void handleOpenVoiceDiagnostic()}>
|
||||
语音发送诊断
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(!setupReady || !composerStarted) && senderStatus && (
|
||||
<PersonalWechatSetupGuide
|
||||
runtimeStatus={runtimeStatus}
|
||||
senderStatus={senderStatus}
|
||||
runtimeProgress={runtimeProgress}
|
||||
runtimeBusy={runtimeBusy}
|
||||
binding={binding}
|
||||
detecting={detecting}
|
||||
onDownloadRuntime={() => void handleDownloadRuntime()}
|
||||
onBind={() => void handleBind()}
|
||||
detectionAttempted={detectionAttempted}
|
||||
onDetect={() => void handleDetect()}
|
||||
onStartSending={() => setComposerStarted(true)}
|
||||
onOpenTextToSpeechSettings={handleOpenSettings}
|
||||
/>
|
||||
)}
|
||||
|
||||
{setupReady && composerStarted && (
|
||||
<PersonalWechatChatComposer
|
||||
status={senderStatus!}
|
||||
targetId={targetId}
|
||||
isGroupChat={isGroupChat}
|
||||
initialMode={initialMode}
|
||||
initialImage={initialImage}
|
||||
onOpenTextToSpeechSettings={handleOpenSettings}
|
||||
onCancel={handleClose}
|
||||
onSend={handleSend}
|
||||
onMessage={(message) => setMessages((current) => [...current, message])}
|
||||
busy={sendBusy}
|
||||
/>
|
||||
)}
|
||||
{sendError && (
|
||||
<div className="personal-wechat-global-error" role="alert">
|
||||
{sendError}
|
||||
{(!setupReady || !composerStarted) && (
|
||||
<div className="personal-wechat-chat-footer flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span>保留 OneBot 进程</span>
|
||||
<Switch
|
||||
checked={keepOneBotProcess}
|
||||
onCheckedChange={(checked) => void handleKeepOneBotProcessChange(checked)}
|
||||
aria-label="保留 OneBot 进程"
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" onClick={handleClose} disabled={isBusy}>
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{(!setupReady || !composerStarted) && (
|
||||
<div className="personal-wechat-chat-footer">
|
||||
<Button variant="outline" onClick={handleClose} disabled={isBusy}>
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<PersonalWechatVoiceDiagnosticDialog
|
||||
open={voiceDiagnosticOpen}
|
||||
diagnostic={voiceDiagnostic}
|
||||
onOpenChange={setVoiceDiagnosticOpen}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ interface PersonalWechatSetupGuideProps {
|
||||
binding: boolean
|
||||
detecting: boolean
|
||||
detectionAttempted: boolean
|
||||
sessionBound: boolean
|
||||
onDownloadRuntime: () => void
|
||||
onBind: () => void
|
||||
onDetect: () => void
|
||||
@@ -61,6 +62,7 @@ export function PersonalWechatSetupGuide({
|
||||
binding,
|
||||
detecting,
|
||||
detectionAttempted,
|
||||
sessionBound,
|
||||
onDownloadRuntime,
|
||||
onBind,
|
||||
onDetect,
|
||||
@@ -70,12 +72,14 @@ export function PersonalWechatSetupGuide({
|
||||
const [showSupportedVersions, setShowSupportedVersions] = useState(false)
|
||||
const runtimeReady = runtimeStatus?.state === 'ready' || senderStatus?.runtimeReady === true
|
||||
const runtimeDownloading = runtimeBusy || runtimeStatus?.state === 'downloading'
|
||||
const connected = senderStatus ? isWechatBound(senderStatus) : false
|
||||
const connected = sessionBound && (senderStatus ? isWechatBound(senderStatus) : false)
|
||||
const canSendText = Boolean(senderStatus?.canSendText)
|
||||
const canSendImage = Boolean(senderStatus?.canSendImage)
|
||||
const canSendVoice = Boolean(senderStatus?.canSendVoice)
|
||||
const mediaReady = canSendImage || canSendVoice
|
||||
const verificationComplete = canSendText && mediaReady
|
||||
const detectedTextReady = detectionAttempted && canSendText
|
||||
const detectedMediaReady = detectionAttempted && mediaReady
|
||||
const verificationComplete = connected && detectedTextReady && detectedMediaReady
|
||||
const allReady = verificationComplete
|
||||
const progress = runtimeProgress || runtimeStatus
|
||||
const progressPercent = Math.max(0, Math.min(100, Math.round((progress?.progress || 0) * 100)))
|
||||
@@ -160,7 +164,8 @@ export function PersonalWechatSetupGuide({
|
||||
<p className="personal-wechat-step-warning" role="note">
|
||||
绑定微信可能导致当前微信异常闪退,这是正常现象。若微信退出,请重新启动微信后,再回到这里重新检测/绑定。
|
||||
<br />
|
||||
微信总是自动更新?请在微信左下角打开“设置 → 通用”,取消勾选“有更新时自动升级微信”,否则版本变化后可能无法绑定。
|
||||
微信总是自动更新?请在微信左下角打开“设置 →
|
||||
通用”,取消勾选“有更新时自动升级微信”,否则版本变化后可能无法绑定。
|
||||
</p>
|
||||
)}
|
||||
<Button
|
||||
@@ -188,7 +193,7 @@ export function PersonalWechatSetupGuide({
|
||||
<p className="personal-wechat-step-hint">
|
||||
TraceMemo 需要通过你主动发送的消息初始化微信消息能力。完成后点击“重新检测”。
|
||||
</p>
|
||||
{!verificationComplete && (
|
||||
{connected && !verificationComplete && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
@@ -214,8 +219,8 @@ export function PersonalWechatSetupGuide({
|
||||
<strong>能力检测</strong>
|
||||
<div className="personal-wechat-capabilities" aria-label="微信消息能力">
|
||||
{[
|
||||
['文字消息', canSendText],
|
||||
['图片和语音消息', mediaReady]
|
||||
['文字消息', detectedTextReady],
|
||||
['图片和语音消息', detectedMediaReady]
|
||||
].map(([label, ready]) => (
|
||||
<span key={String(label)} className={ready ? 'is-ready' : ''}>
|
||||
<b aria-hidden>{ready ? '✓' : '−'}</b>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import type { PersonalWechatVoiceDiagnostic } from '../../../../shared/personal-wechat'
|
||||
import { Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '../ui'
|
||||
|
||||
interface PersonalWechatVoiceDiagnosticDialogProps {
|
||||
open: boolean
|
||||
diagnostic: PersonalWechatVoiceDiagnostic | null
|
||||
onOpenChange: (open: boolean) => void
|
||||
}
|
||||
|
||||
export function PersonalWechatVoiceDiagnosticDialog({
|
||||
open,
|
||||
diagnostic,
|
||||
onOpenChange
|
||||
}: PersonalWechatVoiceDiagnosticDialogProps): React.ReactElement {
|
||||
const [copied, setCopied] = useState(false)
|
||||
const json = useMemo(() => (diagnostic ? JSON.stringify(diagnostic, null, 2) : ''), [diagnostic])
|
||||
|
||||
const copyDiagnostic = async (): Promise<void> => {
|
||||
if (!json) return
|
||||
const result = await window.api.copyText(json)
|
||||
setCopied(result.success)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(nextOpen) => {
|
||||
if (nextOpen) setCopied(false)
|
||||
onOpenChange(nextOpen)
|
||||
}}
|
||||
>
|
||||
<DialogContent className="max-w-[560px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>语音发送诊断</DialogTitle>
|
||||
</DialogHeader>
|
||||
{json ? (
|
||||
<pre className="max-h-[min(50vh,420px)] overflow-auto rounded-md border border-border-subtle bg-muted/40 p-3 text-xs leading-5 text-foreground">
|
||||
{json}
|
||||
</pre>
|
||||
) : (
|
||||
<p className="py-8 text-center text-sm text-muted-foreground">暂无诊断信息</p>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
关闭
|
||||
</Button>
|
||||
<Button onClick={() => void copyDiagnostic()} disabled={!json}>
|
||||
{copied ? '已复制' : '复制诊断 JSON'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user