597 lines
16 KiB
TypeScript
597 lines
16 KiB
TypeScript
import {
|
||
getUserInfoComponentContent,
|
||
getUserInfoComponentStyles,
|
||
getUserInfoComponentScript,
|
||
} from "./userInfoComponent";
|
||
import {
|
||
getMoreOptionsComponentContent,
|
||
getMoreOptionsComponentStyles,
|
||
getMoreOptionsComponentScript,
|
||
} from "./moreOptionsComponent";
|
||
import {
|
||
getSettingsComponentContent,
|
||
getSettingsComponentStyles,
|
||
getSettingsComponentScript,
|
||
} from "./settingsComponent";
|
||
import {
|
||
userAvatarIconSvg,
|
||
moreIconSvg,
|
||
setting,
|
||
} from "../constants/toolIcons";
|
||
|
||
/**
|
||
* 获取会话历史栏的 HTML 内容
|
||
*/
|
||
export function getConversationHistoryBarContent(): string {
|
||
return `
|
||
<div class="conversation-history-bar">
|
||
<div class="history-dropdown-container">
|
||
<button class="history-dropdown-button" onclick="toggleHistoryDropdown()">
|
||
<span class="dropdown-label">历史对话</span>
|
||
<svg class="dropdown-icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3 0.1-12.7-6.4-12.7z" fill="currentColor"/>
|
||
</svg>
|
||
</button>
|
||
|
||
<div class="history-dropdown-menu" id="historyDropdownMenu">
|
||
<div class="history-list" id="historyList">
|
||
<!-- 会话历史列表将在这里动态生成 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="right-actions">
|
||
<button class="new-conversation-button" onclick="createNewConversation()" title="新建对话">
|
||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" fill="currentColor"/>
|
||
</svg>
|
||
</button>
|
||
|
||
<div class="user-info-container" style="display: none;">
|
||
</div>
|
||
|
||
<div class='setting'>
|
||
<button class="setting-btn" title="设置" onclick="openSettingsModal()">
|
||
${setting}
|
||
</button>
|
||
</div>
|
||
|
||
<div class='more-container'>
|
||
<button class="more-button" title="更多选项" onclick="toggleMoreOptionsDropdown()">
|
||
${moreIconSvg}
|
||
</button>
|
||
${getMoreOptionsComponentContent()}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
${getSettingsComponentContent()}
|
||
`;
|
||
}
|
||
|
||
/**
|
||
* 获取会话历史栏的 CSS 样式
|
||
*/
|
||
export function getConversationHistoryBarStyles(): string {
|
||
return `
|
||
.conversation-history-bar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 8px 16px;
|
||
background: var(--vscode-tab-activeBackground);
|
||
border-bottom: 1px solid var(--vscode-panel-border);
|
||
flex-shrink: 0;
|
||
min-height: 35px;
|
||
}
|
||
|
||
.history-dropdown-container {
|
||
position: relative;
|
||
flex: 1;
|
||
}
|
||
|
||
.right-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.user-info-container {
|
||
position: relative;
|
||
}
|
||
|
||
.user-avatar-icon-button {
|
||
width: 30px;
|
||
height: 30px;
|
||
padding: 0;
|
||
background: transparent;
|
||
color: var(--vscode-foreground);
|
||
border: none;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s ease;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.user-avatar-icon-button:hover {
|
||
background: var(--vscode-toolbar-hoverBackground);
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.user-avatar-icon-button:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.user-avatar-icon-button.active {
|
||
background: var(--vscode-toolbar-hoverBackground);
|
||
}
|
||
|
||
.user-avatar-icon-button svg {
|
||
width: 20px;
|
||
height: 20px;
|
||
}
|
||
|
||
${getUserInfoComponentStyles()}
|
||
|
||
${getSettingsComponentStyles()}
|
||
|
||
.setting {
|
||
position: relative;
|
||
}
|
||
|
||
.setting-btn {
|
||
width: 30px;
|
||
height: 30px;
|
||
padding: 0;
|
||
background: transparent;
|
||
color: var(--vscode-foreground);
|
||
border: none;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s ease;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.setting-btn:hover {
|
||
background: var(--vscode-toolbar-hoverBackground);
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.setting-btn:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.more-container {
|
||
position: relative;
|
||
}
|
||
|
||
.more-button {
|
||
width: 30px;
|
||
height: 30px;
|
||
padding: 0;
|
||
background: transparent;
|
||
color: var(--vscode-foreground);
|
||
border: none;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s ease;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.more-button:hover {
|
||
background: var(--vscode-toolbar-hoverBackground);
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.more-button:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.more-button.active {
|
||
background: var(--vscode-toolbar-hoverBackground);
|
||
}
|
||
|
||
${getMoreOptionsComponentStyles()}
|
||
|
||
.history-dropdown-button {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 8px 12px;
|
||
background: transparent;
|
||
color: var(--vscode-foreground);
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.history-dropdown-button:hover {
|
||
background: var(--vscode-toolbar-hoverBackground);
|
||
}
|
||
|
||
.dropdown-label {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.dropdown-icon {
|
||
width: 12px;
|
||
height: 12px;
|
||
transition: transform 0.2s ease;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.history-dropdown-button.active .dropdown-icon {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
.history-dropdown-menu {
|
||
position: absolute;
|
||
top: calc(100% + 4px);
|
||
left: 0;
|
||
min-width: 300px;
|
||
max-height: 400px;
|
||
background: var(--vscode-dropdown-background);
|
||
border: 1px solid var(--vscode-dropdown-border);
|
||
border-radius: 4px;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
z-index: 1000;
|
||
display: none;
|
||
}
|
||
|
||
.history-dropdown-menu.active {
|
||
display: block;
|
||
}
|
||
|
||
.history-list {
|
||
padding: 4px 0;
|
||
}
|
||
|
||
.history-item {
|
||
padding: 10px 16px;
|
||
cursor: pointer;
|
||
transition: background 0.2s ease;
|
||
border-bottom: 1px solid var(--vscode-panel-border);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
|
||
.history-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.history-item:hover {
|
||
background: var(--vscode-list-hoverBackground);
|
||
}
|
||
|
||
.history-item-title {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
flex: 1;
|
||
}
|
||
|
||
.history-item-time {
|
||
font-size: 12px;
|
||
opacity: 0.7;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.history-empty {
|
||
padding: 20px;
|
||
text-align: center;
|
||
color: var(--vscode-descriptionForeground);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.history-load-more {
|
||
padding: 12px 16px;
|
||
text-align: center;
|
||
color: var(--vscode-descriptionForeground);
|
||
font-size: 12px;
|
||
border-top: 1px solid var(--vscode-panel-border);
|
||
}
|
||
|
||
.new-conversation-button {
|
||
width: 30px;
|
||
height: 30px;
|
||
padding: 0;
|
||
background: transparent;
|
||
color: var(--vscode-foreground);
|
||
border: none;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s ease;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.new-conversation-button:hover {
|
||
background: #007ACC;
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.new-conversation-button:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.new-conversation-button svg {
|
||
width: 20px;
|
||
height: 20px;
|
||
}
|
||
|
||
/* 滚动条样式 */
|
||
.history-dropdown-menu::-webkit-scrollbar {
|
||
width: 8px;
|
||
}
|
||
|
||
.history-dropdown-menu::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
.history-dropdown-menu::-webkit-scrollbar-thumb {
|
||
background: rgba(128, 128, 128, 0.5);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.history-dropdown-menu::-webkit-scrollbar-thumb:hover {
|
||
background: rgba(128, 128, 128, 0.7);
|
||
}
|
||
`;
|
||
}
|
||
|
||
/**
|
||
* 获取会话历史栏的 JavaScript 脚本
|
||
*/
|
||
export function getConversationHistoryBarScript(): string {
|
||
return `
|
||
${getUserInfoComponentScript()}
|
||
|
||
${getMoreOptionsComponentScript()}
|
||
|
||
${getSettingsComponentScript()}
|
||
|
||
// 更新用户头像图标按钮显示
|
||
function updateUserAvatarIconButton(userInfo) {
|
||
const userAvatarIconButton = document.getElementById('userAvatarIconButton');
|
||
|
||
if (userInfo && userInfo.nickname) {
|
||
// 显示用户头像图标按钮
|
||
if (userAvatarIconButton) {
|
||
userAvatarIconButton.style.display = 'flex';
|
||
}
|
||
// 同时更新用户详情弹窗的数据
|
||
if (typeof updateUserInfoDisplay === 'function') {
|
||
updateUserInfoDisplay(userInfo);
|
||
}
|
||
} else {
|
||
// 隐藏用户头像图标按钮
|
||
if (userAvatarIconButton) {
|
||
userAvatarIconButton.style.display = 'none';
|
||
}
|
||
}
|
||
}
|
||
|
||
// 会话历史相关变量
|
||
let conversationHistory = [];
|
||
let currentConversationId = null;
|
||
let currentOffset = 0;
|
||
let totalHistory = 0;
|
||
let hasMoreHistory = false;
|
||
let isLoadingHistory = false;
|
||
let currentLoadRequestId = 0; // 请求 ID,用于防止并发加载
|
||
const HISTORY_PAGE_SIZE = 10;
|
||
const MAX_HISTORY_ITEMS = 100;
|
||
|
||
// 切换历史记录下拉菜单
|
||
function toggleHistoryDropdown() {
|
||
const menu = document.getElementById('historyDropdownMenu');
|
||
const button = document.querySelector('.history-dropdown-button');
|
||
|
||
if (menu.classList.contains('active')) {
|
||
menu.classList.remove('active');
|
||
button.classList.remove('active');
|
||
} else {
|
||
menu.classList.add('active');
|
||
button.classList.add('active');
|
||
// 重置并加载会话历史
|
||
resetAndLoadHistory();
|
||
}
|
||
}
|
||
|
||
// 重置并加载会话历史
|
||
function resetAndLoadHistory() {
|
||
conversationHistory = [];
|
||
currentOffset = 0;
|
||
totalHistory = 0;
|
||
hasMoreHistory = false;
|
||
const historyList = document.getElementById('historyList');
|
||
if (historyList) {
|
||
historyList.innerHTML = '<div class="history-empty">加载中...</div>';
|
||
}
|
||
loadMoreHistory();
|
||
}
|
||
|
||
// 加载更多会话历史
|
||
function loadMoreHistory() {
|
||
if (isLoadingHistory || (currentOffset > 0 && !hasMoreHistory)) {
|
||
return;
|
||
}
|
||
|
||
// 检查是否已达到最大数量
|
||
if (currentOffset >= MAX_HISTORY_ITEMS) {
|
||
return;
|
||
}
|
||
|
||
// 生成新的请求 ID,用于防止并发加载
|
||
const requestId = ++currentLoadRequestId;
|
||
|
||
isLoadingHistory = true;
|
||
vscode.postMessage({
|
||
command: 'loadConversationHistory',
|
||
offset: currentOffset,
|
||
limit: HISTORY_PAGE_SIZE,
|
||
requestId: requestId
|
||
});
|
||
}
|
||
|
||
// 渲染会话历史列表(支持追加)
|
||
function renderConversationHistory(data) {
|
||
isLoadingHistory = false;
|
||
|
||
if (!data || !data.items) {
|
||
return;
|
||
}
|
||
|
||
// 追加新数据(去重)
|
||
const existingIds = new Set(conversationHistory.map(item => item.id));
|
||
const newItems = [];
|
||
for (const item of data.items) {
|
||
if (!existingIds.has(item.id)) {
|
||
existingIds.add(item.id);
|
||
newItems.push(item);
|
||
}
|
||
}
|
||
conversationHistory = conversationHistory.concat(newItems);
|
||
totalHistory = data.total;
|
||
hasMoreHistory = data.hasMore;
|
||
currentOffset = conversationHistory.length;
|
||
|
||
const historyList = document.getElementById('historyList');
|
||
if (!historyList) {
|
||
return;
|
||
}
|
||
|
||
// 如果没有任何历史记录
|
||
if (conversationHistory.length === 0) {
|
||
historyList.innerHTML = '<div class="history-empty">暂无会话历史</div>';
|
||
return;
|
||
}
|
||
|
||
// 渲染所有历史记录
|
||
historyList.innerHTML = conversationHistory.map(item => \`
|
||
<div class="history-item" onclick="selectConversation('\${item.id}')">
|
||
<div class="history-item-title">\${item.title || '未命名会话'}</div>
|
||
<div class="history-item-time">\${formatTime(item.timestamp)}</div>
|
||
</div>
|
||
\`).join('');
|
||
|
||
// 如果还有更多数据,添加"加载更多"提示
|
||
if (hasMoreHistory && currentOffset < MAX_HISTORY_ITEMS) {
|
||
historyList.innerHTML += \`
|
||
<div class="history-load-more" id="loadMoreIndicator">
|
||
<span>滚动加载更多...</span>
|
||
</div>
|
||
\`;
|
||
} else if (currentOffset >= MAX_HISTORY_ITEMS && hasMoreHistory) {
|
||
historyList.innerHTML += \`
|
||
<div class="history-load-more">
|
||
<span>已显示最近 \${MAX_HISTORY_ITEMS} 条记录</span>
|
||
</div>
|
||
\`;
|
||
}
|
||
}
|
||
|
||
// 选择会话
|
||
function selectConversation(conversationId) {
|
||
currentConversationId = conversationId;
|
||
vscode.postMessage({
|
||
command: 'selectConversation',
|
||
conversationId: conversationId
|
||
});
|
||
|
||
// 关闭下拉菜单
|
||
const menu = document.getElementById('historyDropdownMenu');
|
||
const button = document.querySelector('.history-dropdown-button');
|
||
menu.classList.remove('active');
|
||
button.classList.remove('active');
|
||
}
|
||
|
||
// 创建新会话
|
||
function createNewConversation() {
|
||
vscode.postMessage({ command: 'createNewConversation' });
|
||
}
|
||
|
||
// 格式化时间
|
||
function formatTime(timestamp) {
|
||
const date = new Date(timestamp);
|
||
const now = new Date();
|
||
const diff = now - date;
|
||
|
||
// 小于1分钟
|
||
if (diff < 60000) {
|
||
return '刚刚';
|
||
}
|
||
// 小于1小时
|
||
if (diff < 3600000) {
|
||
return Math.floor(diff / 60000) + '分钟前';
|
||
}
|
||
// 小于1天
|
||
if (diff < 86400000) {
|
||
return Math.floor(diff / 3600000) + '小时前';
|
||
}
|
||
// 小于7天
|
||
if (diff < 604800000) {
|
||
return Math.floor(diff / 86400000) + '天前';
|
||
}
|
||
|
||
// 超过7天显示具体日期
|
||
return date.toLocaleDateString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit'
|
||
});
|
||
}
|
||
|
||
// 监听下拉菜单滚动事件(防止重复注册)
|
||
const historyDropdownMenu = document.getElementById('historyDropdownMenu');
|
||
if (historyDropdownMenu && !historyDropdownMenu._scrollListenerAdded) {
|
||
historyDropdownMenu._scrollListenerAdded = true;
|
||
historyDropdownMenu.addEventListener('scroll', () => {
|
||
const menu = historyDropdownMenu;
|
||
const scrollTop = menu.scrollTop;
|
||
const scrollHeight = menu.scrollHeight;
|
||
const clientHeight = menu.clientHeight;
|
||
|
||
// 当滚动到距离底部 50px 时,加载更多
|
||
if (scrollHeight - scrollTop - clientHeight < 50) {
|
||
loadMoreHistory();
|
||
}
|
||
});
|
||
}
|
||
|
||
// 点击外部关闭下拉菜单
|
||
document.addEventListener('click', (event) => {
|
||
const container = document.querySelector('.history-dropdown-container');
|
||
const menu = document.getElementById('historyDropdownMenu');
|
||
const button = document.querySelector('.history-dropdown-button');
|
||
|
||
if (menu && menu.classList.contains('active')) {
|
||
if (!container.contains(event.target)) {
|
||
menu.classList.remove('active');
|
||
button.classList.remove('active');
|
||
}
|
||
}
|
||
});
|
||
`;
|
||
}
|