716 lines
20 KiB
TypeScript
716 lines
20 KiB
TypeScript
import {
|
||
getWaveformPreviewContent,
|
||
getWaveformPreviewScript,
|
||
} from "./waveformPreviewContent";
|
||
import {
|
||
getConversationHistoryBarContent,
|
||
getConversationHistoryBarStyles,
|
||
getConversationHistoryBarScript,
|
||
} from "./conversationHistoryBar";
|
||
import {
|
||
getInputAreaContent,
|
||
getInputAreaStyles,
|
||
getInputAreaScript,
|
||
} from "./inputArea";
|
||
import {
|
||
getMessageAreaContent,
|
||
getMessageAreaStyles,
|
||
getMessageAreaScript,
|
||
} from "./messageArea";
|
||
import { getAgentCardStyles, getAgentCardScript } from "./agentCard";
|
||
import {
|
||
getProgressBarContent,
|
||
getProgressBarStyles,
|
||
getProgressBarScript,
|
||
} from "./progressBar";
|
||
import { getCurrentEnv } from "../config/settings";
|
||
/**
|
||
* 获取 WebView 面板的 HTML 内容
|
||
*/
|
||
export function getWebviewContent(
|
||
iconUri?: string,
|
||
autoIconUri?: string,
|
||
liteIconUri?: string,
|
||
syIconUri?: string,
|
||
maxIconUri?: string
|
||
): string {
|
||
// 获取当前环境,只在 dev 和 test 环境下显示快速操作按钮
|
||
const currentEnv = getCurrentEnv();
|
||
const showQuickActions = currentEnv === 'dev' || currentEnv === 'test';
|
||
|
||
return `<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>IC Coder</title>
|
||
<style>
|
||
body {
|
||
font-family: var(--vscode-font-family);
|
||
background: var(--vscode-editor-background);
|
||
color: var(--vscode-foreground);
|
||
margin: 0;
|
||
padding: 0;
|
||
height: 100vh;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.header {
|
||
text-align: center;
|
||
padding: 20px;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
flex: 1;
|
||
}
|
||
.header.hidden {
|
||
display: none;
|
||
}
|
||
.header h1 {
|
||
color: var(--vscode-button-background);
|
||
margin: 0 0 8px 0;
|
||
}
|
||
.chat-container {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
padding: 0 20px 20px 20px;
|
||
}
|
||
${getMessageAreaStyles()}
|
||
${getAgentCardStyles()}
|
||
${getWaveformPreviewContent()}
|
||
${getConversationHistoryBarStyles()}
|
||
${getProgressBarStyles()}
|
||
${getInputAreaStyles()}
|
||
|
||
.file-editor-section {
|
||
margin-bottom: 15px;
|
||
padding: 15px;
|
||
background: var(--vscode-input-background);
|
||
border: 1px solid var(--vscode-input-border);
|
||
border-radius: 8px;
|
||
display: none;
|
||
flex-shrink: 0;
|
||
}
|
||
.file-editor-section.active {
|
||
display: block;
|
||
}
|
||
.file-editor-section h3 {
|
||
margin: 0 0 10px 0;
|
||
color: var(--vscode-button-background);
|
||
}
|
||
.file-editor-textarea {
|
||
width: 100%;
|
||
min-height: 300px;
|
||
padding: 10px;
|
||
background: var(--vscode-editor-background);
|
||
color: var(--vscode-editor-foreground);
|
||
border: 1px solid var(--vscode-input-border);
|
||
border-radius: 4px;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 12px;
|
||
resize: vertical;
|
||
}
|
||
.editor-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
/* 流式消息样式 */
|
||
.streaming .message-content {
|
||
border-right: 2px solid var(--vscode-focusBorder);
|
||
animation: blink 1s infinite;
|
||
}
|
||
@keyframes blink {
|
||
0%, 50% { border-color: var(--vscode-focusBorder); }
|
||
51%, 100% { border-color: transparent; }
|
||
}
|
||
|
||
/* 加载指示器样式 */
|
||
.loading-message {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 12px 16px;
|
||
color: var(--vscode-descriptionForeground);
|
||
}
|
||
.loading-dots {
|
||
display: flex;
|
||
gap: 4px;
|
||
}
|
||
.loading-dots span {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
background: var(--vscode-focusBorder);
|
||
animation: loadingDot 1.4s infinite ease-in-out;
|
||
}
|
||
.loading-dots span:nth-child(1) { animation-delay: 0s; }
|
||
.loading-dots span:nth-child(2) { animation-delay: 0.2s; }
|
||
.loading-dots span:nth-child(3) { animation-delay: 0.4s; }
|
||
@keyframes loadingDot {
|
||
0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; }
|
||
40% { transform: scale(1); opacity: 1; }
|
||
}
|
||
.loading-text {
|
||
font-size: 13px;
|
||
}
|
||
|
||
/* 工具状态样式 */
|
||
.tool-status {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 12px;
|
||
margin: 4px 0;
|
||
font-size: 12px;
|
||
border-radius: 6px;
|
||
background: var(--vscode-textBlockQuote-background);
|
||
}
|
||
.tool-status.tool-start {
|
||
border-left: 3px solid var(--vscode-charts-blue);
|
||
}
|
||
.tool-status.tool-complete {
|
||
border-left: 3px solid var(--vscode-charts-green);
|
||
}
|
||
.tool-status.tool-error {
|
||
border-left: 3px solid var(--vscode-charts-red);
|
||
}
|
||
.tool-icon {
|
||
font-size: 14px;
|
||
}
|
||
.tool-name {
|
||
font-weight: 500;
|
||
color: var(--vscode-foreground);
|
||
}
|
||
.tool-status-text {
|
||
color: var(--vscode-descriptionForeground);
|
||
}
|
||
.tool-detail {
|
||
margin-top: 4px;
|
||
font-size: 11px;
|
||
color: var(--vscode-descriptionForeground);
|
||
white-space: pre-wrap;
|
||
max-height: 100px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* 用户问题样式 */
|
||
.question-message {
|
||
padding: 16px;
|
||
}
|
||
.question-text {
|
||
margin-bottom: 12px;
|
||
font-weight: 500;
|
||
}
|
||
.question-options {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
.question-option {
|
||
padding: 8px 16px;
|
||
background: var(--vscode-button-secondaryBackground);
|
||
color: var(--vscode-button-secondaryForeground);
|
||
border: 1px solid var(--vscode-button-border);
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.question-option:hover {
|
||
background: var(--vscode-button-secondaryHoverBackground);
|
||
}
|
||
.question-option.selected {
|
||
background: var(--vscode-button-background);
|
||
color: var(--vscode-button-foreground);
|
||
}
|
||
.question-message.answered .question-option:not(.selected) {
|
||
opacity: 0.5;
|
||
pointer-events: none;
|
||
}
|
||
.custom-input-container {
|
||
display: flex;
|
||
gap: 8px;
|
||
width: 100%;
|
||
margin-top: 8px;
|
||
}
|
||
.custom-input {
|
||
flex: 1;
|
||
padding: 8px 12px;
|
||
background: var(--vscode-input-background);
|
||
color: var(--vscode-input-foreground);
|
||
border: 1px solid var(--vscode-input-border);
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
}
|
||
.custom-submit {
|
||
padding: 8px 16px;
|
||
background: var(--vscode-button-background);
|
||
color: var(--vscode-button-foreground);
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
}
|
||
.custom-submit:hover {
|
||
background: var(--vscode-button-hoverBackground);
|
||
}
|
||
.question-message.answered .custom-input-container {
|
||
display: none;
|
||
}
|
||
|
||
/* 分段消息样式 */
|
||
.segmented-message {
|
||
padding: 0;
|
||
}
|
||
.message-segment {
|
||
padding: 10px 22px;
|
||
}
|
||
.segment-text {
|
||
line-height: 1.6;
|
||
}
|
||
.segment-tool {
|
||
background: var(--vscode-textBlockQuote-background);
|
||
border-radius: 6px;
|
||
margin: 8px 0;
|
||
padding: 10px 14px;
|
||
}
|
||
.tool-segment-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 13px;
|
||
}
|
||
.tool-segment-icon {
|
||
font-size: 14px;
|
||
}
|
||
.tool-segment-name {
|
||
font-weight: 500;
|
||
color: var(--vscode-foreground);
|
||
}
|
||
.tool-segment-result {
|
||
margin-top: 6px;
|
||
font-size: 12px;
|
||
color: var(--vscode-descriptionForeground);
|
||
padding-left: 22px;
|
||
}
|
||
.segment-tool.tool-success {
|
||
border-left: 3px solid var(--vscode-charts-green);
|
||
}
|
||
.segment-tool.tool-error {
|
||
border-left: 3px solid var(--vscode-charts-red);
|
||
}
|
||
.segment-tool.tool-running {
|
||
border-left: 3px solid var(--vscode-charts-blue);
|
||
}
|
||
.segment-question {
|
||
background: var(--vscode-textBlockQuote-background);
|
||
border-radius: 6px;
|
||
margin: 8px 0;
|
||
padding: 12px 14px;
|
||
border-left: 3px solid var(--vscode-charts-orange);
|
||
}
|
||
.question-segment .question-text {
|
||
margin-bottom: 8px;
|
||
font-weight: 500;
|
||
}
|
||
.question-segment .question-options {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
}
|
||
.question-opt {
|
||
padding: 4px 10px;
|
||
background: var(--vscode-button-secondaryBackground);
|
||
color: var(--vscode-button-secondaryForeground);
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* 状态栏样式 */
|
||
.status-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 16px;
|
||
background: var(--vscode-textBlockQuote-background);
|
||
border-radius: 6px;
|
||
margin: 8px 0;
|
||
font-size: 13px;
|
||
color: var(--vscode-descriptionForeground);
|
||
}
|
||
.status-indicator {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: var(--vscode-charts-blue);
|
||
animation: statusPulse 1.5s ease-in-out infinite;
|
||
}
|
||
@keyframes statusPulse {
|
||
0%, 100% { opacity: 1; transform: scale(1); }
|
||
50% { opacity: 0.5; transform: scale(0.8); }
|
||
}
|
||
.status-bar.working .status-indicator {
|
||
background: var(--vscode-charts-orange);
|
||
}
|
||
.status-bar.success .status-indicator {
|
||
background: var(--vscode-charts-green);
|
||
animation: none;
|
||
}
|
||
.status-bar.error .status-indicator {
|
||
background: var(--vscode-charts-red);
|
||
animation: none;
|
||
}
|
||
|
||
/* 快捷操作按钮样式 */
|
||
.quick-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 15px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.quick-btn {
|
||
padding: 8px 16px;
|
||
background: var(--vscode-button-secondaryBackground);
|
||
color: var(--vscode-button-secondaryForeground);
|
||
border: 1px solid var(--vscode-button-border);
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
transition: all 0.2s;
|
||
}
|
||
.quick-btn:hover {
|
||
background: var(--vscode-button-secondaryHoverBackground);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
${getConversationHistoryBarContent()}
|
||
${getProgressBarContent()}
|
||
<div class="header">
|
||
<div style="display: flex; align-items: center; justify-content: center; gap: 10px;">
|
||
<img src="${iconUri}" alt="IC Coder" style="width: 28px; height: 28px;" />
|
||
<h1 style="margin: 0;">IC Coder</h1>
|
||
</div>
|
||
<p>专注于真实FPGA研发的Verilog智能体编程平台</p>
|
||
</div>
|
||
|
||
<div class="chat-container">
|
||
${getMessageAreaContent()}
|
||
|
||
<!-- 状态栏 -->
|
||
<div id="statusBar" class="status-bar" style="display: none;">
|
||
<div class="status-indicator"></div>
|
||
<span id="statusText">思考中...</span>
|
||
</div>
|
||
|
||
${showQuickActions ? `<div class="quick-actions">
|
||
<button class="quick-btn" onclick="quickAction('counter')">生成计数器</button>
|
||
<button class="quick-btn" onclick="quickAction('fsm')">生成状态机</button>
|
||
<button class="quick-btn" onclick="quickAction('testbench')">生成测试平台</button>
|
||
<button class="quick-btn" onclick="quickAction('explore')">知识探索</button>
|
||
</div>` : ''}
|
||
|
||
${getInputAreaContent(autoIconUri, liteIconUri, syIconUri, maxIconUri)}
|
||
</div>
|
||
|
||
<script>
|
||
console.log('[WebView] 脚本开始执行');
|
||
const vscode = acquireVsCodeApi();
|
||
console.log('[WebView] vscode API 已获取');
|
||
const messageInput = document.getElementById('messageInput');
|
||
const modeSelect = document.getElementById('modeSelect');
|
||
const messagesEl = document.getElementById('messages');
|
||
|
||
// 全局变量
|
||
let currentStreamingMessage = null;
|
||
let loadingIndicator = null;
|
||
let currentSegmentedMessage = null; // 当前分段消息容器
|
||
|
||
// ========== 模式选择器脚本(直接内联,避免模板字符串嵌套问题)==========
|
||
let currentMode = 'agent';
|
||
|
||
function toggleModeDropdown() {
|
||
const modeSelectEl = document.getElementById('modeSelect');
|
||
const modelSelectEl = document.getElementById('modelSelect');
|
||
if (modeSelectEl) {
|
||
modeSelectEl.classList.toggle('active');
|
||
if (modelSelectEl) {
|
||
modelSelectEl.classList.remove('active');
|
||
}
|
||
}
|
||
}
|
||
|
||
function selectMode(value, label) {
|
||
currentMode = value;
|
||
const modeValue = document.getElementById('modeValue');
|
||
const modeTooltip = document.getElementById('modeTooltip');
|
||
if (modeValue) {
|
||
modeValue.textContent = label;
|
||
}
|
||
if (modeTooltip) {
|
||
const tooltipMap = {
|
||
'plan': '只读模式 - 只能查询分析',
|
||
'ask': '逐个确认 - 每个写操作需确认',
|
||
'agent': '智能体自主模式',
|
||
'auto': '完全自动 - 所有操作自动执行'
|
||
};
|
||
modeTooltip.textContent = tooltipMap[value] || '切换模式';
|
||
}
|
||
const options = document.querySelectorAll('.mode-option');
|
||
options.forEach(option => {
|
||
if (option.getAttribute('data-value') === value) {
|
||
option.classList.add('selected');
|
||
} else {
|
||
option.classList.remove('selected');
|
||
}
|
||
});
|
||
const modeSelectEl = document.getElementById('modeSelect');
|
||
if (modeSelectEl) {
|
||
modeSelectEl.classList.remove('active');
|
||
}
|
||
}
|
||
|
||
function getCurrentMode() {
|
||
return currentMode;
|
||
}
|
||
|
||
document.addEventListener('click', (event) => {
|
||
const modeSelectEl = document.getElementById('modeSelect');
|
||
if (modeSelectEl && !modeSelectEl.contains(event.target)) {
|
||
modeSelectEl.classList.remove('active');
|
||
}
|
||
});
|
||
// ========== 模式选择器脚本结束 ==========
|
||
|
||
function quickAction(type) {
|
||
const questions = {
|
||
counter: '生成一个4位同步计数器',
|
||
fsm: '生成一个状态机',
|
||
testbench: '生成测试平台',
|
||
explore: '请启动知识探索智能体,分析当前项目结构'
|
||
};
|
||
if (questions[type]) {
|
||
messageInput.value = questions[type];
|
||
sendMessage();
|
||
}
|
||
}
|
||
|
||
// 检查 header 显示状态
|
||
function checkHeaderVisibility() {
|
||
const header = document.querySelector('.header');
|
||
const messages = document.getElementById('messages');
|
||
if (header && messages) {
|
||
if (messages.children.length > 0) {
|
||
header.classList.add('hidden');
|
||
} else {
|
||
header.classList.remove('hidden');
|
||
}
|
||
}
|
||
}
|
||
|
||
// 监听来自插件的消息
|
||
window.addEventListener('message', event => {
|
||
const message = event.data;
|
||
console.log('[WebView] 收到消息:', message.command, message);
|
||
|
||
switch (message.command) {
|
||
case 'receiveMessage':
|
||
// 接收完整消息
|
||
addMessage(message.text, 'bot');
|
||
break;
|
||
|
||
case 'updateStreamingMessage':
|
||
// 更新流式消息
|
||
updateOrCreateStreamingMessage(message.text);
|
||
break;
|
||
|
||
case 'updateSegments':
|
||
// 实时更新分段消息(按后端返回顺序)
|
||
console.log('[WebView] 实时更新段落, segments:', message.segments);
|
||
updateSegmentsRealtime(message.segments, message.isComplete);
|
||
// 如果对话完成,恢复发送按钮状态
|
||
if (message.isComplete && typeof setSendButtonState === 'function') {
|
||
setSendButtonState(false);
|
||
}
|
||
break;
|
||
|
||
case 'receiveSegments':
|
||
// 接收分段消息(兼容旧代码)
|
||
console.log('[WebView] 调用 renderSegments, segments:', message.segments);
|
||
renderSegments(message.segments);
|
||
break;
|
||
|
||
case 'toolStart':
|
||
// 工具开始执行
|
||
addToolStatus(message.toolName, 'start');
|
||
break;
|
||
|
||
case 'toolComplete':
|
||
// 工具执行完成
|
||
addToolStatus(message.toolName, 'complete', message.result);
|
||
break;
|
||
|
||
case 'toolError':
|
||
// 工具执行错误
|
||
addToolStatus(message.toolName, 'error', message.error);
|
||
break;
|
||
|
||
case 'updateStatus':
|
||
// 更新状态栏
|
||
const statusBar = document.getElementById('statusBar');
|
||
const statusText = document.getElementById('statusText');
|
||
if (statusBar && statusText) {
|
||
statusBar.style.display = 'flex';
|
||
statusText.textContent = message.text;
|
||
statusBar.className = 'status-bar ' + (message.type || '');
|
||
}
|
||
break;
|
||
|
||
case 'hideStatus':
|
||
// 隐藏状态栏
|
||
const statusBarHide = document.getElementById('statusBar');
|
||
if (statusBarHide) {
|
||
statusBarHide.style.display = 'none';
|
||
}
|
||
break;
|
||
|
||
case 'resetSegmentedMessage':
|
||
// 重置分段消息容器(停止对话时调用)
|
||
console.log('[WebView] 重置分段消息容器');
|
||
currentSegmentedMessage = null;
|
||
break;
|
||
|
||
case 'contextUsage':
|
||
// 更新上下文使用量显示
|
||
if (typeof updateContextDisplay === 'function') {
|
||
updateContextDisplay(message.currentTokens, message.maxTokens);
|
||
}
|
||
break;
|
||
|
||
case 'hideLoading':
|
||
// 隐藏加载指示器
|
||
hideLoadingIndicator();
|
||
break;
|
||
|
||
case 'workspaceStatus':
|
||
// 更新工作区状态
|
||
if (typeof hasWorkspace !== 'undefined') {
|
||
hasWorkspace = message.hasWorkspace;
|
||
console.log('[WebView] 工作区状态:', hasWorkspace);
|
||
}
|
||
break;
|
||
|
||
case 'vcdInfo':
|
||
// 渲染迷你波形预览信息
|
||
try {
|
||
if (message.containerId && typeof renderWaveformInfo === 'function') {
|
||
renderWaveformInfo(message.containerId, message.vcdInfo || {});
|
||
}
|
||
} catch (e) {
|
||
console.warn('[WebView] 渲染波形信息失败:', e);
|
||
}
|
||
break;
|
||
|
||
case 'vcdGenerated':
|
||
// VCD 文件生成成功,添加消息并附带波形预览
|
||
addMessage(message.text, 'bot');
|
||
try {
|
||
if (message.vcdFilePath) {
|
||
const lastMsg = messagesEl ? messagesEl.lastElementChild : null;
|
||
if (lastMsg && typeof addWaveformPreviewToMessage === 'function') {
|
||
addWaveformPreviewToMessage(lastMsg, message.vcdFilePath, message.fileName || 'waveform.vcd');
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.warn('[WebView] 添加波形预览失败:', e);
|
||
}
|
||
break;
|
||
|
||
case 'fileContent':
|
||
// 文件内容
|
||
addMessage('文件内容:\\n' + message.content, 'bot');
|
||
break;
|
||
|
||
case 'fileError':
|
||
// 文件错误
|
||
addMessage('❌ ' + message.error, 'bot');
|
||
break;
|
||
|
||
case 'showQuestion':
|
||
// 显示用户问题
|
||
showQuestion(message.askId, message.question, message.options);
|
||
break;
|
||
|
||
case 'conversationHistory':
|
||
// 渲染会话历史列表(支持分页)
|
||
renderConversationHistory({
|
||
items: message.items || [],
|
||
total: message.total || 0,
|
||
hasMore: message.hasMore || false
|
||
});
|
||
break;
|
||
|
||
case 'clearChat':
|
||
// 清空聊天界面
|
||
const messagesContainer = document.getElementById('messages');
|
||
if (messagesContainer) {
|
||
messagesContainer.innerHTML = '';
|
||
}
|
||
break;
|
||
|
||
case 'addUserMessage':
|
||
// 添加用户消息
|
||
if (message.text) {
|
||
addMessage(message.text, 'user');
|
||
}
|
||
break;
|
||
|
||
case 'addAiMessage':
|
||
// 添加AI消息
|
||
if (message.text) {
|
||
addMessage(message.text, 'bot');
|
||
}
|
||
break;
|
||
|
||
case 'switchMode':
|
||
// 切换运行模式(Plan 确认后自动切换到 Agent)
|
||
if (message.mode && typeof selectMode === 'function') {
|
||
const labelMap = {
|
||
'plan': 'Plan',
|
||
'ask': 'Ask',
|
||
'agent': 'Agent',
|
||
'auto': 'Auto'
|
||
};
|
||
selectMode(message.mode, labelMap[message.mode] || message.mode);
|
||
console.log('[WebView] 模式已切换到:', message.mode);
|
||
}
|
||
break;
|
||
|
||
case 'addMessage':
|
||
// 添加消息(通用)
|
||
if (message.text && message.sender) {
|
||
addMessage(message.text, message.sender);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
console.log('[WebView] 未处理的消息类型:', message.command);
|
||
}
|
||
});
|
||
|
||
${getMessageAreaScript()}
|
||
${getAgentCardScript()}
|
||
${getWaveformPreviewScript()}
|
||
${getConversationHistoryBarScript()}
|
||
${getProgressBarScript()}
|
||
${getInputAreaScript()}
|
||
</script></body>
|
||
</html>`;
|
||
}
|