feat: add model name display in AI actions and optimize HTML report

- Add ModelName field to PlanningResult and SubActionResult
- Update HTML report with improved layout and model name display
- Fix elapsed time setting bug and enhance mobile responsiveness
This commit is contained in:
lilong.129
2025-06-08 22:08:51 +08:00
parent 660e8ca124
commit 14cef72f5a
7 changed files with 609 additions and 118 deletions
+1 -1
View File
@@ -1 +1 @@
v5.0.0-beta-2506081925 v5.0.0-beta-2506082208
+511 -47
View File
@@ -449,16 +449,46 @@ const htmlTemplate = `<!DOCTYPE html>
box-shadow: 0 4px 6px rgba(0,0,0,0.1); box-shadow: 0 4px 6px rgba(0,0,0,0.1);
} }
.header h1 { .header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-left h1 {
font-size: 2.5em; font-size: 2.5em;
margin-bottom: 10px; margin-bottom: 10px;
} }
.header .subtitle { .header-left .subtitle {
font-size: 1.2em; font-size: 1.2em;
opacity: 0.9; opacity: 0.9;
} }
.header-right {
text-align: right;
}
.start-time {
background: rgba(255, 255, 255, 0.2);
padding: 12px 20px;
border-radius: 8px;
backdrop-filter: blur(10px);
}
.time-label {
display: block;
font-size: 0.9em;
opacity: 0.8;
margin-bottom: 4px;
}
.time-value {
display: block;
font-size: 1.1em;
font-weight: bold;
}
.summary { .summary {
background: white; background: white;
padding: 25px; padding: 25px;
@@ -511,16 +541,83 @@ const htmlTemplate = `<!DOCTYPE html>
.platform-info { .platform-info {
background: #e9ecef; background: #e9ecef;
padding: 15px; padding: 20px;
border-radius: 8px; border-radius: 8px;
margin-top: 20px; margin-top: 20px;
} }
.platform-info h3 { .platform-info h3 {
margin-bottom: 10px; margin-bottom: 15px;
color: #495057; color: #495057;
} }
.platform-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.platform-item {
background: white;
padding: 15px;
border-radius: 8px;
text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.platform-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.platform-label {
font-size: 1.0em;
color: #6c757d;
margin-bottom: 8px;
font-weight: 500;
}
.platform-value {
font-size: 0.9em;
font-weight: bold;
color: #2c3e50;
word-break: break-all;
}
.controls {
background: white;
padding: 20px;
border-radius: 10px;
margin-bottom: 30px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
text-align: center;
}
.controls button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 24px;
margin: 0 10px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.controls button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.controls button:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.step-container { .step-container {
background: white; background: white;
margin-bottom: 20px; margin-bottom: 20px;
@@ -643,16 +740,56 @@ const htmlTemplate = `<!DOCTYPE html>
align-items: center; align-items: center;
gap: 15px; gap: 15px;
margin-bottom: 10px; margin-bottom: 10px;
cursor: pointer;
transition: background-color 0.3s;
padding: 8px;
border-radius: 6px;
}
.action-header:hover {
background-color: rgba(0, 123, 255, 0.1);
} }
.action-header strong { .action-header strong {
color: #007bff; color: #007bff;
} }
.action-toggle {
margin-left: auto;
font-size: 0.8em;
color: #6c757d;
transition: transform 0.3s;
}
.action-toggle.rotated {
transform: rotate(-90deg);
}
.action-toggle.collapsed {
transform: rotate(-90deg);
}
.action-content {
display: none;
}
.action-content.expanded {
display: block;
}
.action-params { .action-params {
color: #6c757d; color: #6c757d;
font-style: italic; font-style: italic;
margin-bottom: 10px; margin-bottom: 10px;
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px;
font-size: 0.9em;
line-height: 1.4;
} }
.error { .error {
@@ -674,6 +811,22 @@ const htmlTemplate = `<!DOCTYPE html>
margin-bottom: 10px; margin-bottom: 10px;
} }
.sub-action-content {
display: flex;
gap: 20px;
align-items: flex-start;
}
.sub-action-left {
flex: 1;
min-width: 0;
}
.sub-action-right {
flex: 1;
min-width: 0;
}
.sub-action-header { .sub-action-header {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -691,13 +844,53 @@ const htmlTemplate = `<!DOCTYPE html>
} }
.thought { .thought {
background: #fff3cd; background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);
border: 1px solid #ffeaa7; border: 2px solid #2196f3;
border-radius: 6px; border-radius: 12px;
padding: 8px; padding: 15px;
margin: 8px 0; margin: 10px 0;
font-style: italic; font-style: italic;
color: #856404; color: #1565c0;
font-size: 1.0em;
font-weight: 500;
box-shadow: 0 2px 8px rgba(33, 150, 243, 0.15);
display: flex;
align-items: flex-start;
gap: 10px;
}
.thought::before {
content: "💭";
font-size: 1.2em;
flex-shrink: 0;
margin-top: 0px;
line-height: 1;
}
.model-name-container {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 8px 12px;
margin: 8px 0;
font-size: 0.9em;
display: flex;
align-items: center;
gap: 8px;
}
.model-label {
font-weight: 600;
color: #495057;
}
.model-value {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
background: #e9ecef;
padding: 2px 6px;
border-radius: 4px;
color: #495057;
font-size: 0.85em;
} }
.arguments { .arguments {
@@ -711,7 +904,31 @@ const htmlTemplate = `<!DOCTYPE html>
} }
.requests { .requests {
margin-top: 10px; margin-top: 15px;
}
.requests-toggle {
background: #6c757d;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 0.8em;
margin-bottom: 10px;
transition: background-color 0.3s;
}
.requests-toggle:hover {
background: #5a6268;
}
.requests-content {
display: none;
}
.requests-content.show {
display: block;
} }
.request-item { .request-item {
@@ -774,7 +991,24 @@ const htmlTemplate = `<!DOCTYPE html>
} }
.sub-action-screenshots, .screenshots-section { .sub-action-screenshots, .screenshots-section {
margin-top: 15px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border: 2px solid #28a745;
border-radius: 12px;
padding: 12px;
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.15);
}
.sub-action-screenshots h5, .screenshots-section h4 {
color: #155724;
margin-bottom: 10px;
font-size: 1.0em;
font-weight: 600;
}
.screenshots-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
} }
.screenshot-item { .screenshot-item {
@@ -783,6 +1017,13 @@ const htmlTemplate = `<!DOCTYPE html>
border-radius: 8px; border-radius: 8px;
padding: 10px; padding: 10px;
margin-bottom: 15px; margin-bottom: 15px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.screenshot-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
} }
.screenshot-item.small { .screenshot-item.small {
@@ -876,12 +1117,50 @@ const htmlTemplate = `<!DOCTYPE html>
margin-top: 20px; margin-top: 20px;
} }
.logs-header {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
padding: 8px;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 6px;
transition: background-color 0.3s;
margin-bottom: 10px;
}
.logs-header:hover {
background: #e9ecef;
}
.logs-header h4 {
margin: 0;
color: #495057;
}
.logs-toggle {
margin-left: auto;
font-size: 0.8em;
color: #6c757d;
transition: transform 0.3s;
}
.logs-toggle.collapsed {
transform: rotate(-90deg);
}
.logs-container { .logs-container {
max-height: 400px; max-height: 400px;
overflow-y: auto; overflow-y: auto;
border: 1px solid #dee2e6; border: 1px solid #dee2e6;
border-radius: 6px; border-radius: 6px;
background: #f8f9fa; background: #f8f9fa;
display: none;
}
.logs-container.show {
display: block;
} }
.log-entry { .log-entry {
@@ -1065,10 +1344,43 @@ const htmlTemplate = `<!DOCTYPE html>
padding: 10px; padding: 10px;
} }
.header h1 { .header-content {
flex-direction: column;
align-items: flex-start;
gap: 20px;
}
.header-left h1 {
font-size: 2em; font-size: 2em;
} }
.header-right {
text-align: left;
width: 100%;
}
.start-time {
width: 100%;
text-align: center;
}
.platform-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
}
.platform-item {
padding: 12px;
}
.platform-label {
font-size: 0.8em;
}
.platform-value {
font-size: 1em;
}
.summary-grid { .summary-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px; gap: 10px;
@@ -1091,12 +1403,38 @@ const htmlTemplate = `<!DOCTYPE html>
gap: 8px; gap: 8px;
} }
.controls button {
padding: 6px 10px;
font-size: 0.8em;
margin: 2px;
}
.logs-header {
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
.logs-header h4 {
font-size: 0.9em;
}
.request-header { .request-header {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
gap: 6px; gap: 6px;
} }
.sub-action-content {
flex-direction: column;
gap: 15px;
}
.screenshots-grid {
grid-template-columns: 1fr;
gap: 10px;
}
.screenshot-image img { .screenshot-image img {
max-height: 250px; max-height: 250px;
} }
@@ -1140,9 +1478,19 @@ const htmlTemplate = `<!DOCTYPE html>
<body> <body>
<div class="container"> <div class="container">
<div class="header"> <div class="header">
<div class="header-content">
<div class="header-left">
<h1>🚀 HttpRunner Test Report</h1> <h1>🚀 HttpRunner Test Report</h1>
<div class="subtitle">Automated Testing Results</div> <div class="subtitle">Automated Testing Results</div>
</div> </div>
<div class="header-right">
<div class="start-time">
<span class="time-label">Start Time:</span>
<span class="time-value">{{.Time.StartAt.Format "2006-01-02 15:04:05"}}</span>
</div>
</div>
</div>
</div>
<div class="summary"> <div class="summary">
<h2>📊 Test Summary</h2> <h2>📊 Test Summary</h2>
@@ -1187,16 +1535,26 @@ const htmlTemplate = `<!DOCTYPE html>
<div class="platform-info"> <div class="platform-info">
<h3>🔧 Platform Information</h3> <h3>🔧 Platform Information</h3>
<p><strong>HttpRunner Version:</strong> {{.Platform.HttprunnerVersion}}</p> <div class="platform-grid">
<p><strong>Go Version:</strong> {{.Platform.GoVersion}}</p> <div class="platform-item">
<p><strong>Platform:</strong> {{.Platform.Platform}}</p> <div class="platform-label">HttpRunner Version</div>
<p><strong>Start Time:</strong> {{.Time.StartAt.Format "2006-01-02 15:04:05"}}</p> <div class="platform-value">{{.Platform.HttprunnerVersion}}</div>
</div>
<div class="platform-item">
<div class="platform-label">Go Version</div>
<div class="platform-value">{{.Platform.GoVersion}}</div>
</div>
<div class="platform-item">
<div class="platform-label">Platform</div>
<div class="platform-value">{{.Platform.Platform}}</div>
</div>
</div>
</div> </div>
</div> </div>
<div class="controls"> <div class="controls">
<button onclick="expandAll()">Expand All</button> <button id="toggleStepsBtn" onclick="toggleAllSteps()">Collapse All Steps</button>
<button onclick="collapseAll()">Collapse All</button> <button id="toggleActionsBtn" onclick="toggleAllActions()">Expand All Actions</button>
</div> </div>
<div class="test-cases"> <div class="test-cases">
@@ -1234,11 +1592,13 @@ const htmlTemplate = `<!DOCTYPE html>
<h4>Actions</h4> <h4>Actions</h4>
{{range $actionIndex, $action := $step.Actions}} {{range $actionIndex, $action := $step.Actions}}
<div class="action-item"> <div class="action-item">
<div class="action-header"> <div class="action-header" onclick="toggleAction({{$stepIndex}}, {{$actionIndex}})">
<strong>{{$action.Method}}</strong> <strong>{{$action.Method}}</strong>
<span class="duration">{{formatDuration $action.Elapsed}}</span> <span class="duration">{{formatDuration $action.Elapsed}}</span>
{{if $action.Error}}<span class="error">Error: {{$action.Error}}</span>{{end}} {{if $action.Error}}<span class="error">Error: {{$action.Error}}</span>{{end}}
<span class="action-toggle collapsed" id="action-toggle-{{$stepIndex}}-{{$actionIndex}}">▶</span>
</div> </div>
<div class="action-content" id="action-content-{{$stepIndex}}-{{$actionIndex}}">
<div class="action-params">{{$action.Params}}</div> <div class="action-params">{{$action.Params}}</div>
{{if $action.SubActions}} {{if $action.SubActions}}
@@ -1250,16 +1610,29 @@ const htmlTemplate = `<!DOCTYPE html>
<span class="duration">{{formatDuration $subAction.Elapsed}}</span> <span class="duration">{{formatDuration $subAction.Elapsed}}</span>
</div> </div>
{{if $subAction.Thought}} <div class="sub-action-content">
<div class="thought">💭 {{$subAction.Thought}}</div> <div class="sub-action-left">
{{end}}
{{if $subAction.Arguments}} {{if $subAction.Arguments}}
<div class="arguments">Arguments: {{safeHTML (toJSON $subAction.Arguments)}}</div> <div class="arguments">Arguments: {{safeHTML (toJSON $subAction.Arguments)}}</div>
{{end}} {{end}}
{{if $subAction.Thought}}
<div class="thought">{{$subAction.Thought}}</div>
{{end}}
{{if $subAction.ModelName}}
<div class="model-name-container">
<span class="model-label">🤖 Model:</span>
<span class="model-value">{{$subAction.ModelName}}</span>
</div>
{{end}}
{{if $subAction.Requests}} {{if $subAction.Requests}}
<div class="requests"> <div class="requests">
<button class="requests-toggle" onclick="toggleRequests(this)">
📡 Show Requests ({{len $subAction.Requests}})
</button>
<div class="requests-content">
{{range $request := $subAction.Requests}} {{range $request := $subAction.Requests}}
<div class="request-item"> <div class="request-item">
<div class="request-header"> <div class="request-header">
@@ -1277,10 +1650,15 @@ const htmlTemplate = `<!DOCTYPE html>
</div> </div>
{{end}} {{end}}
</div> </div>
</div>
{{end}} {{end}}
</div>
{{if $subAction.ScreenResults}} {{if $subAction.ScreenResults}}
<div class="sub-action-right">
<div class="sub-action-screenshots"> <div class="sub-action-screenshots">
<h5>📸 Screenshots</h5>
<div class="screenshots-grid">
{{range $screenshot := $subAction.ScreenResults}} {{range $screenshot := $subAction.ScreenResults}}
{{$base64Image := encodeImageBase64 $screenshot.ImagePath}} {{$base64Image := encodeImageBase64 $screenshot.ImagePath}}
{{if $base64Image}} {{if $base64Image}}
@@ -1298,12 +1676,16 @@ const htmlTemplate = `<!DOCTYPE html>
{{end}} {{end}}
{{end}} {{end}}
</div> </div>
</div>
</div>
{{end}} {{end}}
</div> </div>
</div>
{{end}} {{end}}
</div> </div>
{{end}} {{end}}
</div> </div>
</div>
{{end}} {{end}}
</div> </div>
{{end}} {{end}}
@@ -1355,8 +1737,11 @@ const htmlTemplate = `<!DOCTYPE html>
{{$stepLogs := getStepLogs $step}} {{$stepLogs := getStepLogs $step}}
{{if $stepLogs}} {{if $stepLogs}}
<div class="logs-section"> <div class="logs-section">
<h4>📋 Step Logs</h4> <div class="logs-header" onclick="toggleStepLogs({{$stepIndex}})">
<div class="logs-container"> <h4>📋 Step Logs ({{len $stepLogs}})</h4>
<span class="logs-toggle collapsed" id="logs-toggle-{{$stepIndex}}">▶</span>
</div>
<div class="logs-container" id="logs-container-{{$stepIndex}}">
{{range $logEntry := $stepLogs}} {{range $logEntry := $stepLogs}}
<div class="log-entry {{$logEntry.Level}}"> <div class="log-entry {{$logEntry.Level}}">
<div class="log-header" {{if $logEntry.Fields}}onclick="toggleLogFields(this)"{{end}}> <div class="log-header" {{if $logEntry.Fields}}onclick="toggleLogFields(this)"{{end}}>
@@ -1419,6 +1804,49 @@ const htmlTemplate = `<!DOCTYPE html>
} }
} }
function toggleStepLogs(stepIndex) {
const container = document.getElementById('logs-container-' + stepIndex);
const toggle = document.getElementById('logs-toggle-' + stepIndex);
if (container.classList.contains('show')) {
container.classList.remove('show');
toggle.classList.add('collapsed');
toggle.textContent = '▶';
} else {
container.classList.add('show');
toggle.classList.remove('collapsed');
toggle.textContent = '▼';
}
}
function toggleRequests(buttonElement) {
const requestsDiv = buttonElement.parentElement;
const requestsContent = requestsDiv.querySelector('.requests-content');
if (requestsContent.classList.contains('show')) {
requestsContent.classList.remove('show');
buttonElement.textContent = buttonElement.textContent.replace('Hide', 'Show');
} else {
requestsContent.classList.add('show');
buttonElement.textContent = buttonElement.textContent.replace('Show', 'Hide');
}
}
function toggleAction(stepIndex, actionIndex) {
const content = document.getElementById('action-content-' + stepIndex + '-' + actionIndex);
const toggle = document.getElementById('action-toggle-' + stepIndex + '-' + actionIndex);
if (content.classList.contains('expanded')) {
content.classList.remove('expanded');
toggle.classList.add('collapsed');
toggle.textContent = '▶';
} else {
content.classList.add('expanded');
toggle.classList.remove('collapsed');
toggle.textContent = '▼';
}
}
function openImageModal(src) { function openImageModal(src) {
const modal = document.getElementById('imageModal'); const modal = document.getElementById('imageModal');
const modalImg = document.getElementById('modalImage'); const modalImg = document.getElementById('modalImage');
@@ -1438,34 +1866,70 @@ const htmlTemplate = `<!DOCTYPE html>
} }
} }
// Expand all steps // Toggle all steps
function expandAll() { function toggleAllSteps() {
const contents = document.querySelectorAll('.step-content'); const contents = document.querySelectorAll('.step-content');
const icons = document.querySelectorAll('.toggle-icon'); const icons = document.querySelectorAll('.toggle-icon');
const button = document.getElementById('toggleStepsBtn');
// Check if any step is currently expanded
const anyExpanded = Array.from(contents).some(content => content.classList.contains('show'));
if (anyExpanded) {
// Collapse all
contents.forEach(content => content.classList.remove('show'));
icons.forEach(icon => icon.classList.remove('rotated'));
button.textContent = 'Expand All Steps';
} else {
// Expand all
contents.forEach(content => content.classList.add('show'));
icons.forEach(icon => icon.classList.add('rotated'));
button.textContent = 'Collapse All Steps';
}
}
// Toggle all actions
function toggleAllActions() {
const contents = document.querySelectorAll('.action-content');
const toggles = document.querySelectorAll('.action-toggle');
const button = document.getElementById('toggleActionsBtn');
// Check if any action is currently expanded
const anyExpanded = Array.from(contents).some(content => content.classList.contains('expanded'));
if (anyExpanded) {
// Collapse all
contents.forEach(content => content.classList.remove('expanded'));
toggles.forEach(toggle => {
toggle.classList.add('collapsed');
toggle.textContent = '▶';
});
button.textContent = 'Expand All Actions';
} else {
// Expand all
contents.forEach(content => content.classList.add('expanded'));
toggles.forEach(toggle => {
toggle.classList.remove('collapsed');
toggle.textContent = '▼';
});
button.textContent = 'Collapse All Actions';
}
}
// Auto-expand all steps on load to show actions
document.addEventListener('DOMContentLoaded', function() {
// Expand all steps to show the actions list
const contents = document.querySelectorAll('.step-content');
const icons = document.querySelectorAll('.toggle-icon');
const stepsButton = document.getElementById('toggleStepsBtn');
contents.forEach(content => content.classList.add('show')); contents.forEach(content => content.classList.add('show'));
icons.forEach(icon => icon.classList.add('rotated')); icons.forEach(icon => icon.classList.add('rotated'));
}
// Collapse all steps // Update button text to reflect current state (steps are expanded)
function collapseAll() { if (stepsButton) {
const contents = document.querySelectorAll('.step-content'); stepsButton.textContent = 'Collapse All Steps';
const icons = document.querySelectorAll('.toggle-icon');
contents.forEach(content => content.classList.remove('show'));
icons.forEach(icon => icon.classList.remove('rotated'));
} }
// Auto-expand failed steps on load
document.addEventListener('DOMContentLoaded', function() {
const failedSteps = document.querySelectorAll('.step-container .status-badge.failure');
failedSteps.forEach(badge => {
const stepContainer = badge.closest('.step-container');
const stepHeader = stepContainer.querySelector('.step-header');
if (stepHeader) {
stepHeader.click();
}
});
}); });
</script> </script>
</body> </body>
+4
View File
@@ -21,11 +21,13 @@ func NewLLMContentParser(modelType option.LLMServiceType) LLMContentParser {
switch modelType { switch modelType {
case option.DOUBAO_1_5_UI_TARS_250428: case option.DOUBAO_1_5_UI_TARS_250428:
return &UITARSContentParser{ return &UITARSContentParser{
modelType: modelType,
systemPrompt: doubao_1_5_ui_tars_planning_prompt, systemPrompt: doubao_1_5_ui_tars_planning_prompt,
actionMapping: doubao_1_5_ui_tars_action_mapping, actionMapping: doubao_1_5_ui_tars_action_mapping,
} }
default: default:
return &JSONContentParser{ return &JSONContentParser{
modelType: modelType,
systemPrompt: doubao_1_5_thinking_vision_pro_planning_prompt, systemPrompt: doubao_1_5_thinking_vision_pro_planning_prompt,
actionMapping: doubao_1_5_thinking_vision_pro_action_mapping, actionMapping: doubao_1_5_thinking_vision_pro_action_mapping,
} }
@@ -34,6 +36,7 @@ func NewLLMContentParser(modelType option.LLMServiceType) LLMContentParser {
// JSONContentParser parses the response as JSON string format // JSONContentParser parses the response as JSON string format
type JSONContentParser struct { type JSONContentParser struct {
modelType option.LLMServiceType
systemPrompt string systemPrompt string
actionMapping map[string]option.ActionName actionMapping map[string]option.ActionName
} }
@@ -98,5 +101,6 @@ func (p *JSONContentParser) Parse(content string, size types.Size) (*PlanningRes
ToolCalls: toolCalls, ToolCalls: toolCalls,
Thought: jsonResponse.Thought, Thought: jsonResponse.Thought,
Content: content, Content: content,
ModelName: string(p.modelType),
}, nil }, nil
} }
+2
View File
@@ -21,6 +21,7 @@ const (
// UITARSContentParser parses the Thought/Action format response // UITARSContentParser parses the Thought/Action format response
type UITARSContentParser struct { type UITARSContentParser struct {
modelType option.LLMServiceType
systemPrompt string systemPrompt string
actionMapping map[string]option.ActionName actionMapping map[string]option.ActionName
} }
@@ -55,6 +56,7 @@ func (p *UITARSContentParser) Parse(content string, size types.Size) (*PlanningR
ToolCalls: toolCalls, ToolCalls: toolCalls,
Thought: thought, Thought: thought,
Content: content, Content: content,
ModelName: string(p.modelType),
}, nil }, nil
} }
+3
View File
@@ -32,6 +32,7 @@ type PlanningResult struct {
Thought string `json:"thought"` Thought string `json:"thought"`
Content string `json:"content"` // original content from model Content string `json:"content"` // original content from model
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
ModelName string `json:"model_name"` // model name used for planning
} }
func NewPlanner(ctx context.Context, modelConfig *ModelConfig) (*Planner, error) { func NewPlanner(ctx context.Context, modelConfig *ModelConfig) (*Planner, error) {
@@ -132,6 +133,7 @@ func (p *Planner) Call(ctx context.Context, opts *PlanningOptions) (*PlanningRes
result := &PlanningResult{ result := &PlanningResult{
ToolCalls: message.ToolCalls, ToolCalls: message.ToolCalls,
Thought: message.Content, Thought: message.Content,
ModelName: string(p.modelConfig.ModelType),
} }
return result, nil return result, nil
} }
@@ -142,6 +144,7 @@ func (p *Planner) Call(ctx context.Context, opts *PlanningOptions) (*PlanningRes
result = &PlanningResult{ result = &PlanningResult{
Thought: message.Content, Thought: message.Content,
Error: err.Error(), Error: err.Error(),
ModelName: string(p.modelConfig.ModelType),
} }
log.Debug().Str("reason", err.Error()).Msg("parse content to actions failed") log.Debug().Str("reason", err.Error()).Msg("parse content to actions failed")
} }
+18
View File
@@ -35,6 +35,7 @@ func (dExt *XTDriver) StartToGoal(ctx context.Context, prompt string, opts ...op
} }
// Plan next action with history reset on first attempt // Plan next action with history reset on first attempt
planningStartTime := time.Now()
planningOpts := opts planningOpts := opts
if attempt == 1 { if attempt == 1 {
// Add ResetHistory option for the first attempt // Add ResetHistory option for the first attempt
@@ -52,6 +53,9 @@ func (dExt *XTDriver) StartToGoal(ctx context.Context, prompt string, opts ...op
ActionName: "plan_next_action", ActionName: "plan_next_action",
Arguments: prompt, Arguments: prompt,
Error: err, Error: err,
StartTime: planningStartTime.Unix(),
Elapsed: time.Since(planningStartTime).Milliseconds(),
SessionData: dExt.GetSession().GetData(true),
}) })
return allSubActions, err return allSubActions, err
} }
@@ -59,6 +63,17 @@ func (dExt *XTDriver) StartToGoal(ctx context.Context, prompt string, opts ...op
// Check if task is finished BEFORE executing actions // Check if task is finished BEFORE executing actions
if dExt.isTaskFinished(result) { if dExt.isTaskFinished(result) {
log.Info().Msg("task finished, stopping StartToGoal") log.Info().Msg("task finished, stopping StartToGoal")
// Create a sub-action result to record the planning result even when task is finished
subActionResult := &SubActionResult{
ActionName: "plan_next_action",
Arguments: prompt,
StartTime: planningStartTime.Unix(),
Elapsed: time.Since(planningStartTime).Milliseconds(),
Thought: result.Thought,
ModelName: result.ModelName,
SessionData: dExt.GetSession().GetData(true),
}
allSubActions = append(allSubActions, subActionResult)
return allSubActions, nil return allSubActions, nil
} }
@@ -79,6 +94,7 @@ func (dExt *XTDriver) StartToGoal(ctx context.Context, prompt string, opts ...op
Arguments: toolCall.Function.Arguments, Arguments: toolCall.Function.Arguments,
StartTime: subActionStartTime.Unix(), StartTime: subActionStartTime.Unix(),
Thought: result.Thought, Thought: result.Thought,
ModelName: result.ModelName,
} }
if err := dExt.invokeToolCall(ctx, toolCall); err != nil { if err := dExt.invokeToolCall(ctx, toolCall); err != nil {
@@ -86,6 +102,7 @@ func (dExt *XTDriver) StartToGoal(ctx context.Context, prompt string, opts ...op
allSubActions = append(allSubActions, subActionResult) allSubActions = append(allSubActions, subActionResult)
return allSubActions, err return allSubActions, err
} }
subActionResult.Elapsed = time.Since(subActionStartTime).Milliseconds()
// Collect sub-action specific attachments and reset session data // Collect sub-action specific attachments and reset session data
subActionResult.SessionData = dExt.GetSession().GetData(true) // reset after getting data subActionResult.SessionData = dExt.GetSession().GetData(true) // reset after getting data
@@ -227,6 +244,7 @@ type SubActionResult struct {
Elapsed int64 `json:"elapsed_ms"` // sub-action elapsed time(ms) Elapsed int64 `json:"elapsed_ms"` // sub-action elapsed time(ms)
Error error `json:"error,omitempty"` // sub-action execution result Error error `json:"error,omitempty"` // sub-action execution result
Thought string `json:"thought,omitempty"` // sub-action thought Thought string `json:"thought,omitempty"` // sub-action thought
ModelName string `json:"model_name,omitempty"` // model name used for AI actions
SessionData SessionData
} }