@@ -7,6 +7,8 @@ import Input from "@/components/ui/Input.vue";
77import { useActionLock } from " @/composables/useActionLock" ;
88import { useMagicNet } from " @/composables/useMagicNet" ;
99import { copyText } from " @/utils" ;
10+ import { sanitizeOutputText } from " ./outputDiagnostics" ;
11+ import { buildRuntimeLogInsight , formatRuntimeLogIssueReport , runtimeLogInsightTone } from " ./runtimeLogInsights" ;
1012
1113const { runCli, state, compactOutput } = useMagicNet ();
1214const { isRunning, withAction } = useActionLock ();
@@ -18,8 +20,10 @@ const output = ref("");
1820const copied = ref (false );
1921const issueCopied = ref (false );
2022const lastLabel = ref (" " );
23+ const loadedTarget = ref <" sing-box" | " mcp" >(" sing-box" );
2124const autoRefresh = ref (false );
2225let timer = 0 ;
26+ const issuePattern = / \b (warn| warning| fail| failed| error| fatal| panic| denied| timeout| not found)\b / i ;
2327
2428const commandPreview = computed (() => {
2529 const count = normalizedLines ();
@@ -32,16 +36,16 @@ const filteredLines = computed(() => logLines.value.filter((line) => {
3236 const matchesKeyword = ! keyword || lower .includes (keyword );
3337 const matchesLevel = level .value === " all"
3438 || (level .value === " warn" && / \b (warn| warning)\b / i .test (line ))
35- || (level .value === " error" && / \b (error| failed| fatal| panic)\b / i .test (line ));
39+ || (level .value === " error" && / \b (error| fail | failed| fatal| panic| denied | timeout | not found )\b / i .test (line ));
3640 return matchesKeyword && matchesLevel ;
3741}));
3842const warningCount = computed (() => logLines .value .filter ((line ) => / \b (warn| warning)\b / i .test (line )).length );
39- const errorCount = computed (() => logLines .value .filter ((line ) => / \b (error| failed| fatal| panic)\b / i .test (line )).length );
43+ const errorCount = computed (() => logLines .value .filter ((line ) => / \b (error| fail | failed| fatal| panic)\b / i .test (line )).length );
4044const visibleOutput = computed (() => filteredLines .value .join (" \n " ));
4145const issueLines = computed (() => logLines .value
42- .filter ((line ) => / \b (warn | warning | error | failed | fatal | panic) \b / i .test (line ))
46+ .filter ((line ) => issuePattern .test (line ))
4347 .slice (- 80 ));
44- const lastIssueLine = computed (() => issueLines .value . at ( - 1 ) || " " );
48+ const logInsight = computed (() => buildRuntimeLogInsight ( logLines .value , warningCount . value , errorCount . value , issueLines . value ) );
4549const quickFilters = [
4650 { label: " 错误" , query: " " , level: " error" },
4751 { label: " 警告" , query: " " , level: " warn" },
@@ -56,6 +60,7 @@ async function refreshLogs(): Promise<void> {
5660 await withAction (" runtime-logs" , async () => {
5761 output .value = await runCli (command , label );
5862 lastLabel .value = label ;
63+ loadedTarget .value = target .value ;
5964 copied .value = false ;
6065 issueCopied .value = false ;
6166 });
@@ -74,20 +79,19 @@ function toggleAutoRefresh(): void {
7479}
7580
7681async function copyLogs(): Promise <void > {
77- copied .value = await copyText (visibleOutput .value || output .value );
78- state .output = copied .value ? " 运行日志已复制 。" : " 剪贴板不可用,运行日志未复制。" ;
82+ copied .value = await copyText (sanitizeOutputText ( visibleOutput .value || output .value ) );
83+ state .output = copied .value ? " 脱敏运行日志已复制 。" : " 剪贴板不可用,运行日志未复制。" ;
7984}
8085
8186async function copyIssueSummary(): Promise <void > {
82- const text = [
83- ` MagicNet ${target .value } log issues ` ,
84- ` lines=${logLines .value .length } ` ,
85- ` warnings=${warningCount .value } ` ,
86- ` errors=${errorCount .value } ` ,
87- " " ,
88- ... issueLines .value
89- ].join (" \n " ).trim ();
90- issueCopied .value = await copyText (text );
87+ issueCopied .value = await copyText (formatRuntimeLogIssueReport ({
88+ target: loadedTarget .value ,
89+ lines: logLines .value ,
90+ issueLines: issueLines .value ,
91+ warningCount: warningCount .value ,
92+ errorCount: errorCount .value ,
93+ otherIssueCount: Math .max (0 , issueLines .value .length - warningCount .value - errorCount .value )
94+ }));
9195 state .output = issueCopied .value ? " 日志问题摘要已复制。" : " 剪贴板不可用,日志问题摘要未复制。" ;
9296}
9397
@@ -160,7 +164,12 @@ onUnmounted(stopTimer);
160164 <span >日志 {{ logLines.length }} 行</span >
161165 <span >命中 {{ filteredLines.length }} 行</span >
162166 <span class =" text-amber-300" >警告 {{ warningCount }}</span >
163- <span class =" text-red-300" >错误 {{ errorCount }}</span >
167+ <span class =" text-red-300" >问题 {{ issueLines.length }}</span >
168+ </div >
169+ <div v-if =" output" class =" rounded-md border p-3 text-sm leading-6" :class =" runtimeLogInsightTone(logInsight.status)" >
170+ <p class =" font-medium" >{{ logInsight.label }}</p >
171+ <p class =" mt-1 text-xs opacity-80" >{{ logInsight.detail }}</p >
172+ <p v-if =" logInsight.lastIssue" class =" mt-2 truncate text-xs opacity-90" >最近问题:{{ logInsight.lastIssue }}</p >
164173 </div >
165174 <div v-if =" output" class =" flex flex-wrap gap-2" >
166175 <Button
@@ -174,9 +183,6 @@ onUnmounted(stopTimer);
174183 </Button >
175184 <Button size="sm" variant="ghost" :disabled =" ! query && level === ' all' " @click =" query = ' ' ; level = ' all' " >全部</Button >
176185 </div >
177- <p v-if =" lastIssueLine" class =" truncate rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-xs text-amber-100" >
178- 最近问题:{{ lastIssueLine }}
179- </p >
180186 <Button v-if =" issueLines .length " size="sm" variant="outline" @click =" copyIssueSummary " >
181187 <Copy :size =" 15 " />{{ issueCopied ? "已复制摘要" : "复制问题摘要" }}
182188 </Button >
0 commit comments