增加opus开启标识
This commit is contained in:
@@ -1002,6 +1002,7 @@ pub struct PackycodeUserQuota {
|
|||||||
pub plan_expires_at: String, // 计划过期时间
|
pub plan_expires_at: String, // 计划过期时间
|
||||||
pub username: Option<String>, // 用户名
|
pub username: Option<String>, // 用户名
|
||||||
pub email: Option<String>, // 邮箱
|
pub email: Option<String>, // 邮箱
|
||||||
|
pub opus_enabled: Option<bool>, // 是否启用Opus模型
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取 PackyCode 用户信息(额度等)
|
/// 获取 PackyCode 用户信息(额度等)
|
||||||
@@ -1044,23 +1045,37 @@ pub async fn packycode_get_user_quota(station_id: String, db: State<'_, AgentDb>
|
|||||||
"https://www.packycode.com/api/backend/users/info"
|
"https://www.packycode.com/api/backend/users/info"
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建 HTTP 客户端
|
// 创建 HTTP 客户端 - 禁用系统代理
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
.timeout(Duration::from_secs(10))
|
.timeout(Duration::from_secs(30))
|
||||||
|
.no_proxy() // 禁用所有代理
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| format!("创建 HTTP 客户端失败: {}", e))?;
|
.map_err(|e| format!("创建 HTTP 客户端失败: {}", e))?;
|
||||||
|
|
||||||
// 发送请求
|
log::info!("正在请求 PackyCode 用户信息: {}", url);
|
||||||
|
log::debug!("使用 Token: {}...", &station.system_token[..10.min(station.system_token.len())]);
|
||||||
|
|
||||||
|
// 发送请求 - 只使用必要的请求头
|
||||||
let response = client
|
let response = client
|
||||||
.get(url)
|
.get(url)
|
||||||
.header("Authorization", format!("Bearer {}", station.system_token))
|
.header("Authorization", format!("Bearer {}", station.system_token))
|
||||||
.header("User-Agent", "Apifox/1.0.0 (https://apifox.com)")
|
.header("User-Agent", "Claudia")
|
||||||
.header("Accept", "*/*")
|
.header("Accept", "*/*")
|
||||||
.header("Host", if url.contains("share.packycode.com") { "share.packycode.com" } else { "www.packycode.com" })
|
|
||||||
.header("Connection", "keep-alive")
|
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("请求失败: {}", e))?;
|
.map_err(|e| {
|
||||||
|
log::error!("请求 PackyCode API 失败: {}", e);
|
||||||
|
// 提供更详细的错误信息
|
||||||
|
if e.is_connect() {
|
||||||
|
format!("网络连接失败: {}", e)
|
||||||
|
} else if e.is_timeout() {
|
||||||
|
format!("请求超时: {}", e)
|
||||||
|
} else if e.is_request() {
|
||||||
|
format!("请求错误: {}", e)
|
||||||
|
} else {
|
||||||
|
format!("请求失败: {}", e)
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
// 检查响应状态
|
// 检查响应状态
|
||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
@@ -1082,28 +1097,86 @@ pub async fn packycode_get_user_quota(station_id: String, db: State<'_, AgentDb>
|
|||||||
// 提取额度信息
|
// 提取额度信息
|
||||||
let quota = PackycodeUserQuota {
|
let quota = PackycodeUserQuota {
|
||||||
daily_budget_usd: response_data.get("daily_budget_usd")
|
daily_budget_usd: response_data.get("daily_budget_usd")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| {
|
||||||
.and_then(|s| s.parse::<f64>().ok())
|
if v.is_string() {
|
||||||
|
v.as_str().and_then(|s| s.parse::<f64>().ok())
|
||||||
|
} else if v.is_f64() {
|
||||||
|
v.as_f64()
|
||||||
|
} else if v.is_i64() {
|
||||||
|
v.as_i64().map(|i| i as f64)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.unwrap_or(0.0),
|
.unwrap_or(0.0),
|
||||||
daily_spent_usd: response_data.get("daily_spent_usd")
|
daily_spent_usd: response_data.get("daily_spent_usd")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| {
|
||||||
.and_then(|s| s.parse::<f64>().ok())
|
if v.is_null() {
|
||||||
|
Some(0.0)
|
||||||
|
} else if v.is_string() {
|
||||||
|
v.as_str().and_then(|s| s.parse::<f64>().ok())
|
||||||
|
} else if v.is_f64() {
|
||||||
|
v.as_f64()
|
||||||
|
} else if v.is_i64() {
|
||||||
|
v.as_i64().map(|i| i as f64)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.unwrap_or(0.0),
|
.unwrap_or(0.0),
|
||||||
monthly_budget_usd: response_data.get("monthly_budget_usd")
|
monthly_budget_usd: response_data.get("monthly_budget_usd")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| {
|
||||||
.and_then(|s| s.parse::<f64>().ok())
|
if v.is_string() {
|
||||||
|
v.as_str().and_then(|s| s.parse::<f64>().ok())
|
||||||
|
} else if v.is_f64() {
|
||||||
|
v.as_f64()
|
||||||
|
} else if v.is_i64() {
|
||||||
|
v.as_i64().map(|i| i as f64)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.unwrap_or(0.0),
|
.unwrap_or(0.0),
|
||||||
monthly_spent_usd: response_data.get("monthly_spent_usd")
|
monthly_spent_usd: response_data.get("monthly_spent_usd")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| {
|
||||||
.and_then(|s| s.parse::<f64>().ok())
|
if v.is_null() {
|
||||||
|
Some(0.0)
|
||||||
|
} else if v.is_string() {
|
||||||
|
v.as_str().and_then(|s| s.parse::<f64>().ok())
|
||||||
|
} else if v.is_f64() {
|
||||||
|
v.as_f64()
|
||||||
|
} else if v.is_i64() {
|
||||||
|
v.as_i64().map(|i| i as f64)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.unwrap_or(0.0),
|
.unwrap_or(0.0),
|
||||||
balance_usd: response_data.get("balance_usd")
|
balance_usd: response_data.get("balance_usd")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| {
|
||||||
.and_then(|s| s.parse::<f64>().ok())
|
if v.is_string() {
|
||||||
|
v.as_str().and_then(|s| s.parse::<f64>().ok())
|
||||||
|
} else if v.is_f64() {
|
||||||
|
v.as_f64()
|
||||||
|
} else if v.is_i64() {
|
||||||
|
v.as_i64().map(|i| i as f64)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.unwrap_or(0.0),
|
.unwrap_or(0.0),
|
||||||
total_spent_usd: response_data.get("total_spent_usd")
|
total_spent_usd: response_data.get("total_spent_usd")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| {
|
||||||
.and_then(|s| s.parse::<f64>().ok())
|
if v.is_string() {
|
||||||
|
v.as_str().and_then(|s| s.parse::<f64>().ok())
|
||||||
|
} else if v.is_f64() {
|
||||||
|
v.as_f64()
|
||||||
|
} else if v.is_i64() {
|
||||||
|
v.as_i64().map(|i| i as f64)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.unwrap_or(0.0),
|
.unwrap_or(0.0),
|
||||||
plan_type: response_data.get("plan_type")
|
plan_type: response_data.get("plan_type")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
@@ -1119,6 +1192,8 @@ pub async fn packycode_get_user_quota(station_id: String, db: State<'_, AgentDb>
|
|||||||
email: response_data.get("email")
|
email: response_data.get("email")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.map(|s| s.to_string()),
|
.map(|s| s.to_string()),
|
||||||
|
opus_enabled: response_data.get("opus_enabled")
|
||||||
|
.and_then(|v| v.as_bool()),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(quota)
|
Ok(quota)
|
||||||
|
@@ -357,6 +357,11 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
<Badge variant="secondary" className="text-xs">
|
<Badge variant="secondary" className="text-xs">
|
||||||
{quotaData[station.id].plan_type.toUpperCase()}
|
{quotaData[station.id].plan_type.toUpperCase()}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
{quotaData[station.id].opus_enabled && (
|
||||||
|
<Badge variant="default" className="text-xs bg-purple-600">
|
||||||
|
Opus
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{quotaData[station.id].plan_expires_at && (
|
{quotaData[station.id].plan_expires_at && (
|
||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
@@ -369,7 +374,7 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<span className="text-muted-foreground">账户余额:</span>
|
<span className="text-muted-foreground">账户余额:</span>
|
||||||
<span className="font-semibold text-blue-600">
|
<span className="font-semibold text-blue-600">
|
||||||
${quotaData[station.id].balance_usd.toFixed(2)}
|
${Number(quotaData[station.id].balance_usd).toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -378,21 +383,36 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<span className="text-muted-foreground">日额度:</span>
|
<span className="text-muted-foreground">日额度:</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className={quotaData[station.id].daily_spent_usd > quotaData[station.id].daily_budget_usd * 0.8 ? 'text-orange-600' : 'text-green-600'}>
|
{(() => {
|
||||||
${quotaData[station.id].daily_spent_usd.toFixed(2)}
|
const daily_spent = Number(quotaData[station.id].daily_spent_usd || 0);
|
||||||
</span>
|
const daily_budget = Number(quotaData[station.id].daily_budget_usd);
|
||||||
<span className="text-muted-foreground">/</span>
|
return (
|
||||||
<span className="text-muted-foreground">${quotaData[station.id].daily_budget_usd.toFixed(2)}</span>
|
<>
|
||||||
|
<span className={daily_spent > daily_budget * 0.8 ? 'text-orange-600' : 'text-green-600'}>
|
||||||
|
${daily_spent.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground">/</span>
|
||||||
|
<span className="text-muted-foreground">${daily_budget.toFixed(2)}</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
<div className="w-full h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className={`h-full transition-all ${
|
className={`h-full transition-all ${
|
||||||
quotaData[station.id].daily_spent_usd / quotaData[station.id].daily_budget_usd > 0.8
|
(() => {
|
||||||
? 'bg-orange-500'
|
const daily_spent = Number(quotaData[station.id].daily_spent_usd || 0);
|
||||||
: 'bg-green-500'
|
const daily_budget = Number(quotaData[station.id].daily_budget_usd);
|
||||||
|
return daily_spent / daily_budget > 0.8;
|
||||||
|
})() ? 'bg-orange-500' : 'bg-green-500'
|
||||||
}`}
|
}`}
|
||||||
style={{ width: `${Math.min((quotaData[station.id].daily_spent_usd / quotaData[station.id].daily_budget_usd) * 100, 100)}%` }}
|
style={{ width: `${Math.min(
|
||||||
|
(() => {
|
||||||
|
const daily_spent = Number(quotaData[station.id].daily_spent_usd || 0);
|
||||||
|
const daily_budget = Number(quotaData[station.id].daily_budget_usd);
|
||||||
|
return (daily_spent / daily_budget) * 100;
|
||||||
|
})(), 100)}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -402,28 +422,43 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
|
|||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<span className="text-muted-foreground">月额度:</span>
|
<span className="text-muted-foreground">月额度:</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className={quotaData[station.id].monthly_spent_usd > quotaData[station.id].monthly_budget_usd * 0.8 ? 'text-orange-600' : 'text-green-600'}>
|
{(() => {
|
||||||
${quotaData[station.id].monthly_spent_usd.toFixed(2)}
|
const monthly_spent = Number(quotaData[station.id].monthly_spent_usd || 0);
|
||||||
</span>
|
const monthly_budget = Number(quotaData[station.id].monthly_budget_usd);
|
||||||
<span className="text-muted-foreground">/</span>
|
return (
|
||||||
<span className="text-muted-foreground">${quotaData[station.id].monthly_budget_usd.toFixed(2)}</span>
|
<>
|
||||||
|
<span className={monthly_spent > monthly_budget * 0.8 ? 'text-orange-600' : 'text-green-600'}>
|
||||||
|
${monthly_spent.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground">/</span>
|
||||||
|
<span className="text-muted-foreground">${monthly_budget.toFixed(2)}</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
<div className="w-full h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className={`h-full transition-all ${
|
className={`h-full transition-all ${
|
||||||
quotaData[station.id].monthly_spent_usd / quotaData[station.id].monthly_budget_usd > 0.8
|
(() => {
|
||||||
? 'bg-orange-500'
|
const monthly_spent = Number(quotaData[station.id].monthly_spent_usd || 0);
|
||||||
: 'bg-green-500'
|
const monthly_budget = Number(quotaData[station.id].monthly_budget_usd);
|
||||||
|
return monthly_spent / monthly_budget > 0.8;
|
||||||
|
})() ? 'bg-orange-500' : 'bg-green-500'
|
||||||
}`}
|
}`}
|
||||||
style={{ width: `${Math.min((quotaData[station.id].monthly_spent_usd / quotaData[station.id].monthly_budget_usd) * 100, 100)}%` }}
|
style={{ width: `${Math.min(
|
||||||
|
(() => {
|
||||||
|
const monthly_spent = Number(quotaData[station.id].monthly_spent_usd || 0);
|
||||||
|
const monthly_budget = Number(quotaData[station.id].monthly_budget_usd);
|
||||||
|
return (monthly_spent / monthly_budget) * 100;
|
||||||
|
})(), 100)}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 总消费 */}
|
{/* 总消费 */}
|
||||||
<div className="flex items-center justify-between text-xs text-muted-foreground pt-2 border-t">
|
<div className="flex items-center justify-between text-xs text-muted-foreground pt-2 border-t">
|
||||||
<span>总消费: ${quotaData[station.id].total_spent_usd.toFixed(2)}</span>
|
<span>总消费: ${Number(quotaData[station.id].total_spent_usd).toFixed(2)}</span>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
@@ -28,7 +28,7 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
const isInitializedRef = useRef(false);
|
const isInitializedRef = useRef(false);
|
||||||
const unlistenRef = useRef<(() => void) | null>(null);
|
const unlistenRef = useRef<(() => void) | null>(null);
|
||||||
const resizeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const resizeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
const [sessionId, setSessionId] = useState<string | null>(null);
|
const [sessionId, setSessionId] = useState<string | null>(null);
|
||||||
const [terminalSize, setTerminalSize] = useState({ cols: 80, rows: 24 });
|
const [terminalSize, setTerminalSize] = useState({ cols: 80, rows: 24 });
|
||||||
@@ -37,27 +37,27 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
// 计算终端应该有的尺寸
|
// 计算终端应该有的尺寸
|
||||||
const calculateOptimalSize = useCallback(() => {
|
const calculateOptimalSize = useCallback(() => {
|
||||||
if (!terminalRef.current) return { cols: 80, rows: 24 };
|
if (!terminalRef.current) return { cols: 80, rows: 24 };
|
||||||
|
|
||||||
const container = terminalRef.current;
|
const container = terminalRef.current;
|
||||||
const rect = container.getBoundingClientRect();
|
const rect = container.getBoundingClientRect();
|
||||||
|
|
||||||
// 获取或估算字符尺寸
|
// 获取或估算字符尺寸
|
||||||
const fontSize = 14; // 我们设置的字体大小
|
const fontSize = 14; // 我们设置的字体大小
|
||||||
const charWidth = fontSize * 0.6; // 等宽字体的典型宽度比例
|
const charWidth = fontSize * 0.6; // 等宽字体的典型宽度比例
|
||||||
const lineHeight = fontSize * 1.2; // 行高
|
const lineHeight = fontSize * 1.2; // 行高
|
||||||
|
|
||||||
// 计算能容纳的最大列数和行数
|
// 计算能容纳的最大列数和行数
|
||||||
const availableWidth = rect.width - 2;
|
const availableWidth = rect.width - 2;
|
||||||
const availableHeight = rect.height - 2;
|
const availableHeight = rect.height - 2;
|
||||||
|
|
||||||
const cols = Math.max(80, Math.floor(availableWidth / charWidth));
|
const cols = Math.max(80, Math.floor(availableWidth / charWidth));
|
||||||
const rows = Math.max(24, Math.floor(availableHeight / lineHeight));
|
const rows = Math.max(24, Math.floor(availableHeight / lineHeight));
|
||||||
|
|
||||||
// 计算实际使用的宽度
|
// 计算实际使用的宽度
|
||||||
const usedWidth = cols * charWidth;
|
const usedWidth = cols * charWidth;
|
||||||
const unusedWidth = availableWidth - usedWidth;
|
const unusedWidth = availableWidth - usedWidth;
|
||||||
const percentUsed = ((usedWidth / availableWidth) * 100).toFixed(1);
|
const percentUsed = ((usedWidth / availableWidth) * 100).toFixed(1);
|
||||||
|
|
||||||
console.log('[Terminal] Size calculation:', {
|
console.log('[Terminal] Size calculation:', {
|
||||||
containerWidth: rect.width,
|
containerWidth: rect.width,
|
||||||
availableWidth,
|
availableWidth,
|
||||||
@@ -68,25 +68,25 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
percentUsed: `${percentUsed}%`,
|
percentUsed: `${percentUsed}%`,
|
||||||
message: unusedWidth > 10 ? `还有 ${unusedWidth.toFixed(1)}px 未使用` : '宽度使用正常'
|
message: unusedWidth > 10 ? `还有 ${unusedWidth.toFixed(1)}px 未使用` : '宽度使用正常'
|
||||||
});
|
});
|
||||||
|
|
||||||
return { cols, rows };
|
return { cols, rows };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 调整终端大小
|
// 调整终端大小
|
||||||
const resizeTerminal = useCallback(() => {
|
const resizeTerminal = useCallback(() => {
|
||||||
if (!xtermRef.current || !terminalRef.current) return;
|
if (!xtermRef.current || !terminalRef.current) return;
|
||||||
|
|
||||||
// 先尝试获取实际的字符尺寸
|
// 先尝试获取实际的字符尺寸
|
||||||
let actualCharWidth = 8.4; // 默认值
|
let actualCharWidth = 8.4; // 默认值
|
||||||
let actualLineHeight = 16.8; // 默认值
|
let actualLineHeight = 16.8; // 默认值
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const core = (xtermRef.current as any)._core;
|
const core = (xtermRef.current as any)._core;
|
||||||
if (core && core._renderService && core._renderService.dimensions) {
|
if (core && core._renderService && core._renderService.dimensions) {
|
||||||
const dims = core._renderService.dimensions;
|
const dims = core._renderService.dimensions;
|
||||||
if (dims.actualCellWidth) actualCharWidth = dims.actualCellWidth;
|
if (dims.actualCellWidth) actualCharWidth = dims.actualCellWidth;
|
||||||
if (dims.actualCellHeight) actualLineHeight = dims.actualCellHeight;
|
if (dims.actualCellHeight) actualLineHeight = dims.actualCellHeight;
|
||||||
|
|
||||||
console.log('[Terminal] Using actual char dimensions:', {
|
console.log('[Terminal] Using actual char dimensions:', {
|
||||||
actualCharWidth,
|
actualCharWidth,
|
||||||
actualLineHeight
|
actualLineHeight
|
||||||
@@ -95,23 +95,23 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 使用默认值
|
// 使用默认值
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用实际字符尺寸计算新的列数和行数
|
// 使用实际字符尺寸计算新的列数和行数
|
||||||
const rect = terminalRef.current.getBoundingClientRect();
|
const rect = terminalRef.current.getBoundingClientRect();
|
||||||
const availableWidth = rect.width - 2;
|
const availableWidth = rect.width - 2;
|
||||||
const availableHeight = rect.height - 2;
|
const availableHeight = rect.height - 2;
|
||||||
|
|
||||||
// 更新容器宽度显示
|
// 更新容器宽度显示
|
||||||
setContainerWidth(rect.width);
|
setContainerWidth(rect.width);
|
||||||
|
|
||||||
const newCols = Math.max(80, Math.floor(availableWidth / actualCharWidth));
|
const newCols = Math.max(80, Math.floor(availableWidth / actualCharWidth));
|
||||||
const newRows = Math.max(24, Math.floor(availableHeight / actualLineHeight));
|
const newRows = Math.max(24, Math.floor(availableHeight / actualLineHeight));
|
||||||
|
|
||||||
// 计算宽度使用情况
|
// 计算宽度使用情况
|
||||||
const usedWidth = newCols * actualCharWidth;
|
const usedWidth = newCols * actualCharWidth;
|
||||||
const unusedWidth = availableWidth - usedWidth;
|
const unusedWidth = availableWidth - usedWidth;
|
||||||
const percentUsed = ((usedWidth / availableWidth) * 100).toFixed(1);
|
const percentUsed = ((usedWidth / availableWidth) * 100).toFixed(1);
|
||||||
|
|
||||||
// 只有当尺寸真的改变时才调整
|
// 只有当尺寸真的改变时才调整
|
||||||
if (newCols !== terminalSize.cols || newRows !== terminalSize.rows) {
|
if (newCols !== terminalSize.cols || newRows !== terminalSize.rows) {
|
||||||
console.log('[Terminal] Resizing:', {
|
console.log('[Terminal] Resizing:', {
|
||||||
@@ -123,15 +123,15 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
unusedWidth,
|
unusedWidth,
|
||||||
percentUsed: `${percentUsed}%`
|
percentUsed: `${percentUsed}%`
|
||||||
});
|
});
|
||||||
|
|
||||||
setTerminalSize({ cols: newCols, rows: newRows });
|
setTerminalSize({ cols: newCols, rows: newRows });
|
||||||
xtermRef.current.resize(newCols, newRows);
|
xtermRef.current.resize(newCols, newRows);
|
||||||
|
|
||||||
// 更新后端
|
// 更新后端
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
api.resizeTerminal(sessionId, newCols, newRows).catch(console.error);
|
api.resizeTerminal(sessionId, newCols, newRows).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 强制刷新渲染
|
// 强制刷新渲染
|
||||||
try {
|
try {
|
||||||
const core = (xtermRef.current as any)._core;
|
const core = (xtermRef.current as any)._core;
|
||||||
@@ -149,7 +149,7 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
if (resizeTimeoutRef.current) {
|
if (resizeTimeoutRef.current) {
|
||||||
clearTimeout(resizeTimeoutRef.current);
|
clearTimeout(resizeTimeoutRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
resizeTimeoutRef.current = setTimeout(() => {
|
resizeTimeoutRef.current = setTimeout(() => {
|
||||||
resizeTerminal();
|
resizeTerminal();
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -158,9 +158,9 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
// 初始化终端
|
// 初始化终端
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isInitializedRef.current || !terminalRef.current) return;
|
if (isInitializedRef.current || !terminalRef.current) return;
|
||||||
|
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
|
|
||||||
const initializeTerminal = async () => {
|
const initializeTerminal = async () => {
|
||||||
try {
|
try {
|
||||||
console.log('[Terminal] Initializing...');
|
console.log('[Terminal] Initializing...');
|
||||||
@@ -219,7 +219,7 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
// 添加插件
|
// 添加插件
|
||||||
const webLinksAddon = new WebLinksAddon();
|
const webLinksAddon = new WebLinksAddon();
|
||||||
const searchAddon = new SearchAddon();
|
const searchAddon = new SearchAddon();
|
||||||
|
|
||||||
xterm.loadAddon(webLinksAddon);
|
xterm.loadAddon(webLinksAddon);
|
||||||
xterm.loadAddon(searchAddon);
|
xterm.loadAddon(searchAddon);
|
||||||
|
|
||||||
@@ -227,10 +227,11 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
if (terminalRef.current) {
|
if (terminalRef.current) {
|
||||||
xterm.open(terminalRef.current);
|
xterm.open(terminalRef.current);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
console.error('[Terminal] Terminal container ref is null');
|
console.error('[Terminal] Terminal container ref is null');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存引用
|
// 保存引用
|
||||||
xtermRef.current = xterm;
|
xtermRef.current = xterm;
|
||||||
|
|
||||||
@@ -244,25 +245,25 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
const dims = core._renderService.dimensions;
|
const dims = core._renderService.dimensions;
|
||||||
const actualCharWidth = dims.actualCellWidth || dims.scaledCellWidth;
|
const actualCharWidth = dims.actualCellWidth || dims.scaledCellWidth;
|
||||||
const actualLineHeight = dims.actualCellHeight || dims.scaledCellHeight;
|
const actualLineHeight = dims.actualCellHeight || dims.scaledCellHeight;
|
||||||
|
|
||||||
if (actualCharWidth && actualLineHeight) {
|
if (actualCharWidth && actualLineHeight) {
|
||||||
console.log('[Terminal] Actual character dimensions:', {
|
console.log('[Terminal] Actual character dimensions:', {
|
||||||
charWidth: actualCharWidth,
|
charWidth: actualCharWidth,
|
||||||
lineHeight: actualLineHeight
|
lineHeight: actualLineHeight
|
||||||
});
|
});
|
||||||
|
|
||||||
// 使用实际尺寸重新计算
|
// 使用实际尺寸重新计算
|
||||||
const rect = terminalRef.current.getBoundingClientRect();
|
const rect = terminalRef.current.getBoundingClientRect();
|
||||||
const availableWidth = rect.width - 2;
|
const availableWidth = rect.width - 2;
|
||||||
const newCols = Math.floor(availableWidth / actualCharWidth);
|
const newCols = Math.floor(availableWidth / actualCharWidth);
|
||||||
|
|
||||||
console.log('[Terminal] Recalculating with actual dimensions:', {
|
console.log('[Terminal] Recalculating with actual dimensions:', {
|
||||||
availableWidth,
|
availableWidth,
|
||||||
actualCharWidth,
|
actualCharWidth,
|
||||||
newCols,
|
newCols,
|
||||||
currentCols: xtermRef.current.cols
|
currentCols: xtermRef.current.cols
|
||||||
});
|
});
|
||||||
|
|
||||||
if (newCols > xtermRef.current.cols) {
|
if (newCols > xtermRef.current.cols) {
|
||||||
xtermRef.current.resize(newCols, xtermRef.current.rows);
|
xtermRef.current.resize(newCols, xtermRef.current.rows);
|
||||||
setTerminalSize({ cols: newCols, rows: xtermRef.current.rows });
|
setTerminalSize({ cols: newCols, rows: xtermRef.current.rows });
|
||||||
@@ -278,7 +279,7 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
|
|
||||||
// 创建终端会话
|
// 创建终端会话
|
||||||
const newSessionId = await api.createTerminalSession(projectPath || process.cwd());
|
const newSessionId = await api.createTerminalSession(projectPath || process.cwd());
|
||||||
|
|
||||||
if (!isMounted) {
|
if (!isMounted) {
|
||||||
await api.closeTerminalSession(newSessionId);
|
await api.closeTerminalSession(newSessionId);
|
||||||
return;
|
return;
|
||||||
@@ -319,11 +320,11 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isMounted = false;
|
isMounted = false;
|
||||||
|
|
||||||
if (resizeTimeoutRef.current) {
|
if (resizeTimeoutRef.current) {
|
||||||
clearTimeout(resizeTimeoutRef.current);
|
clearTimeout(resizeTimeoutRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unlistenRef.current) {
|
if (unlistenRef.current) {
|
||||||
unlistenRef.current();
|
unlistenRef.current();
|
||||||
unlistenRef.current = null;
|
unlistenRef.current = null;
|
||||||
@@ -337,7 +338,7 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
xtermRef.current.dispose();
|
xtermRef.current.dispose();
|
||||||
xtermRef.current = null;
|
xtermRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
isInitializedRef.current = false;
|
isInitializedRef.current = false;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -349,11 +350,11 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
// 监听容器大小变化
|
// 监听容器大小变化
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!terminalRef.current) return;
|
if (!terminalRef.current) return;
|
||||||
|
|
||||||
// 初始化时立即获取容器宽度
|
// 初始化时立即获取容器宽度
|
||||||
const rect = terminalRef.current.getBoundingClientRect();
|
const rect = terminalRef.current.getBoundingClientRect();
|
||||||
setContainerWidth(rect.width);
|
setContainerWidth(rect.width);
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const { width } = entry.contentRect;
|
const { width } = entry.contentRect;
|
||||||
@@ -361,12 +362,12 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
}
|
}
|
||||||
handleResize();
|
handleResize();
|
||||||
});
|
});
|
||||||
|
|
||||||
resizeObserver.observe(terminalRef.current);
|
resizeObserver.observe(terminalRef.current);
|
||||||
|
|
||||||
// 监听窗口大小变化
|
// 监听窗口大小变化
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
@@ -404,7 +405,7 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
容器宽度: {containerWidth.toFixed(0)}px
|
容器宽度: {containerWidth.toFixed(0)}px
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{onToggleMaximize && (
|
{onToggleMaximize && (
|
||||||
<Button
|
<Button
|
||||||
@@ -442,7 +443,7 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
backgroundColor: '#1e1e1e',
|
backgroundColor: '#1e1e1e',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isConnected && (
|
{!isConnected && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-black/50">
|
<div className="absolute inset-0 flex items-center justify-center bg-black/50">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
@@ -456,4 +457,4 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Terminal;
|
export default Terminal;
|
||||||
|
@@ -585,16 +585,17 @@ export interface NodeSpeedTestResult {
|
|||||||
|
|
||||||
/** PackyCode 用户额度信息 */
|
/** PackyCode 用户额度信息 */
|
||||||
export interface PackycodeUserQuota {
|
export interface PackycodeUserQuota {
|
||||||
daily_budget_usd: number; // 日预算(美元)
|
daily_budget_usd: string | number; // 日预算(美元)
|
||||||
daily_spent_usd: number; // 日已使用(美元)
|
daily_spent_usd: string | number | null; // 日已使用(美元)
|
||||||
monthly_budget_usd: number; // 月预算(美元)
|
monthly_budget_usd: string | number; // 月预算(美元)
|
||||||
monthly_spent_usd: number; // 月已使用(美元)
|
monthly_spent_usd: string | number | null; // 月已使用(美元)
|
||||||
balance_usd: number; // 账户余额(美元)
|
balance_usd: string | number; // 账户余额(美元)
|
||||||
total_spent_usd: number; // 总消费(美元)
|
total_spent_usd: string | number; // 总消费(美元)
|
||||||
plan_type: string; // 计划类型 (pro, basic, etc.)
|
plan_type: string; // 计划类型 (pro, basic, etc.)
|
||||||
plan_expires_at: string; // 计划过期时间
|
plan_expires_at: string; // 计划过期时间
|
||||||
username?: string; // 用户名
|
username?: string; // 用户名
|
||||||
email?: string; // 邮箱
|
email?: string; // 邮箱
|
||||||
|
opus_enabled?: boolean; // 是否启用Opus模型
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user