1127 lines
28 KiB
Vue
1127 lines
28 KiB
Vue
<template>
|
|
<div class="th-page debug-eml-view">
|
|
<header class="debug-eml-hero">
|
|
<div class="debug-eml-hero__copy">
|
|
<h1>{{ t('debugEml.title') }}</h1>
|
|
<p>{{ t('debugEml.subtitle') }}</p>
|
|
</div>
|
|
|
|
<dl class="debug-eml-status-strip">
|
|
<div>
|
|
<dt>{{ t('debugEml.status') }}</dt>
|
|
<dd>{{ statusLabel }}</dd>
|
|
</div>
|
|
<div>
|
|
<dt>{{ t('debugEml.debugRunId') }}</dt>
|
|
<dd class="th-code">
|
|
{{ currentDebugRunId ?? '-' }}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt>{{ t('debugEml.backendStatus') }}</dt>
|
|
<dd class="th-code">
|
|
{{ currentBackendStatus ?? '-' }}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
</header>
|
|
|
|
<div class="debug-eml-workspace">
|
|
<aside class="debug-eml-panel">
|
|
<form
|
|
class="debug-eml-card debug-eml-form"
|
|
@submit.prevent="submitUpload"
|
|
>
|
|
<div class="th-section-header">
|
|
<h2 class="th-section-title">
|
|
<i
|
|
class="pi pi-upload"
|
|
aria-hidden="true"
|
|
/>
|
|
{{ t('debugEml.uploadConfig') }}
|
|
</h2>
|
|
</div>
|
|
|
|
<div class="debug-eml-grid">
|
|
<label>
|
|
<span>{{ t('debugEml.hotelId') }}</span>
|
|
<input
|
|
v-model="hotelId"
|
|
name="hotel_id"
|
|
type="text"
|
|
:disabled="busy"
|
|
autocomplete="off"
|
|
>
|
|
</label>
|
|
<label>
|
|
<span>{{ t('debugEml.debugUploadKey') }}</span>
|
|
<input
|
|
v-model="debugUploadKey"
|
|
name="debug_upload_key"
|
|
type="password"
|
|
:disabled="busy"
|
|
autocomplete="off"
|
|
>
|
|
</label>
|
|
<label>
|
|
<span>{{ t('debugEml.runLabel') }}</span>
|
|
<input
|
|
v-model="runLabel"
|
|
name="run_label"
|
|
type="text"
|
|
:disabled="busy"
|
|
autocomplete="off"
|
|
>
|
|
</label>
|
|
<label>
|
|
<span>{{ t('debugEml.emlFile') }}</span>
|
|
<input
|
|
type="file"
|
|
accept=".eml,message/rfc822"
|
|
:disabled="busy"
|
|
@change="onFileChange"
|
|
>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="debug-eml-actions">
|
|
<button
|
|
type="submit"
|
|
class="primary-button"
|
|
:disabled="submitDisabled"
|
|
>
|
|
<i
|
|
class="pi pi-send"
|
|
aria-hidden="true"
|
|
/>
|
|
{{ busy ? t('debugEml.submitting') : t('debugEml.submit') }}
|
|
</button>
|
|
<span
|
|
v-if="selectedFile"
|
|
class="debug-eml-selected-file"
|
|
>
|
|
<i
|
|
class="pi pi-file"
|
|
aria-hidden="true"
|
|
/>
|
|
{{ selectedFile.name }}
|
|
</span>
|
|
</div>
|
|
</form>
|
|
|
|
<section class="debug-eml-card debug-eml-status">
|
|
<div class="th-section-header">
|
|
<h2 class="th-section-title">
|
|
<i
|
|
class="pi pi-info-circle"
|
|
aria-hidden="true"
|
|
/>
|
|
{{ t('debugEml.executionStatus') }}
|
|
</h2>
|
|
</div>
|
|
<dl class="debug-eml-kv">
|
|
<div>
|
|
<dt>{{ t('debugEml.status') }}</dt>
|
|
<dd>{{ statusLabel }}</dd>
|
|
</div>
|
|
<div>
|
|
<dt>{{ t('debugEml.debugRunId') }}</dt>
|
|
<dd class="th-code">
|
|
{{ currentDebugRunId ?? '-' }}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt>{{ t('debugEml.backendStatus') }}</dt>
|
|
<dd class="th-code">
|
|
{{ currentBackendStatus ?? '-' }}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
<p
|
|
v-if="busy"
|
|
class="debug-eml-callout"
|
|
>
|
|
{{ t('debugEml.waitingSuperAgent') }}
|
|
</p>
|
|
<div
|
|
v-if="errorCode"
|
|
class="empty-state empty-state--error"
|
|
>
|
|
<strong>{{ errorCode }}</strong>
|
|
<span>{{ errorDisplayMessage }}</span>
|
|
<small v-if="errorMessage">
|
|
{{ t('debugEml.backendMessage') }}: {{ errorMessage }}
|
|
</small>
|
|
</div>
|
|
</section>
|
|
</aside>
|
|
|
|
<main class="debug-eml-results">
|
|
<section
|
|
v-if="stageEvents.length || busy"
|
|
class="debug-eml-card debug-eml-stream-section"
|
|
>
|
|
<div class="th-section-header">
|
|
<h2 class="th-section-title">
|
|
<i
|
|
class="pi pi-list-check"
|
|
aria-hidden="true"
|
|
/>
|
|
{{ t('debugEml.systemStages') }}
|
|
</h2>
|
|
</div>
|
|
<ol
|
|
v-if="stageEvents.length"
|
|
class="debug-eml-event-list debug-eml-event-list--stages"
|
|
>
|
|
<li
|
|
v-for="(stage, index) in stageEvents"
|
|
:key="`${stage.status ?? 'stage'}-${index}`"
|
|
>
|
|
<strong>{{ stage.status ?? '-' }}</strong>
|
|
<span>{{ stage.safe_summary ?? '-' }}</span>
|
|
<small class="th-code">{{ stage.debug_run_id ?? '-' }}</small>
|
|
</li>
|
|
</ol>
|
|
<div
|
|
v-else
|
|
class="empty-state empty-state--compact"
|
|
>
|
|
{{ t('debugEml.waitingSuperAgent') }}
|
|
</div>
|
|
</section>
|
|
|
|
<section
|
|
v-if="visibleTraceEvents.length || busy"
|
|
class="debug-eml-card debug-eml-stream-section"
|
|
>
|
|
<div class="th-section-header">
|
|
<h2 class="th-section-title">
|
|
<i
|
|
class="pi pi-wave-pulse"
|
|
aria-hidden="true"
|
|
/>
|
|
{{ t('debugEml.superAgentTrace') }}
|
|
</h2>
|
|
</div>
|
|
<ol
|
|
v-if="visibleTraceEvents.length"
|
|
class="debug-eml-event-list debug-eml-event-list--trace"
|
|
>
|
|
<li
|
|
v-for="(trace, index) in visibleTraceEvents"
|
|
:key="`${trace.event ?? 'trace'}-${trace.tool_call_id ?? index}`"
|
|
>
|
|
<strong>{{ trace.event ?? '-' }}</strong>
|
|
<span v-if="trace.text">{{ trace.text }}</span>
|
|
<dl class="debug-eml-trace-fields">
|
|
<div v-if="trace.tool_name">
|
|
<dt>{{ t('debugEml.traceToolName') }}</dt>
|
|
<dd class="th-code">
|
|
{{ trace.tool_name }}
|
|
</dd>
|
|
</div>
|
|
<div v-if="trace.input_summary">
|
|
<dt>{{ t('debugEml.traceInputSummary') }}</dt>
|
|
<dd class="th-code">
|
|
{{ trace.input_summary }}
|
|
</dd>
|
|
</div>
|
|
<div v-if="trace.output_summary">
|
|
<dt>{{ t('debugEml.traceOutputSummary') }}</dt>
|
|
<dd class="th-code">
|
|
{{ trace.output_summary }}
|
|
</dd>
|
|
</div>
|
|
<div v-if="trace.status">
|
|
<dt>{{ t('debugEml.traceStatus') }}</dt>
|
|
<dd class="th-code">
|
|
{{ trace.status }}
|
|
</dd>
|
|
</div>
|
|
<div v-if="trace.ts">
|
|
<dt>{{ t('debugEml.traceTime') }}</dt>
|
|
<dd class="th-code">
|
|
{{ formatReservationDateTime(trace.ts) }}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
</li>
|
|
</ol>
|
|
<div
|
|
v-else
|
|
class="empty-state empty-state--compact"
|
|
>
|
|
{{ t('common.noData') }}
|
|
</div>
|
|
</section>
|
|
|
|
<template v-if="result">
|
|
<section class="debug-eml-card">
|
|
<div class="th-section-header">
|
|
<h2 class="th-section-title">
|
|
<i
|
|
class="pi pi-sitemap"
|
|
aria-hidden="true"
|
|
/>
|
|
{{ t('debugEml.sourceTrace') }}
|
|
</h2>
|
|
</div>
|
|
<dl class="debug-eml-kv debug-eml-kv--grid">
|
|
<div>
|
|
<dt>{{ t('debugEml.sourceMessageId') }}</dt>
|
|
<dd class="th-code">
|
|
{{ result.source_message_id ?? '-' }}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt>{{ t('debugEml.sourceProvider') }}</dt>
|
|
<dd>
|
|
{{ result.source_provider ?? '-' }}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt>{{ t('debugEml.externalMessageId') }}</dt>
|
|
<dd class="th-code">
|
|
{{ result.external_message_id ?? '-' }}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt>{{ t('debugEml.externalConversationId') }}</dt>
|
|
<dd class="th-code">
|
|
{{ result.external_conversation_id ?? '-' }}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
</section>
|
|
|
|
<section class="debug-eml-card debug-eml-preview-section">
|
|
<div class="th-section-header">
|
|
<h2 class="th-section-title">
|
|
<i
|
|
class="pi pi-envelope"
|
|
aria-hidden="true"
|
|
/>
|
|
{{ t('debugEml.mailPreview') }}
|
|
</h2>
|
|
</div>
|
|
<!-- eslint-disable vue/no-v-html -->
|
|
<div
|
|
v-if="renderableHtmlBody"
|
|
class="debug-eml-html-preview"
|
|
v-html="renderableHtmlBody"
|
|
/>
|
|
<!-- eslint-enable vue/no-v-html -->
|
|
<div
|
|
v-else
|
|
class="empty-state"
|
|
>
|
|
{{ t('debugEml.noSafeHtml') }}
|
|
</div>
|
|
|
|
<div class="debug-eml-media-list">
|
|
<h3>{{ t('debugEml.uploadedMedia') }}</h3>
|
|
<ul v-if="result.uploaded_media.length">
|
|
<li
|
|
v-for="(media, index) in result.uploaded_media"
|
|
:key="media.external_media_id ?? media.object_key ?? index"
|
|
>
|
|
<span>{{ media.media_type ?? '-' }}</span>
|
|
<a
|
|
v-if="safeExternalUrl(media.external_url)"
|
|
:href="safeExternalUrl(media.external_url)"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
{{ media.file_name ?? media.external_media_id ?? t('debugEml.unnamedMedia') }}
|
|
</a>
|
|
<strong v-else>
|
|
{{ media.file_name ?? media.external_media_id ?? t('debugEml.unnamedMedia') }}
|
|
</strong>
|
|
<small>
|
|
{{ media.content_type ?? '-' }} / {{ formatMediaSize(media.size_bytes) }}
|
|
</small>
|
|
</li>
|
|
</ul>
|
|
<p
|
|
v-else
|
|
class="th-muted"
|
|
>
|
|
{{ t('common.noData') }}
|
|
</p>
|
|
</div>
|
|
|
|
<details class="debug-eml-raw-html">
|
|
<summary>{{ t('debugEml.rawHtml') }}</summary>
|
|
<pre>{{ result.html_body_with_oss_urls ?? '-' }}</pre>
|
|
</details>
|
|
</section>
|
|
|
|
<section class="debug-eml-card debug-eml-result-section">
|
|
<div class="th-section-header">
|
|
<h2 class="th-section-title">
|
|
<i
|
|
class="pi pi-bolt"
|
|
aria-hidden="true"
|
|
/>
|
|
{{ t('debugEml.superAgentResult') }}
|
|
</h2>
|
|
</div>
|
|
<dl class="debug-eml-kv debug-eml-kv--grid">
|
|
<div>
|
|
<dt>{{ t('debugEml.superAgentSessionId') }}</dt>
|
|
<dd class="th-code">
|
|
{{ result.superagent_session_id ?? '-' }}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt>{{ t('debugEml.superAgentRunId') }}</dt>
|
|
<dd class="th-code">
|
|
{{ result.superagent_run_id ?? '-' }}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
<div class="debug-eml-two-column">
|
|
<section>
|
|
<h3>{{ t('debugEml.parsedJson') }}</h3>
|
|
<pre>{{ formatJson(result.superagent_parsed_json) }}</pre>
|
|
</section>
|
|
<section>
|
|
<h3>{{ t('debugEml.rawAnswer') }}</h3>
|
|
<pre>{{ result.superagent_raw_answer ?? '-' }}</pre>
|
|
</section>
|
|
</div>
|
|
<div
|
|
v-if="result.warnings.length"
|
|
class="debug-eml-warning-list"
|
|
>
|
|
<h3>{{ t('debugEml.warnings') }}</h3>
|
|
<ul>
|
|
<li
|
|
v-for="warning in result.warnings"
|
|
:key="warning"
|
|
>
|
|
{{ warning }}
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="debug-eml-card">
|
|
<div class="th-section-header">
|
|
<h2 class="th-section-title">
|
|
<i
|
|
class="pi pi-code"
|
|
aria-hidden="true"
|
|
/>
|
|
{{ t('debugEml.debugPayload') }}
|
|
</h2>
|
|
</div>
|
|
<pre>{{ formatJson(result.agentbus_like_payload) }}</pre>
|
|
</section>
|
|
</template>
|
|
|
|
<section
|
|
v-else
|
|
class="debug-eml-card debug-eml-empty-panel"
|
|
>
|
|
<div class="empty-state">
|
|
<strong>{{ t('debugEml.executionStatus') }}</strong>
|
|
<span>{{ busy ? t('debugEml.waitingSuperAgent') : t('common.noData') }}</span>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
import { reservationHotelId } from '@/config/reservationConfig'
|
|
import { DebugEmlUploadError, uploadDebugEmlSuperAgentRunStream } from '@/services/debugEmlService'
|
|
import type {
|
|
DebugEmlErrorCode,
|
|
DebugEmlStageEvent,
|
|
DebugEmlSuperAgentRunResult,
|
|
DebugEmlSuperAgentTraceEvent,
|
|
} from '@/types/debugEml'
|
|
import { formatReservationDateTime } from '@/utils/reservationFormat'
|
|
|
|
type DebugEmlStatus = 'idle' | 'uploading' | 'succeeded' | 'failed'
|
|
|
|
const { t } = useI18n()
|
|
|
|
const hotelId = ref(reservationHotelId ?? '')
|
|
const debugUploadKey = ref('')
|
|
const runLabel = ref('')
|
|
const selectedFile = ref<File | null>(null)
|
|
const status = ref<DebugEmlStatus>('idle')
|
|
const result = ref<DebugEmlSuperAgentRunResult | null>(null)
|
|
const stageEvents = ref<DebugEmlStageEvent[]>([])
|
|
const traceEvents = ref<DebugEmlSuperAgentTraceEvent[]>([])
|
|
const errorCode = ref<DebugEmlErrorCode | ''>('')
|
|
const errorMessage = ref('')
|
|
|
|
const busy = computed(() => status.value === 'uploading')
|
|
const submitDisabled = computed(
|
|
() => busy.value || !debugUploadKey.value.trim() || !selectedFile.value,
|
|
)
|
|
const statusLabel = computed(() => t(`debugEml.statuses.${status.value}`))
|
|
const latestStageEvent = computed(() => stageEvents.value[stageEvents.value.length - 1] ?? null)
|
|
const currentDebugRunId = computed(
|
|
() => result.value?.debug_run_id || latestStageEvent.value?.debug_run_id || null,
|
|
)
|
|
const currentBackendStatus = computed(
|
|
() => result.value?.status || latestStageEvent.value?.status || null,
|
|
)
|
|
const visibleTraceEvents = computed(() =>
|
|
traceEvents.value.length ? traceEvents.value : result.value?.superagent_trace_events ?? [],
|
|
)
|
|
const errorDisplayMessage = computed(() => {
|
|
if (!errorCode.value) {
|
|
return ''
|
|
}
|
|
const errorKey = `debugEml.errors.${errorCode.value}`
|
|
const mappedMessage = t(errorKey)
|
|
return mappedMessage === errorKey ? errorMessage.value || t('debugEml.errors.UNKNOWN') : mappedMessage
|
|
})
|
|
const renderableHtmlBody = computed(() => {
|
|
if (!result.value) {
|
|
return ''
|
|
}
|
|
if (result.value.html_render_mode && result.value.html_render_mode !== 'SANITIZED_HTML') {
|
|
return ''
|
|
}
|
|
return result.value.html_body_sanitized ?? ''
|
|
})
|
|
|
|
function onFileChange(event: Event): void {
|
|
const input = event.target as HTMLInputElement
|
|
selectedFile.value = input.files?.[0] ?? null
|
|
}
|
|
|
|
async function submitUpload(): Promise<void> {
|
|
if (submitDisabled.value || !selectedFile.value) {
|
|
return
|
|
}
|
|
|
|
status.value = 'uploading'
|
|
errorCode.value = ''
|
|
errorMessage.value = ''
|
|
result.value = null
|
|
stageEvents.value = []
|
|
traceEvents.value = []
|
|
|
|
try {
|
|
const finalResult = await uploadDebugEmlSuperAgentRunStream(
|
|
{
|
|
hotelId: hotelId.value,
|
|
debugUploadKey: debugUploadKey.value.trim(),
|
|
runLabel: runLabel.value,
|
|
file: selectedFile.value,
|
|
},
|
|
{
|
|
onStage: (stage) => {
|
|
stageEvents.value = [...stageEvents.value, stage]
|
|
},
|
|
onTrace: (trace) => {
|
|
traceEvents.value = [...traceEvents.value, trace]
|
|
},
|
|
onResult: (streamResult) => {
|
|
result.value = streamResult
|
|
},
|
|
},
|
|
)
|
|
result.value = finalResult
|
|
if (!traceEvents.value.length && finalResult.superagent_trace_events.length) {
|
|
traceEvents.value = finalResult.superagent_trace_events
|
|
}
|
|
status.value = 'succeeded'
|
|
} catch (error) {
|
|
status.value = 'failed'
|
|
if (error instanceof DebugEmlUploadError) {
|
|
errorCode.value = error.errorCode
|
|
errorMessage.value = error.message
|
|
return
|
|
}
|
|
errorCode.value = 'DEBUG_EML_RUN_FAILED'
|
|
errorMessage.value = error instanceof Error ? error.message : String(error)
|
|
}
|
|
}
|
|
|
|
function formatJson(value: unknown): string {
|
|
if (value === null || value === undefined) {
|
|
return '-'
|
|
}
|
|
return JSON.stringify(value, null, 2)
|
|
}
|
|
|
|
function safeExternalUrl(value: string | null): string | undefined {
|
|
if (!value) {
|
|
return undefined
|
|
}
|
|
try {
|
|
const url = new URL(value)
|
|
return url.protocol === 'http:' || url.protocol === 'https:' ? value : undefined
|
|
} catch {
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
function formatMediaSize(sizeBytes: number | null): string {
|
|
if (!sizeBytes) {
|
|
return '-'
|
|
}
|
|
if (sizeBytes < 1024) {
|
|
return `${sizeBytes} B`
|
|
}
|
|
if (sizeBytes < 1024 * 1024) {
|
|
return `${Math.round(sizeBytes / 1024)} KB`
|
|
}
|
|
return `${(sizeBytes / 1024 / 1024).toFixed(1)} MB`
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.debug-eml-view {
|
|
max-width: 1440px;
|
|
display: grid;
|
|
gap: 18px;
|
|
}
|
|
|
|
.debug-eml-hero {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) minmax(360px, 520px);
|
|
gap: 18px;
|
|
align-items: stretch;
|
|
border: 1px solid rgba(148, 163, 184, 0.24);
|
|
border-radius: 14px;
|
|
background:
|
|
linear-gradient(135deg, rgb(255 255 255 / 96%), rgb(248 250 252 / 92%)),
|
|
radial-gradient(circle at top left, rgba(37, 99, 235, 0.12), transparent 34%);
|
|
box-shadow: 0 20px 50px rgb(15 23 42 / 6%);
|
|
padding: 22px;
|
|
}
|
|
|
|
.debug-eml-hero__copy {
|
|
min-width: 0;
|
|
display: grid;
|
|
align-content: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.debug-eml-hero__copy h1,
|
|
.debug-eml-hero__copy p {
|
|
margin: 0;
|
|
}
|
|
|
|
.debug-eml-hero__copy h1 {
|
|
color: var(--th-color-slate-900);
|
|
font-size: 28px;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.debug-eml-hero__copy p {
|
|
max-width: 760px;
|
|
color: var(--th-color-slate-500);
|
|
font-size: 14px;
|
|
line-height: 1.7;
|
|
}
|
|
|
|
.debug-eml-status-strip {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: 10px;
|
|
margin: 0;
|
|
}
|
|
|
|
.debug-eml-status-strip div {
|
|
min-width: 0;
|
|
border: 1px solid rgba(148, 163, 184, 0.28);
|
|
border-radius: 10px;
|
|
background: rgb(255 255 255 / 82%);
|
|
padding: 14px;
|
|
}
|
|
|
|
.debug-eml-status-strip dt {
|
|
color: var(--th-color-slate-500);
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.debug-eml-status-strip dd {
|
|
margin: 7px 0 0;
|
|
color: var(--th-color-slate-900);
|
|
font-size: 13px;
|
|
font-weight: 800;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.debug-eml-workspace {
|
|
display: grid;
|
|
grid-template-columns: minmax(300px, 360px) minmax(0, 1fr);
|
|
gap: 18px;
|
|
align-items: start;
|
|
}
|
|
|
|
.debug-eml-panel {
|
|
position: sticky;
|
|
top: calc(var(--th-topbar-height) + 18px);
|
|
display: grid;
|
|
gap: 14px;
|
|
}
|
|
|
|
.debug-eml-results {
|
|
display: grid;
|
|
gap: 16px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.debug-eml-card {
|
|
display: grid;
|
|
gap: 16px;
|
|
min-width: 0;
|
|
border: 1px solid rgba(148, 163, 184, 0.25);
|
|
border-radius: 12px;
|
|
background: rgb(255 255 255 / 96%);
|
|
box-shadow: 0 12px 30px rgb(15 23 42 / 5%);
|
|
padding: 18px;
|
|
}
|
|
|
|
.debug-eml-form,
|
|
.debug-eml-status,
|
|
.debug-eml-preview-section,
|
|
.debug-eml-result-section {
|
|
display: grid;
|
|
gap: 16px;
|
|
}
|
|
|
|
.debug-eml-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: 13px;
|
|
}
|
|
|
|
.debug-eml-grid label {
|
|
display: grid;
|
|
gap: 7px;
|
|
color: var(--th-color-slate-500);
|
|
font-size: 12px;
|
|
font-weight: 800;
|
|
}
|
|
|
|
.debug-eml-grid input {
|
|
width: 100%;
|
|
min-height: 40px;
|
|
border: 1px solid rgba(148, 163, 184, 0.45);
|
|
border-radius: 8px;
|
|
background: var(--th-color-white);
|
|
color: var(--th-color-slate-900);
|
|
padding: 0 12px;
|
|
font: inherit;
|
|
transition:
|
|
border-color 0.15s ease,
|
|
box-shadow 0.15s ease;
|
|
}
|
|
|
|
.debug-eml-grid input[type='file'] {
|
|
min-height: auto;
|
|
padding: 9px 10px;
|
|
color: var(--th-color-slate-600);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.debug-eml-grid input:focus {
|
|
outline: none;
|
|
border-color: var(--th-color-blue-600);
|
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.12);
|
|
}
|
|
|
|
.debug-eml-grid input:disabled {
|
|
cursor: not-allowed;
|
|
background: var(--th-color-slate-50);
|
|
color: var(--th-color-slate-500);
|
|
}
|
|
|
|
.debug-eml-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.primary-button {
|
|
min-height: 42px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
border: 0;
|
|
border-radius: 9px;
|
|
background: var(--th-color-blue-600);
|
|
color: var(--th-color-white);
|
|
padding: 0 18px;
|
|
font-weight: 800;
|
|
cursor: pointer;
|
|
box-shadow: 0 12px 22px rgba(37, 99, 235, 0.22);
|
|
transition:
|
|
transform 0.15s ease,
|
|
box-shadow 0.15s ease,
|
|
opacity 0.15s ease;
|
|
}
|
|
|
|
.primary-button:not(:disabled):hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 16px 28px rgba(37, 99, 235, 0.25);
|
|
}
|
|
|
|
.primary-button:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.55;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.debug-eml-selected-file {
|
|
min-width: 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 7px;
|
|
border: 1px solid rgba(148, 163, 184, 0.3);
|
|
border-radius: 999px;
|
|
background: var(--th-color-slate-50);
|
|
color: var(--th-color-slate-600);
|
|
padding: 6px 10px;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.empty-state {
|
|
min-height: 120px;
|
|
display: grid;
|
|
place-items: center;
|
|
gap: 8px;
|
|
color: var(--th-color-slate-500);
|
|
font-size: 13px;
|
|
padding: 24px;
|
|
text-align: center;
|
|
border: 1px dashed rgba(148, 163, 184, 0.38);
|
|
border-radius: 10px;
|
|
background: var(--th-color-slate-50);
|
|
}
|
|
|
|
.empty-state strong {
|
|
color: var(--th-color-slate-900);
|
|
}
|
|
|
|
.empty-state--compact {
|
|
min-height: 84px;
|
|
padding: 16px;
|
|
}
|
|
|
|
.empty-state--error {
|
|
place-items: start;
|
|
color: var(--th-color-danger);
|
|
text-align: left;
|
|
background: rgba(220, 38, 38, 0.06);
|
|
border-color: rgba(220, 38, 38, 0.22);
|
|
}
|
|
|
|
.debug-eml-kv {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: 10px;
|
|
margin: 0;
|
|
}
|
|
|
|
.debug-eml-kv--grid {
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
}
|
|
|
|
.debug-eml-kv div {
|
|
min-width: 0;
|
|
border: 1px solid rgba(148, 163, 184, 0.25);
|
|
border-radius: 10px;
|
|
padding: 12px;
|
|
background: linear-gradient(180deg, var(--th-color-white), var(--th-color-slate-50));
|
|
}
|
|
|
|
.debug-eml-kv dt {
|
|
color: var(--th-color-slate-500);
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.debug-eml-kv dd {
|
|
margin: 5px 0 0;
|
|
color: var(--th-color-slate-900);
|
|
font-size: 13px;
|
|
font-weight: 800;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.debug-eml-callout {
|
|
margin: 0;
|
|
border: 1px solid rgba(37, 99, 235, 0.16);
|
|
border-radius: 10px;
|
|
background: rgba(37, 99, 235, 0.08);
|
|
color: var(--th-color-blue-600);
|
|
padding: 12px;
|
|
font-size: 13px;
|
|
font-weight: 800;
|
|
}
|
|
|
|
.debug-eml-stream-section {
|
|
gap: 14px;
|
|
}
|
|
|
|
.debug-eml-event-list {
|
|
display: grid;
|
|
gap: 10px;
|
|
margin: 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
.debug-eml-event-list li {
|
|
min-width: 0;
|
|
display: grid;
|
|
gap: 8px;
|
|
border: 1px solid rgba(148, 163, 184, 0.25);
|
|
border-radius: 10px;
|
|
background: var(--th-color-slate-50);
|
|
padding: 12px;
|
|
}
|
|
|
|
.debug-eml-event-list strong {
|
|
color: var(--th-color-slate-900);
|
|
font-size: 13px;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.debug-eml-event-list span,
|
|
.debug-eml-event-list small {
|
|
color: var(--th-color-slate-600);
|
|
font-size: 12px;
|
|
line-height: 1.55;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.debug-eml-event-list--stages li {
|
|
grid-template-columns: minmax(150px, 210px) minmax(0, 1fr) minmax(100px, 160px);
|
|
align-items: start;
|
|
}
|
|
|
|
.debug-eml-event-list--trace li {
|
|
background: linear-gradient(180deg, var(--th-color-white), var(--th-color-slate-50));
|
|
}
|
|
|
|
.debug-eml-trace-fields {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
gap: 8px;
|
|
margin: 0;
|
|
}
|
|
|
|
.debug-eml-trace-fields div {
|
|
min-width: 0;
|
|
border-radius: 8px;
|
|
background: rgb(255 255 255 / 76%);
|
|
padding: 8px;
|
|
}
|
|
|
|
.debug-eml-trace-fields dt {
|
|
color: var(--th-color-slate-500);
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.debug-eml-trace-fields dd {
|
|
margin: 4px 0 0;
|
|
color: var(--th-color-slate-900);
|
|
font-size: 12px;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.debug-eml-html-preview {
|
|
max-height: 520px;
|
|
overflow: auto;
|
|
border: 1px solid rgba(148, 163, 184, 0.28);
|
|
border-radius: 10px;
|
|
background: var(--th-color-white);
|
|
padding: 18px;
|
|
color: var(--th-color-slate-900);
|
|
line-height: 1.65;
|
|
}
|
|
|
|
.debug-eml-media-list {
|
|
display: grid;
|
|
gap: 10px;
|
|
}
|
|
|
|
.debug-eml-media-list h3,
|
|
.debug-eml-two-column h3,
|
|
.debug-eml-warning-list h3 {
|
|
margin: 0;
|
|
color: var(--th-color-slate-900);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.debug-eml-media-list ul,
|
|
.debug-eml-warning-list ul {
|
|
display: grid;
|
|
gap: 8px;
|
|
margin: 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
.debug-eml-media-list li {
|
|
display: grid;
|
|
grid-template-columns: minmax(100px, 140px) minmax(0, 1fr) minmax(170px, 220px);
|
|
gap: 12px;
|
|
align-items: center;
|
|
border: 1px solid rgba(148, 163, 184, 0.25);
|
|
border-radius: 10px;
|
|
background: var(--th-color-slate-50);
|
|
padding: 10px 12px;
|
|
}
|
|
|
|
.debug-eml-media-list a,
|
|
.debug-eml-media-list strong {
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.debug-eml-media-list span,
|
|
.debug-eml-media-list small {
|
|
color: var(--th-color-slate-500);
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.debug-eml-raw-html {
|
|
border: 1px solid rgba(148, 163, 184, 0.28);
|
|
border-radius: 10px;
|
|
background: var(--th-color-slate-50);
|
|
padding: 12px;
|
|
}
|
|
|
|
.debug-eml-raw-html summary {
|
|
cursor: pointer;
|
|
color: var(--th-color-slate-700);
|
|
font-size: 13px;
|
|
font-weight: 800;
|
|
}
|
|
|
|
pre {
|
|
max-height: 420px;
|
|
overflow: auto;
|
|
margin: 0;
|
|
border: 1px solid rgba(148, 163, 184, 0.28);
|
|
border-radius: 10px;
|
|
background: #0f172a;
|
|
color: #dbeafe;
|
|
padding: 12px;
|
|
white-space: pre-wrap;
|
|
overflow-wrap: anywhere;
|
|
font-size: 12px;
|
|
line-height: 1.55;
|
|
}
|
|
|
|
.debug-eml-result-section pre,
|
|
.debug-eml-card > pre,
|
|
.debug-eml-raw-html pre {
|
|
background: #0f172a;
|
|
color: #dbeafe;
|
|
}
|
|
|
|
.debug-eml-two-column {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
gap: 14px;
|
|
}
|
|
|
|
.debug-eml-two-column section {
|
|
display: grid;
|
|
gap: 10px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.debug-eml-warning-list {
|
|
display: grid;
|
|
gap: 10px;
|
|
}
|
|
|
|
.debug-eml-warning-list li {
|
|
border-radius: 10px;
|
|
background: rgba(245, 158, 11, 0.12);
|
|
color: #92400e;
|
|
padding: 10px 12px;
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.debug-eml-empty-panel {
|
|
min-height: 360px;
|
|
align-content: center;
|
|
}
|
|
|
|
.th-section-title {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.th-section-title i {
|
|
color: var(--th-color-blue-600);
|
|
font-size: 14px;
|
|
}
|
|
|
|
@media (max-width: 1180px) {
|
|
.debug-eml-hero,
|
|
.debug-eml-workspace {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.debug-eml-panel {
|
|
position: static;
|
|
}
|
|
|
|
.debug-eml-grid {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
}
|
|
|
|
@media (max-width: 920px) {
|
|
.debug-eml-status-strip,
|
|
.debug-eml-kv,
|
|
.debug-eml-kv--grid,
|
|
.debug-eml-two-column,
|
|
.debug-eml-event-list--stages li,
|
|
.debug-eml-trace-fields,
|
|
.debug-eml-media-list li {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.debug-eml-hero {
|
|
padding: 18px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.debug-eml-hero__copy h1 {
|
|
font-size: 24px;
|
|
}
|
|
|
|
.debug-eml-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.debug-eml-card {
|
|
padding: 16px;
|
|
}
|
|
}
|
|
</style>
|