Merge remote-tracking branch 'origin/feat/plugin-front-end' into feat/back-to-front
This commit is contained in:
22
package.json
22
package.json
@ -70,27 +70,7 @@
|
|||||||
"id": "iccoder",
|
"id": "iccoder",
|
||||||
"label": "IC Coder"
|
"label": "IC Coder"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"configuration": {
|
|
||||||
"title": "IC Coder",
|
|
||||||
"properties": {
|
|
||||||
"icCoder.backendUrl": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "http://192.168.1.108:2233",
|
|
||||||
"description": "后端服务地址"
|
|
||||||
},
|
|
||||||
"icCoder.timeout": {
|
|
||||||
"type": "number",
|
|
||||||
"default": 60000,
|
|
||||||
"description": "请求超时时间(毫秒)"
|
|
||||||
},
|
|
||||||
"icCoder.userId": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "default-user",
|
|
||||||
"description": "用户ID(临时配置)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"vscode:prepublish": "pnpm run package",
|
"vscode:prepublish": "pnpm run package",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* 配置管理
|
* 配置管理
|
||||||
* 从 VSCode 设置读取配置项
|
* 后端地址已预配置,用户无需手动设置
|
||||||
*/
|
*/
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ export interface IccoderConfig {
|
|||||||
userId: string;
|
userId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 默认配置 */
|
/** 默认配置(预配置,不暴露给用户) */
|
||||||
const DEFAULT_CONFIG: IccoderConfig = {
|
const DEFAULT_CONFIG: IccoderConfig = {
|
||||||
backendUrl: "http://192.168.1.108:2233",
|
backendUrl: "http://192.168.1.108:2233",
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
@ -23,15 +23,10 @@ const DEFAULT_CONFIG: IccoderConfig = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取配置项
|
* 获取配置项
|
||||||
|
* 直接返回预配置的值,用户无需手动配置
|
||||||
*/
|
*/
|
||||||
export function getConfig(): IccoderConfig {
|
export function getConfig(): IccoderConfig {
|
||||||
const config = vscode.workspace.getConfiguration("icCoder");
|
return { ...DEFAULT_CONFIG };
|
||||||
|
|
||||||
return {
|
|
||||||
backendUrl: config.get<string>("backendUrl", DEFAULT_CONFIG.backendUrl),
|
|
||||||
timeout: config.get<number>("timeout", DEFAULT_CONFIG.timeout),
|
|
||||||
userId: config.get<string>("userId", DEFAULT_CONFIG.userId),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -434,30 +434,64 @@ export class DialogSession {
|
|||||||
onToolConfirm: async (data: ToolConfirmEvent) => {
|
onToolConfirm: async (data: ToolConfirmEvent) => {
|
||||||
console.log('[DialogSession] onToolConfirm:', data.toolName, data.confirmId);
|
console.log('[DialogSession] onToolConfirm:', data.toolName, data.confirmId);
|
||||||
|
|
||||||
// 调用回调通知 UI 显示确认对话框
|
// 结束当前文本段落
|
||||||
|
this.finalizeTextSegment();
|
||||||
|
|
||||||
|
// 生成工具描述
|
||||||
|
const toolDescription = this.getToolDescription(data.toolName, data.toolInput);
|
||||||
|
|
||||||
|
// 构建问题文本
|
||||||
|
const toolNameMap: Record<string, string> = {
|
||||||
|
'file_write': '写入文件',
|
||||||
|
'file_delete': '删除文件',
|
||||||
|
'syntax_check': '语法检查',
|
||||||
|
'simulation': '运行仿真'
|
||||||
|
};
|
||||||
|
const toolDisplayName = toolNameMap[data.toolName] || data.toolName;
|
||||||
|
const question = `确认执行操作:${toolDisplayName}\n\n${toolDescription}`;
|
||||||
|
|
||||||
|
// 生成唯一的 askId
|
||||||
|
const askId = `tool_confirm_${data.confirmId}`;
|
||||||
|
|
||||||
|
// 添加问题段落到聊天界面
|
||||||
|
this.segments.push({
|
||||||
|
type: 'question',
|
||||||
|
askId: askId,
|
||||||
|
question: question,
|
||||||
|
options: ['确认执行', '取消']
|
||||||
|
});
|
||||||
|
|
||||||
|
// 实时发送段落更新
|
||||||
|
callbacks.onSegmentUpdate?.(this.segments);
|
||||||
|
|
||||||
|
// 调用回调通知 UI
|
||||||
callbacks.onToolConfirm?.(data.confirmId, data.toolName, data.toolInput);
|
callbacks.onToolConfirm?.(data.confirmId, data.toolName, data.toolInput);
|
||||||
|
|
||||||
// 使用 VSCode 快速选择框显示确认对话框
|
// 使用 userInteractionManager 等待用户回答
|
||||||
const toolDescription = this.getToolDescription(data.toolName, data.toolInput);
|
|
||||||
const result = await vscode.window.showWarningMessage(
|
|
||||||
`确认执行操作: ${data.toolName}`,
|
|
||||||
{ modal: true, detail: toolDescription },
|
|
||||||
'确认执行',
|
|
||||||
'取消'
|
|
||||||
);
|
|
||||||
|
|
||||||
const approved = result === '确认执行';
|
|
||||||
console.log('[DialogSession] 用户确认结果:', approved);
|
|
||||||
|
|
||||||
// 发送确认响应到后端
|
|
||||||
try {
|
try {
|
||||||
await submitToolConfirm({
|
await userInteractionManager.handleAskUser(
|
||||||
confirmId: data.confirmId,
|
{
|
||||||
taskId: this.taskId,
|
askId: askId,
|
||||||
approved
|
question: question,
|
||||||
});
|
options: ['确认执行', '取消']
|
||||||
|
} as AskUserEvent,
|
||||||
|
this.taskId
|
||||||
|
);
|
||||||
|
|
||||||
|
// 注意:用户回答后,需要在 receiveAnswer 中处理 tool_confirm 类型的 askId
|
||||||
|
// 这里不直接调用 submitToolConfirm,而是在 userInteractionManager 中统一处理
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[DialogSession] 发送确认响应失败:', error);
|
console.error('[DialogSession] 处理工具确认失败:', error);
|
||||||
|
// 如果出错,默认取消执行
|
||||||
|
try {
|
||||||
|
await submitToolConfirm({
|
||||||
|
confirmId: data.confirmId,
|
||||||
|
taskId: this.taskId,
|
||||||
|
approved: false
|
||||||
|
});
|
||||||
|
} catch (submitError) {
|
||||||
|
console.error('[DialogSession] 发送取消响应失败:', submitError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* 处理 ask_user 事件,通过 WebView 显示问题并收集用户回答
|
* 处理 ask_user 事件,通过 WebView 显示问题并收集用户回答
|
||||||
*/
|
*/
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { submitAnswer } from './apiClient';
|
import { submitAnswer, submitToolConfirm } from './apiClient';
|
||||||
import type { AskUserEvent, AnswerRequest } from '../types/api';
|
import type { AskUserEvent, AnswerRequest } from '../types/api';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -114,21 +114,46 @@ export class UserInteractionManager {
|
|||||||
taskId: string,
|
taskId: string,
|
||||||
answer: string
|
answer: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const request: AnswerRequest = {
|
// 检查是否是工具确认类型的问题
|
||||||
askId,
|
if (askId.startsWith('tool_confirm_')) {
|
||||||
taskId,
|
// 提取 confirmId
|
||||||
customInput: answer
|
const confirmId = parseInt(askId.replace('tool_confirm_', ''));
|
||||||
};
|
const approved = answer === '确认执行';
|
||||||
|
|
||||||
try {
|
console.log(`[UserInteraction] 提交工具确认: confirmId=${confirmId}, approved=${approved}`);
|
||||||
const response = await submitAnswer(request);
|
|
||||||
if (!response.success) {
|
try {
|
||||||
throw new Error(response.error || '提交回答失败');
|
const response = await submitToolConfirm({
|
||||||
|
confirmId,
|
||||||
|
taskId,
|
||||||
|
approved
|
||||||
|
});
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.error || '提交工具确认失败');
|
||||||
|
}
|
||||||
|
console.log(`[UserInteraction] 工具确认已提交: confirmId=${confirmId}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[UserInteraction] 提交工具确认失败: confirmId=${confirmId}`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 普通问题回答
|
||||||
|
const request: AnswerRequest = {
|
||||||
|
askId,
|
||||||
|
taskId,
|
||||||
|
customInput: answer
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await submitAnswer(request);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.error || '提交回答失败');
|
||||||
|
}
|
||||||
|
console.log(`[UserInteraction] 回答已提交: askId=${askId}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[UserInteraction] 提交回答失败: askId=${askId}`, error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
console.log(`[UserInteraction] 回答已提交: askId=${askId}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`[UserInteraction] 提交回答失败: askId=${askId}`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* 模式选择器组件
|
* 模式选择器组件
|
||||||
* 提供 Plan/Ask/Agent/Auto 四种模式的选择功能
|
* 提供 Plan/Ask/Agent 四种模式的选择功能
|
||||||
*
|
*
|
||||||
* 模式说明:
|
* 模式说明:
|
||||||
* - Plan: 只读模式,只能查询分析,不能写文件
|
* - Plan: 只读模式,只能查询分析,不能写文件
|
||||||
* - Ask: 逐个确认,每个写操作需用户确认
|
* - Ask: 逐个确认,每个写操作需用户确认
|
||||||
* - Agent: 智能体自主,自动执行大部分操作
|
* - Agent: 智能体自主,自动执行大部分操作
|
||||||
* - Auto: 完全自动,所有操作自动执行
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,10 +34,6 @@ export function getModeSelectorContent(): string {
|
|||||||
<span class="mode-option-label">Agent</span>
|
<span class="mode-option-label">Agent</span>
|
||||||
<span class="mode-option-desc">智能体自主</span>
|
<span class="mode-option-desc">智能体自主</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mode-option" data-value="auto" onclick="selectMode('auto', 'Auto')">
|
|
||||||
<span class="mode-option-label">Auto</span>
|
|
||||||
<span class="mode-option-desc">完全自动</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="tooltiptext" id="modeTooltip">智能体自主模式</span>
|
<span class="tooltiptext" id="modeTooltip">智能体自主模式</span>
|
||||||
@ -164,8 +159,7 @@ export function getModeSelectorScript(): string {
|
|||||||
const tooltipMap = {
|
const tooltipMap = {
|
||||||
'plan': '只读模式 - 只能查询分析',
|
'plan': '只读模式 - 只能查询分析',
|
||||||
'ask': '逐个确认 - 每个写操作需确认',
|
'ask': '逐个确认 - 每个写操作需确认',
|
||||||
'agent': '智能体自主模式',
|
'agent': '智能体自主模式','
|
||||||
'auto': '完全自动 - 所有操作自动执行'
|
|
||||||
};
|
};
|
||||||
modeTooltip.textContent = tooltipMap[value] || '切换模式';
|
modeTooltip.textContent = tooltipMap[value] || '切换模式';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -410,7 +410,7 @@ export function getMessageAreaStyles(): string {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.tool-segment-header.collapsed .tool-collapse-icon {
|
.tool-segment-header.collapsed .tool-collapse-icon {
|
||||||
transform: rotate(0deg);
|
transform: rotate(-90deg);
|
||||||
}
|
}
|
||||||
.tool-segment-header:not(.collapsed) .tool-collapse-icon {
|
.tool-segment-header:not(.collapsed) .tool-collapse-icon {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
@ -890,6 +890,9 @@ export function getMessageAreaScript(): string {
|
|||||||
// 存储已回答问题的状态
|
// 存储已回答问题的状态
|
||||||
const answeredQuestions = new Map(); // askId -> answer
|
const answeredQuestions = new Map(); // askId -> answer
|
||||||
|
|
||||||
|
// 存储工具展开/折叠状态
|
||||||
|
const toolCollapseStates = new Map(); // index -> isCollapsed
|
||||||
|
|
||||||
// 实时更新分段消息(按后端返回顺序)
|
// 实时更新分段消息(按后端返回顺序)
|
||||||
function updateSegmentsRealtime(segments, isComplete) {
|
function updateSegmentsRealtime(segments, isComplete) {
|
||||||
console.log('[WebView] updateSegmentsRealtime 被调用, segments:', segments, 'isComplete:', isComplete);
|
console.log('[WebView] updateSegmentsRealtime 被调用, segments:', segments, 'isComplete:', isComplete);
|
||||||
@ -921,9 +924,19 @@ export function getMessageAreaScript(): string {
|
|||||||
messagesEl.appendChild(currentSegmentedMessage);
|
messagesEl.appendChild(currentSegmentedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保存当前所有工具的展开/折叠状态
|
||||||
|
if (currentSegmentedMessage) {
|
||||||
|
const toolHeaders = currentSegmentedMessage.querySelectorAll('.tool-segment-header[data-collapsible="true"]');
|
||||||
|
toolHeaders.forEach((header, idx) => {
|
||||||
|
const isCollapsed = header.classList.contains('collapsed');
|
||||||
|
toolCollapseStates.set(idx, isCollapsed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 清空容器并重新渲染所有段落
|
// 清空容器并重新渲染所有段落
|
||||||
currentSegmentedMessage.innerHTML = '';
|
currentSegmentedMessage.innerHTML = '';
|
||||||
|
|
||||||
|
let toolIndex = 0; // 用于跟踪工具段落的索引
|
||||||
segments.forEach((segment, index) => {
|
segments.forEach((segment, index) => {
|
||||||
const segmentDiv = document.createElement('div');
|
const segmentDiv = document.createElement('div');
|
||||||
segmentDiv.className = 'message-segment segment-' + segment.type;
|
segmentDiv.className = 'message-segment segment-' + segment.type;
|
||||||
@ -942,13 +955,19 @@ export function getMessageAreaScript(): string {
|
|||||||
// 检查工具结果是否过长(超过一行显示不下)
|
// 检查工具结果是否过长(超过一行显示不下)
|
||||||
const shouldCollapse = toolResult && toolResult.length > 60;
|
const shouldCollapse = toolResult && toolResult.length > 60;
|
||||||
|
|
||||||
|
// 恢复之前保存的展开/折叠状态
|
||||||
|
const savedState = toolCollapseStates.get(toolIndex);
|
||||||
|
const isCollapsed = savedState !== undefined ? savedState : shouldCollapse;
|
||||||
|
const currentToolIndex = toolIndex;
|
||||||
|
toolIndex++; // 递增工具索引
|
||||||
|
|
||||||
segmentDiv.innerHTML = \`
|
segmentDiv.innerHTML = \`
|
||||||
<div class="tool-segment-header\${shouldCollapse ? ' collapsed' : ''}" data-collapsible="\${shouldCollapse}">
|
<div class="tool-segment-header\${isCollapsed ? ' collapsed' : ''}" data-collapsible="\${shouldCollapse}" data-tool-index="\${currentToolIndex}">
|
||||||
\${shouldCollapse ? collapseIconSvg : getToolIcon(segment.toolName)}
|
\${shouldCollapse ? \`<span class="tool-collapse-icon">\${collapseIconSvg}</span>\` : getToolIcon(segment.toolName)}
|
||||||
<span class="tool-segment-name">\${getToolDisplayName(segment.toolName) || '工具'}</span>
|
<span class="tool-segment-name">\${getToolDisplayName(segment.toolName) || '工具'}</span>
|
||||||
\${toolResult && !shouldCollapse ? \`<span class="tool-segment-result">\${toolResult}</span>\` : ''}
|
\${toolResult && !shouldCollapse ? \`<span class="tool-segment-result">\${toolResult}</span>\` : ''}
|
||||||
</div>
|
</div>
|
||||||
\${shouldCollapse ? \`<div class="tool-segment-content collapsed"><span class="tool-segment-result" style="display:block;white-space:pre-wrap;max-width:100%;margin-top:8px;margin-left:18px;">\${toolResult}</span></div>\` : ''}
|
\${shouldCollapse ? \`<div class="tool-segment-content\${isCollapsed ? ' collapsed' : ''}" style="max-height:\${isCollapsed ? '0' : 'none'}"><span class="tool-segment-result" style="display:block;white-space:pre-wrap;max-width:100%;margin-top:8px;margin-left:18px;">\${toolResult}</span></div>\` : ''}
|
||||||
\`;
|
\`;
|
||||||
|
|
||||||
// 如果是仿真工具且成功完成,尝试添加波形预览
|
// 如果是仿真工具且成功完成,尝试添加波形预览
|
||||||
@ -974,27 +993,24 @@ export function getMessageAreaScript(): string {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const header = segmentDiv.querySelector('.tool-segment-header');
|
const header = segmentDiv.querySelector('.tool-segment-header');
|
||||||
const content = segmentDiv.querySelector('.tool-segment-content');
|
const content = segmentDiv.querySelector('.tool-segment-content');
|
||||||
const iconCollapsed = segmentDiv.querySelector('.icon-collapsed');
|
|
||||||
const iconExpanded = segmentDiv.querySelector('.icon-expanded');
|
|
||||||
|
|
||||||
if (header && content) {
|
if (header && content) {
|
||||||
header.addEventListener('click', function() {
|
header.addEventListener('click', function() {
|
||||||
const isCollapsed = header.classList.contains('collapsed');
|
const isCollapsed = header.classList.contains('collapsed');
|
||||||
|
const toolIdx = parseInt(header.getAttribute('data-tool-index') || '0');
|
||||||
|
|
||||||
if (isCollapsed) {
|
if (isCollapsed) {
|
||||||
// 展开
|
// 展开
|
||||||
header.classList.remove('collapsed');
|
header.classList.remove('collapsed');
|
||||||
content.classList.remove('collapsed');
|
content.classList.remove('collapsed');
|
||||||
content.style.maxHeight = content.scrollHeight + 'px';
|
content.style.maxHeight = content.scrollHeight + 'px';
|
||||||
if (iconCollapsed) iconCollapsed.style.display = 'none';
|
toolCollapseStates.set(toolIdx, false);
|
||||||
if (iconExpanded) iconExpanded.style.display = 'block';
|
|
||||||
} else {
|
} else {
|
||||||
// 折叠
|
// 折叠
|
||||||
header.classList.add('collapsed');
|
header.classList.add('collapsed');
|
||||||
content.classList.add('collapsed');
|
content.classList.add('collapsed');
|
||||||
content.style.maxHeight = '0';
|
content.style.maxHeight = '0';
|
||||||
if (iconCollapsed) iconCollapsed.style.display = 'block';
|
toolCollapseStates.set(toolIdx, true);
|
||||||
if (iconExpanded) iconExpanded.style.display = 'none';
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1149,7 +1165,7 @@ export function getMessageAreaScript(): string {
|
|||||||
|
|
||||||
segmentDiv.innerHTML = \`
|
segmentDiv.innerHTML = \`
|
||||||
<div class="tool-segment-header\${shouldCollapse ? ' collapsed' : ''}" data-collapsible="\${shouldCollapse}">
|
<div class="tool-segment-header\${shouldCollapse ? ' collapsed' : ''}" data-collapsible="\${shouldCollapse}">
|
||||||
\${shouldCollapse ? collapseIconSvg : getToolIcon(segment.toolName)}
|
\${shouldCollapse ? \`<span class="icon-collapsed" style="display:block;width:16px;height:16px;flex-shrink:0;">\${collapseIconSvg}</span><span class="icon-expanded" style="display:none;width:16px;height:16px;flex-shrink:0;">\${collapseIconSvg}</span>\` : getToolIcon(segment.toolName)}
|
||||||
<span class="tool-segment-name">\${getToolDisplayName(segment.toolName) || '工具'}</span>
|
<span class="tool-segment-name">\${getToolDisplayName(segment.toolName) || '工具'}</span>
|
||||||
\${toolResult && !shouldCollapse ? \`<span class="tool-segment-result">\${toolResult}</span>\` : ''}
|
\${toolResult && !shouldCollapse ? \`<span class="tool-segment-result">\${toolResult}</span>\` : ''}
|
||||||
</div>
|
</div>
|
||||||
@ -1179,27 +1195,24 @@ export function getMessageAreaScript(): string {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const header = segmentDiv.querySelector('.tool-segment-header');
|
const header = segmentDiv.querySelector('.tool-segment-header');
|
||||||
const content = segmentDiv.querySelector('.tool-segment-content');
|
const content = segmentDiv.querySelector('.tool-segment-content');
|
||||||
const iconCollapsed = segmentDiv.querySelector('.icon-collapsed');
|
|
||||||
const iconExpanded = segmentDiv.querySelector('.icon-expanded');
|
|
||||||
|
|
||||||
if (header && content) {
|
if (header && content) {
|
||||||
header.addEventListener('click', function() {
|
header.addEventListener('click', function() {
|
||||||
const isCollapsed = header.classList.contains('collapsed');
|
const isCollapsed = header.classList.contains('collapsed');
|
||||||
|
const toolIdx = parseInt(header.getAttribute('data-tool-index') || '0');
|
||||||
|
|
||||||
if (isCollapsed) {
|
if (isCollapsed) {
|
||||||
// 展开
|
// 展开
|
||||||
header.classList.remove('collapsed');
|
header.classList.remove('collapsed');
|
||||||
content.classList.remove('collapsed');
|
content.classList.remove('collapsed');
|
||||||
content.style.maxHeight = content.scrollHeight + 'px';
|
content.style.maxHeight = content.scrollHeight + 'px';
|
||||||
if (iconCollapsed) iconCollapsed.style.display = 'none';
|
toolCollapseStates.set(toolIdx, false);
|
||||||
if (iconExpanded) iconExpanded.style.display = 'block';
|
|
||||||
} else {
|
} else {
|
||||||
// 折叠
|
// 折叠
|
||||||
header.classList.add('collapsed');
|
header.classList.add('collapsed');
|
||||||
content.classList.add('collapsed');
|
content.classList.add('collapsed');
|
||||||
content.style.maxHeight = '0';
|
content.style.maxHeight = '0';
|
||||||
if (iconCollapsed) iconCollapsed.style.display = 'block';
|
toolCollapseStates.set(toolIdx, true);
|
||||||
if (iconExpanded) iconExpanded.style.display = 'none';
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user