Merge pull request #110 from alibaba/fix/panel-text-uncompleted

feat: add HTML escaping to Panel
This commit is contained in:
Simon
2026-01-14 15:17:43 +08:00
committed by GitHub
2 changed files with 85 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
import { type Step, UIState } from './UIState'
import { I18n, type SupportedLanguage } from './i18n'
import { truncate } from './utils'
import { escapeHtml, truncate } from './utils'
import styles from './Panel.module.css'
@@ -163,6 +163,20 @@ export class Panel {
* Convert semantic update to step data with i18n
*/
#toStepData(data: PanelUpdate): Omit<Step, 'id' | 'stepNumber' | 'timestamp'> {
// #region agent log
fetch('http://127.0.0.1:7243/ingest/bc2a9e35-f282-4f4f-a970-887c381f4d18', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
location: 'Panel.ts:#toStepData',
message: 'Converting update to step',
data: { type: data.type, rawData: JSON.stringify(data).substring(0, 200) },
timestamp: Date.now(),
sessionId: 'debug-session',
hypothesisId: 'D',
}),
}).catch(() => {})
// #endregion
switch (data.type) {
case 'thinking':
return { type: 'thinking', displayText: data.text ?? this.#i18n.t('ui.panel.thinking') }
@@ -591,7 +605,26 @@ export class Panel {
#updateHistory(): void {
const steps = this.#state.getAllSteps()
this.#historySection.innerHTML = steps.map((step) => this.#createHistoryItem(step)).join('')
const html = steps.map((step) => this.#createHistoryItem(step)).join('')
// #region agent log
fetch('http://127.0.0.1:7243/ingest/bc2a9e35-f282-4f4f-a970-887c381f4d18', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
location: 'Panel.ts:#updateHistory',
message: 'Setting innerHTML',
data: {
htmlLength: html.length,
stepsCount: steps.length,
htmlPreview: html.substring(0, 300),
},
timestamp: Date.now(),
sessionId: 'debug-session',
hypothesisId: 'C',
}),
}).catch(() => {})
// #endregion
this.#historySection.innerHTML = html
// Scroll to bottom to show latest records
this.#scrollToBottom()
@@ -605,6 +638,24 @@ export class Panel {
}
#createHistoryItem(step: Step): string {
// #region agent log
fetch('http://127.0.0.1:7243/ingest/bc2a9e35-f282-4f4f-a970-887c381f4d18', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
location: 'Panel.ts:#createHistoryItem',
message: 'Creating history item',
data: {
displayText: step.displayText,
type: step.type,
hasAngleBrackets: /[<>]/.test(step.displayText),
},
timestamp: Date.now(),
sessionId: 'debug-session',
hypothesisId: 'A',
}),
}).catch(() => {})
// #endregion
const time = step.timestamp.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
@@ -656,16 +707,34 @@ export class Panel {
duration: durationText || '', // Explicitly pass empty string to replace template
})
return `
const htmlOutput = `
<div class="${styles.historyItem} ${typeClass}">
<div class="${styles.historyContent}">
<span class="${styles.statusIcon}">${statusIcon}</span>
<span>${step.displayText}</span>
<span>${escapeHtml(step.displayText)}</span>
</div>
<div class="${styles.historyMeta}">
${stepLabel}
</div>
</div>
`
// #region agent log
fetch('http://127.0.0.1:7243/ingest/bc2a9e35-f282-4f4f-a970-887c381f4d18', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
location: 'Panel.ts:#createHistoryItem:return',
message: 'Generated HTML output',
data: {
htmlOutputLength: htmlOutput.length,
htmlOutputPreview: htmlOutput.substring(0, 200),
},
timestamp: Date.now(),
sessionId: 'debug-session',
hypothesisId: 'A',
}),
}).catch(() => {})
// #endregion
return htmlOutput
}
}

View File

@@ -4,3 +4,15 @@ export function truncate(text: string, maxLength: number): string {
}
return text
}
/**
* Escape HTML special characters to prevent XSS and rendering issues
*/
export function escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
}