mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
feat: add html template
This commit is contained in:
1 parent
acf5824a65
commit
021680cd70
3 files changed
+475
-7
No files matched your search
+4
-1
@@ -65,4 +65,7 @@ docs-site/.next
|
||||
docs-site/content
|
||||
|
||||
# python cache
|
||||
__pycache__/
|
||||
__pycache__/
|
||||
|
||||
integration-tests/concurrent-runner/output/
|
||||
integration-tests/concurrent-runner/task-*
|
||||
@@ -0,0 +1,277 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Qwen Code Chat Export</title>
|
||||
<!-- Load React and ReactDOM from CDN -->
|
||||
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
||||
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
||||
|
||||
<!-- Manually create the jsxRuntime object to satisfy the dependency -->
|
||||
<script>
|
||||
// Provide a minimal JSX runtime for builds that expect react/jsx-runtime globals.
|
||||
const withKey = (props, key) =>
|
||||
key == null ? props : Object.assign({}, props, { key });
|
||||
const jsx = (type, props, key) => React.createElement(type, withKey(props, key));
|
||||
const jsxRuntime = {
|
||||
Fragment: React.Fragment,
|
||||
jsx,
|
||||
jsxs: jsx,
|
||||
jsxDEV: jsx
|
||||
};
|
||||
|
||||
window.ReactJSXRuntime = jsxRuntime;
|
||||
window['react/jsx-runtime'] = jsxRuntime;
|
||||
window['react/jsx-dev-runtime'] = jsxRuntime;
|
||||
</script>
|
||||
|
||||
<!-- Load the webui library from CDN -->
|
||||
<script src="https://unpkg.com/@qwen-code/[email protected]/dist/index.umd.js"></script>
|
||||
|
||||
<!-- Load the CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/@qwen-code/[email protected]/dist/styles.css">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--bg-primary: #18181b;
|
||||
--bg-secondary: #27272a;
|
||||
--text-primary: #f4f4f5;
|
||||
--text-secondary: #a1a1aa;
|
||||
--border-color: #3f3f46;
|
||||
--accent-color: #3b82f6;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.page-wrapper {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background-color: rgba(24, 24, 27, 0.95);
|
||||
backdrop-filter: blur(8px);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--text-primary);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
color: #71717a;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
padding: 40px 20px;
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 5px;
|
||||
border: 2px solid var(--bg-primary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #52525b;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.chat-container {
|
||||
max-width: 100%;
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 12px 16px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.meta {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.chat-container {
|
||||
padding: 16px 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-wrapper">
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<h1>Qwen Code Export</h1>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<div class="meta-item">
|
||||
<span class="meta-label">Session Id</span>
|
||||
<span id="session-id" class="font-mono">-</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<span class="meta-label">Export Time</span>
|
||||
<span id="session-date">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chat-root-no-babel" class="chat-container"></div>
|
||||
</div>
|
||||
|
||||
<script id="chat-data" type="application/json">
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
const chatDataElement = document.getElementById('chat-data');
|
||||
const chatData = chatDataElement?.textContent
|
||||
? JSON.parse(chatDataElement.textContent)
|
||||
: {};
|
||||
const rawMessages = Array.isArray(chatData.messages) ? chatData.messages : [];
|
||||
const messages = rawMessages.filter((record) => record && record.type !== 'system');
|
||||
|
||||
// Populate metadata
|
||||
const sessionIdElement = document.getElementById('session-id');
|
||||
if (sessionIdElement && chatData.sessionId) {
|
||||
sessionIdElement.textContent = chatData.sessionId;
|
||||
}
|
||||
|
||||
const sessionDateElement = document.getElementById('session-date');
|
||||
if (sessionDateElement && chatData.startTime) {
|
||||
try {
|
||||
const date = new Date(chatData.startTime);
|
||||
sessionDateElement.textContent = date.toLocaleString(undefined, {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
} catch (e) {
|
||||
sessionDateElement.textContent = chatData.startTime;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the ChatViewer and Platform components from the global object
|
||||
const { ChatViewer, PlatformProvider } = QwenCodeWebUI;
|
||||
|
||||
// Define a minimal platform context for web usage
|
||||
const platformContext = {
|
||||
platform: 'web',
|
||||
postMessage: (message) => {
|
||||
console.log('Posted message:', message);
|
||||
},
|
||||
onMessage: (handler) => {
|
||||
window.addEventListener('message', handler);
|
||||
return () => window.removeEventListener('message', handler);
|
||||
},
|
||||
openFile: (path) => {
|
||||
console.log('Opening file:', path);
|
||||
},
|
||||
getResourceUrl: (resource) => {
|
||||
return null;
|
||||
},
|
||||
features: {
|
||||
canOpenFile: false,
|
||||
canCopy: true
|
||||
}
|
||||
};
|
||||
|
||||
// Render the ChatViewer component without Babel
|
||||
const rootElementNoBabel = document.getElementById('chat-root-no-babel');
|
||||
|
||||
// Create the ChatViewer element wrapped with PlatformProvider using React.createElement (no JSX)
|
||||
const ChatAppNoBabel = React.createElement(PlatformProvider, { value: platformContext },
|
||||
React.createElement(ChatViewer, {
|
||||
messages,
|
||||
autoScroll: false,
|
||||
theme: 'dark'
|
||||
})
|
||||
);
|
||||
|
||||
ReactDOM.render(ChatAppNoBabel, rootElementNoBabel);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -9,6 +9,7 @@ the Qwen CLI in parallel with status tracking and output capture.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import html
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
@@ -93,6 +94,7 @@ class RunRecord:
|
||||
prompt_results: List[PromptResult] = field(default_factory=list)
|
||||
diff_file: Optional[str] = None # Path to git diff output
|
||||
session_log_file: Optional[str] = None # Path to session log (chat recording)
|
||||
session_html_file: Optional[str] = None # Path to rendered chat HTML
|
||||
session_id: Optional[str] = None # Session ID (UUID from chat recording)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
@@ -111,6 +113,7 @@ class RunRecord:
|
||||
"error_message": self.error_message,
|
||||
"diff_file": self.diff_file,
|
||||
"session_log_file": self.session_log_file,
|
||||
"session_html_file": self.session_html_file,
|
||||
"session_id": self.session_id,
|
||||
"prompt_results": [
|
||||
{
|
||||
@@ -142,6 +145,7 @@ class RunRecord:
|
||||
error_message=data.get("error_message"),
|
||||
diff_file=data.get("diff_file"),
|
||||
session_log_file=data.get("session_log_file"),
|
||||
session_html_file=data.get("session_html_file"),
|
||||
session_id=data.get("session_id"),
|
||||
)
|
||||
|
||||
@@ -248,7 +252,7 @@ class GitWorktreeManager:
|
||||
|
||||
return result.stdout
|
||||
|
||||
async def collect_session_log(self, worktree_dir: Path, output_dir: Path) -> Optional[Tuple[Path, str]]:
|
||||
async def collect_session_log(self, worktree_dir: Path, output_dir: Path) -> Optional[Tuple[Path, str, Path]]:
|
||||
"""Collect the session log file from the worktree's chat recording.
|
||||
|
||||
Session logs are stored at:
|
||||
@@ -257,7 +261,7 @@ class GitWorktreeManager:
|
||||
Where projectId is the sanitized worktree path.
|
||||
|
||||
Returns:
|
||||
Tuple of (output_path, session_id) or None if not found.
|
||||
Tuple of (output_path, session_id, rendered_html_path) or None if not found.
|
||||
"""
|
||||
import re
|
||||
|
||||
@@ -293,6 +297,8 @@ class GitWorktreeManager:
|
||||
# Read the original file, modify cwd field, and write to output
|
||||
# cwd should be the actual current working dir (where runner is executed)
|
||||
actual_cwd = str(Path.cwd())
|
||||
messages = []
|
||||
start_time = None
|
||||
async with aiofiles.open(session_log, 'r') as src, aiofiles.open(output_log, 'w') as dst:
|
||||
async for line in src:
|
||||
line = line.strip()
|
||||
@@ -300,6 +306,9 @@ class GitWorktreeManager:
|
||||
try:
|
||||
record = json.loads(line)
|
||||
record['cwd'] = actual_cwd
|
||||
messages.append(record)
|
||||
if not start_time and 'time' in record:
|
||||
start_time = record['time']
|
||||
await dst.write(json.dumps(record, ensure_ascii=False) + '\n')
|
||||
except json.JSONDecodeError:
|
||||
# If line is not valid JSON, write it as-is
|
||||
@@ -307,7 +316,38 @@ class GitWorktreeManager:
|
||||
|
||||
self.console.print(f"[dim]Session log copied: {session_log.name}[/dim]")
|
||||
|
||||
return output_log, session_id
|
||||
# Generate rendered HTML
|
||||
rendered_html_path = chats_output_dir / f"{session_id}.html"
|
||||
try:
|
||||
template_path = Path(__file__).parent / "render-chat-temp.html"
|
||||
if template_path.exists():
|
||||
async with aiofiles.open(template_path, 'r') as f:
|
||||
template_content = await f.read()
|
||||
|
||||
chat_data = {
|
||||
"sessionId": session_id,
|
||||
"startTime": start_time or datetime.now().isoformat(),
|
||||
"messages": messages
|
||||
}
|
||||
|
||||
# Simple string replacement for injection
|
||||
# The template has <script id="chat-data" type="application/json"></script>
|
||||
placeholder = '<script id="chat-data" type="application/json">'
|
||||
json_str = json.dumps(chat_data, ensure_ascii=False, indent=2)
|
||||
# Escape </script> to prevent breaking the HTML script tag
|
||||
json_str = json_str.replace('</script>', '<\\/script>')
|
||||
injection = f'{placeholder}\n{json_str}\n'
|
||||
rendered_content = template_content.replace(placeholder, injection)
|
||||
|
||||
async with aiofiles.open(rendered_html_path, 'w') as f:
|
||||
await f.write(rendered_content)
|
||||
self.console.print(f"[dim]Rendered chat HTML saved: {rendered_html_path.name}[/dim]")
|
||||
else:
|
||||
self.console.print(f"[yellow]Warning: Chat template not found at {template_path}[/yellow]")
|
||||
except Exception as e:
|
||||
self.console.print(f"[yellow]Warning: Failed to render chat HTML: {e}[/yellow]")
|
||||
|
||||
return output_log, session_id, rendered_html_path
|
||||
|
||||
async def _run_command(
|
||||
self,
|
||||
@@ -372,19 +412,165 @@ class StatusTracker:
|
||||
await self._persist()
|
||||
|
||||
async def _persist(self) -> None:
|
||||
"""Persist current state to JSON file."""
|
||||
"""Persist current state to JSON file and generate HTML report."""
|
||||
data = {
|
||||
"updated_at": datetime.now().isoformat(),
|
||||
"runs": [run.to_dict() for run in self._runs.values()],
|
||||
}
|
||||
|
||||
# Write atomically
|
||||
# Write JSON atomically
|
||||
temp_file = self.results_file.with_suffix('.tmp')
|
||||
async with aiofiles.open(temp_file, 'w') as f:
|
||||
await f.write(json.dumps(data, indent=2))
|
||||
|
||||
temp_file.replace(self.results_file)
|
||||
|
||||
# Generate HTML report
|
||||
await self._generate_html(data)
|
||||
|
||||
async def _generate_html(self, data: Dict[str, Any]) -> None:
|
||||
"""Generate a beautiful HTML report."""
|
||||
html_file = self.results_file.with_name("index.html")
|
||||
|
||||
# Calculate summary
|
||||
total = len(data["runs"])
|
||||
succeeded = sum(1 for r in data["runs"] if r["status"] == "succeeded")
|
||||
failed = sum(1 for r in data["runs"] if r["status"] == "failed")
|
||||
running = sum(1 for r in data["runs"] if r["status"] in ["preparing", "running"])
|
||||
|
||||
# Build rows
|
||||
rows = []
|
||||
for run in sorted(data["runs"], key=lambda x: x.get("started_at") or "", reverse=True):
|
||||
status = run["status"]
|
||||
status_class = f"status-{status}"
|
||||
|
||||
# Links
|
||||
links = []
|
||||
|
||||
# Output Directory
|
||||
if run.get("output_dir"):
|
||||
# Make path absolute for local viewing
|
||||
abs_output_dir = os.path.abspath(run["output_dir"])
|
||||
links.append(f'<a href="file://{abs_output_dir}">Outputs</a>')
|
||||
|
||||
# Diff File
|
||||
if run.get("diff_file"):
|
||||
abs_diff_file = os.path.abspath(run["diff_file"])
|
||||
links.append(f'<a href="file://{abs_diff_file}">Diff</a>')
|
||||
|
||||
# Session Log
|
||||
if run.get("session_html_file"):
|
||||
abs_session_html = os.path.abspath(run["session_html_file"])
|
||||
links.append(f'<a href="file://{abs_session_html}">Chat</a>')
|
||||
elif run.get("session_log_file"):
|
||||
abs_session_log = os.path.abspath(run["session_log_file"])
|
||||
links.append(f'<a href="file://{abs_session_log}">Chat (Raw)</a>')
|
||||
|
||||
# Worktree
|
||||
if run.get("worktree_path"):
|
||||
abs_worktree = os.path.abspath(run["worktree_path"])
|
||||
links.append(f'<a href="file://{abs_worktree}">Worktree</a>')
|
||||
|
||||
# Prompt results (stdout/stderr)
|
||||
prompt_links = []
|
||||
for i, p in enumerate(run.get("prompt_results", []), 1):
|
||||
p_links = []
|
||||
if p.get("stdout_file"):
|
||||
p_links.append(f'<a href="file://{os.path.abspath(p["stdout_file"])}">out</a>')
|
||||
if p.get("stderr_file"):
|
||||
p_links.append(f'<a href="file://{os.path.abspath(p["stderr_file"])}">err</a>')
|
||||
|
||||
if p_links:
|
||||
prompt_links.append(f'P{i}: {"|".join(p_links)}')
|
||||
|
||||
links_html = " | ".join(links)
|
||||
prompts_html = "<br>".join(prompt_links)
|
||||
|
||||
duration = "N/A"
|
||||
if run.get("started_at") and run.get("ended_at"):
|
||||
try:
|
||||
start = datetime.fromisoformat(run["started_at"])
|
||||
end = datetime.fromisoformat(run["ended_at"])
|
||||
duration = f"{(end - start).total_seconds():.1f}s"
|
||||
except: pass
|
||||
|
||||
error_msg = f'<div class="error-msg">{html.escape(run["error_message"])}</div>' if run.get("error_message") else ""
|
||||
|
||||
rows.append(f"""
|
||||
<tr>
|
||||
<td><code>{run["run_id"]}</code></td>
|
||||
<td>{html.escape(run["task_name"])}</td>
|
||||
<td>{html.escape(run["model"])}</td>
|
||||
<td><span class="status-pill {status_class}">{status}</span></td>
|
||||
<td>{duration}</td>
|
||||
<td class="links">{links_html}</td>
|
||||
<td class="links">{prompts_html}</td>
|
||||
<td>{error_msg}</td>
|
||||
</tr>
|
||||
""")
|
||||
|
||||
html_content = f"""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Qwen Runner Report</title>
|
||||
<style>
|
||||
body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; max-width: 1200px; margin: 0 auto; padding: 20px; background: #f5f7f9; }}
|
||||
h1 {{ color: #1a202c; }}
|
||||
.summary {{ display: flex; gap: 20px; margin-bottom: 30px; }}
|
||||
.summary-card {{ background: white; padding: 15px 25px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); flex: 1; }}
|
||||
.summary-card h3 {{ margin: 0; font-size: 14px; text-transform: uppercase; color: #718096; }}
|
||||
.summary-card .value {{ font-size: 24px; font-weight: bold; margin-top: 5px; }}
|
||||
table {{ width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }}
|
||||
th, td {{ padding: 12px 15px; text-align: left; border-bottom: 1px solid #e2e8f0; }}
|
||||
th {{ background: #edf2f7; font-weight: 600; color: #4a5568; }}
|
||||
tr:hover {{ background: #f7fafc; }}
|
||||
.status-pill {{ padding: 4px 8px; border-radius: 9999px; font-size: 12px; font-weight: 500; text-transform: uppercase; }}
|
||||
.status-succeeded {{ background: #c6f6d5; color: #22543d; }}
|
||||
.status-failed {{ background: #fed7d7; color: #822727; }}
|
||||
.status-running {{ background: #bee3f8; color: #2a4365; }}
|
||||
.status-preparing {{ background: #e9d8fd; color: #44337a; }}
|
||||
.status-queued {{ background: #edf2f7; color: #4a5568; }}
|
||||
.links a {{ color: #3182ce; text-decoration: none; font-size: 13px; }}
|
||||
.links a:hover {{ text-decoration: underline; }}
|
||||
.error-msg {{ color: #e53e3e; font-size: 12px; margin-top: 4px; max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }}
|
||||
code {{ background: #f1f5f9; padding: 2px 4px; border-radius: 4px; font-family: monospace; font-size: 12px; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Qwen Runner Execution Report</h1>
|
||||
<div class="summary">
|
||||
<div class="summary-card"><h3>Total</h3><div class="value">{total}</div></div>
|
||||
<div class="summary-card"><h3 style="color: #38a169;">Succeeded</h3><div class="value" style="color: #38a169;">{succeeded}</div></div>
|
||||
<div class="summary-card"><h3 style="color: #e53e3e;">Failed</h3><div class="value" style="color: #e53e3e;">{failed}</div></div>
|
||||
<div class="summary-card"><h3 style="color: #3182ce;">Running</h3><div class="value" style="color: #3182ce;">{running}</div></div>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Task</th>
|
||||
<th>Model</th>
|
||||
<th>Status</th>
|
||||
<th>Duration</th>
|
||||
<th>Logs & Artifacts</th>
|
||||
<th>Prompts</th>
|
||||
<th>Error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{"".join(rows)}
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="margin-top: 20px; color: #718096; font-size: 12px; text-align: right;">
|
||||
Updated at: {data["updated_at"]}
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
async with aiofiles.open(html_file, 'w') as f:
|
||||
await f.write(html_content)
|
||||
|
||||
def get_state(self) -> ExecutionState:
|
||||
"""Get current execution state."""
|
||||
runs = list(self._runs.values())
|
||||
@@ -752,8 +938,9 @@ async def execute_single_run(
|
||||
try:
|
||||
result = await worktree_manager.collect_session_log(worktree_dir, output_dir)
|
||||
if result:
|
||||
session_log, session_id = result
|
||||
session_log, session_id, session_html = result
|
||||
run.session_log_file = str(session_log)
|
||||
run.session_html_file = str(session_html)
|
||||
run.session_id = session_id
|
||||
console.print(f"[dim]Session log saved: {session_log.name} (ID: {session_id})[/dim]")
|
||||
except Exception as e:
|
||||
@@ -765,6 +952,7 @@ async def execute_single_run(
|
||||
run.status,
|
||||
diff_file=run.diff_file,
|
||||
session_log_file=run.session_log_file,
|
||||
session_html_file=run.session_html_file,
|
||||
session_id=run.session_id,
|
||||
)
|
||||
|
||||
|
||||
Reference in new issue
Block a user