277 lines
8.2 KiB
TypeScript
277 lines
8.2 KiB
TypeScript
// @ts-nocheck
|
|
import {
|
|
formatTokenCount,
|
|
formatBytes,
|
|
formatPercentage,
|
|
formatRate,
|
|
formatResetTime,
|
|
formatQuotaValue,
|
|
quotaTypeLabel,
|
|
buildQuotaResetSummary,
|
|
isQuotaExceeded as isQuotaExceededUtil,
|
|
buildQuotaToastMessage
|
|
} from '../../utils/formatters';
|
|
|
|
export const resourceMethods = {
|
|
hasContainerStats() {
|
|
return !!(
|
|
this.containerStatus &&
|
|
this.containerStatus.mode === 'docker' &&
|
|
this.containerStatus.stats
|
|
);
|
|
},
|
|
|
|
containerStatusClass() {
|
|
if (!this.containerStatus) {
|
|
return 'status-pill--host';
|
|
}
|
|
if (this.containerStatus.mode !== 'docker') {
|
|
return 'status-pill--host';
|
|
}
|
|
const rawStatus = (
|
|
this.containerStatus.state &&
|
|
(this.containerStatus.state.status || this.containerStatus.state.Status)
|
|
) || '';
|
|
const status = String(rawStatus).toLowerCase();
|
|
if (status.includes('running')) {
|
|
return 'status-pill--running';
|
|
}
|
|
if (status.includes('paused')) {
|
|
return 'status-pill--stopped';
|
|
}
|
|
if (status.includes('exited') || status.includes('dead')) {
|
|
return 'status-pill--stopped';
|
|
}
|
|
return 'status-pill--running';
|
|
},
|
|
|
|
containerStatusText() {
|
|
if (!this.containerStatus) {
|
|
return '未知';
|
|
}
|
|
if (this.containerStatus.mode !== 'docker') {
|
|
return '宿主机模式';
|
|
}
|
|
const rawStatus = (
|
|
this.containerStatus.state &&
|
|
(this.containerStatus.state.status || this.containerStatus.state.Status)
|
|
) || '';
|
|
const status = String(rawStatus).toLowerCase();
|
|
if (status.includes('running')) {
|
|
return '运行中';
|
|
}
|
|
if (status.includes('paused')) {
|
|
return '已暂停';
|
|
}
|
|
if (status.includes('exited') || status.includes('dead')) {
|
|
return '已停止';
|
|
}
|
|
return rawStatus || '容器模式';
|
|
},
|
|
|
|
formatTime(value) {
|
|
if (!value) {
|
|
return '未知时间';
|
|
}
|
|
let date;
|
|
if (typeof value === 'number') {
|
|
date = new Date(value);
|
|
} else if (typeof value === 'string') {
|
|
const parsed = Date.parse(value);
|
|
if (!Number.isNaN(parsed)) {
|
|
date = new Date(parsed);
|
|
} else {
|
|
const numeric = Number(value);
|
|
if (!Number.isNaN(numeric)) {
|
|
date = new Date(numeric);
|
|
}
|
|
}
|
|
} else if (value instanceof Date) {
|
|
date = value;
|
|
}
|
|
if (!date || Number.isNaN(date.getTime())) {
|
|
return String(value);
|
|
}
|
|
const now = Date.now();
|
|
const diff = now - date.getTime();
|
|
if (diff < 60000) {
|
|
return '刚刚';
|
|
}
|
|
if (diff < 3600000) {
|
|
const mins = Math.floor(diff / 60000);
|
|
return `${mins} 分钟前`;
|
|
}
|
|
if (diff < 86400000) {
|
|
const hours = Math.floor(diff / 3600000);
|
|
return `${hours} 小时前`;
|
|
}
|
|
const formatter = new Intl.DateTimeFormat('zh-CN', {
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
return formatter.format(date);
|
|
},
|
|
|
|
async updateCurrentContextTokens() {
|
|
await this.resourceUpdateCurrentContextTokens(this.currentConversationId);
|
|
},
|
|
|
|
async fetchConversationTokenStatistics() {
|
|
await this.resourceFetchConversationTokenStatistics(this.currentConversationId);
|
|
},
|
|
|
|
toggleTokenPanel() {
|
|
this.resourceToggleTokenPanel();
|
|
},
|
|
|
|
applyStatusSnapshot(status) {
|
|
this.resourceApplyStatusSnapshot(status);
|
|
if (status && typeof status.thinking_mode !== 'undefined') {
|
|
this.thinkingMode = !!status.thinking_mode;
|
|
}
|
|
if (status && typeof status.run_mode === 'string') {
|
|
this.runMode = status.run_mode;
|
|
} else if (status && typeof status.thinking_mode !== 'undefined') {
|
|
this.runMode = status.thinking_mode ? 'thinking' : 'fast';
|
|
}
|
|
if (status && typeof status.model_key === 'string') {
|
|
this.modelSet(status.model_key);
|
|
}
|
|
if (status && typeof status.has_images !== 'undefined') {
|
|
this.conversationHasImages = !!status.has_images;
|
|
}
|
|
if (status && typeof status.has_videos !== 'undefined') {
|
|
this.conversationHasVideos = !!status.has_videos;
|
|
}
|
|
},
|
|
|
|
updateContainerStatus(status) {
|
|
this.resourceUpdateContainerStatus(status);
|
|
},
|
|
|
|
pollContainerStats() {
|
|
return this.resourcePollContainerStats();
|
|
},
|
|
|
|
startContainerStatsPolling() {
|
|
this.resourceStartContainerStatsPolling();
|
|
},
|
|
|
|
stopContainerStatsPolling() {
|
|
this.resourceStopContainerStatsPolling();
|
|
},
|
|
|
|
pollProjectStorage() {
|
|
return this.resourcePollProjectStorage();
|
|
},
|
|
|
|
startProjectStoragePolling() {
|
|
this.resourceStartProjectStoragePolling();
|
|
},
|
|
|
|
stopProjectStoragePolling() {
|
|
this.resourceStopProjectStoragePolling();
|
|
},
|
|
|
|
fetchUsageQuota() {
|
|
return this.resourceFetchUsageQuota();
|
|
},
|
|
|
|
startUsageQuotaPolling() {
|
|
this.resourceStartUsageQuotaPolling();
|
|
},
|
|
|
|
stopUsageQuotaPolling() {
|
|
this.resourceStopUsageQuotaPolling();
|
|
},
|
|
|
|
async downloadFile(path) {
|
|
if (!path) {
|
|
this.fileHideContextMenu();
|
|
return;
|
|
}
|
|
const url = `/api/download/file?path=${encodeURIComponent(path)}`;
|
|
const name = path.split('/').pop() || 'file';
|
|
await this.downloadResource(url, name);
|
|
},
|
|
|
|
async downloadFolder(path) {
|
|
if (!path) {
|
|
this.fileHideContextMenu();
|
|
return;
|
|
}
|
|
const url = `/api/download/folder?path=${encodeURIComponent(path)}`;
|
|
const segments = path.split('/').filter(Boolean);
|
|
const folderName = segments.length ? segments.pop() : 'folder';
|
|
await this.downloadResource(url, `${folderName}.zip`);
|
|
},
|
|
|
|
async downloadResource(url, filename) {
|
|
try {
|
|
const response = await fetch(url);
|
|
if (!response.ok) {
|
|
let message = response.statusText;
|
|
try {
|
|
const errorData = await response.json();
|
|
message = errorData.error || errorData.message || message;
|
|
} catch (err) {
|
|
message = await response.text();
|
|
}
|
|
this.uiPushToast({
|
|
title: '下载失败',
|
|
message: message || '无法完成下载',
|
|
type: 'error'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const blob = await response.blob();
|
|
const downloadName = filename || 'download';
|
|
const link = document.createElement('a');
|
|
const href = URL.createObjectURL(blob);
|
|
link.href = href;
|
|
link.download = downloadName;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
URL.revokeObjectURL(href);
|
|
} catch (error) {
|
|
console.error('下载失败:', error);
|
|
this.uiPushToast({
|
|
title: '下载失败',
|
|
message: error.message || String(error),
|
|
type: 'error'
|
|
});
|
|
} finally {
|
|
this.fileHideContextMenu();
|
|
}
|
|
},
|
|
|
|
formatTokenCount,
|
|
formatBytes,
|
|
formatPercentage,
|
|
formatRate,
|
|
formatResetTime,
|
|
formatQuotaValue,
|
|
quotaTypeLabel,
|
|
|
|
quotaResetSummary() {
|
|
return buildQuotaResetSummary(this.usageQuota);
|
|
},
|
|
|
|
isQuotaExceeded(type) {
|
|
return isQuotaExceededUtil(this.usageQuota, type);
|
|
},
|
|
|
|
showQuotaToast(payload) {
|
|
if (!payload) {
|
|
return;
|
|
}
|
|
const type = payload.type || 'fast';
|
|
const message = buildQuotaToastMessage(type, this.usageQuota, payload.reset_at);
|
|
this.uiShowQuotaToastMessage(message, type);
|
|
}
|
|
};
|