Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src-tauri/src/commands/token_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

use ::duckcoding::models::provider::Provider;
use ::duckcoding::models::remote_token::{
CreateRemoteTokenRequest, RemoteToken, RemoteTokenGroup, UpdateRemoteTokenRequest,
CreateRemoteTokenRequest, RemoteToken, RemoteTokenGroup, TokenListData,
UpdateRemoteTokenRequest,
};
use ::duckcoding::services::profile_manager::types::TokenImportStatus;
use ::duckcoding::services::{
Expand All @@ -27,11 +28,18 @@ pub async fn check_token_import_status(
.map_err(|e| e.to_string())
}

/// 获取指定供应商的远程令牌列表
/// 获取指定供应商的远程令牌列表(支持分页)
#[tauri::command]
pub async fn fetch_provider_tokens(provider: Provider) -> Result<Vec<RemoteToken>, String> {
pub async fn fetch_provider_tokens(
provider: Provider,
page: i32,
page_size: i32,
) -> Result<TokenListData, String> {
let client = NewApiClient::new(provider).map_err(|e| e.to_string())?;
client.list_tokens().await.map_err(|e| e.to_string())
client
.list_tokens(page, page_size)
.await
.map_err(|e| e.to_string())
}

/// 获取指定供应商的令牌分组列表
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/models/remote_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ pub struct NewApiResponse<T> {
}

/// NEW API 令牌列表响应的 data 部分
#[derive(Debug, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenListData {
pub page: i32,
pub page_size: i32,
Expand Down
22 changes: 16 additions & 6 deletions src-tauri/src/services/new_api/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,14 @@ impl NewApiClient {
headers
}

/// 获取所有远程令牌列表
pub async fn list_tokens(&self) -> Result<Vec<RemoteToken>> {
let url = format!("{}/api/token", self.base_url());
/// 获取远程令牌列表(支持分页)
pub async fn list_tokens(&self, page: i32, page_size: i32) -> Result<TokenListData> {
let url = format!(
"{}/api/token?p={}&page_size={}",
self.base_url(),
page,
page_size
);
let response = self
.client
.get(&url)
Expand Down Expand Up @@ -82,14 +87,19 @@ impl NewApiClient {
}

// 标准化 API Key,确保所有令牌都有 sk- 前缀
let mut tokens = api_response.data.map(|d| d.items).unwrap_or_default();
for token in &mut tokens {
let mut data = api_response.data.unwrap_or_else(|| TokenListData {
page,
page_size,
total: 0,
items: vec![],
});
for token in &mut data.items {
if !token.key.starts_with("sk-") {
token.key = format!("sk-{}", token.key);
}
}

Ok(tokens)
Ok(data)
}

/// 获取所有令牌分组
Expand Down
11 changes: 8 additions & 3 deletions src/lib/tauri-commands/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@ import type {
RemoteToken,
RemoteTokenGroup,
TokenImportStatus,
TokenListResponse,
UpdateRemoteTokenRequest,
} from '@/types/remote-token';

/**
* 获取指定供应商的远程令牌列表
* 获取指定供应商的远程令牌列表(支持分页)
*/
export async function fetchProviderTokens(provider: Provider): Promise<RemoteToken[]> {
return invoke<RemoteToken[]>('fetch_provider_tokens', { provider });
export async function fetchProviderTokens(
provider: Provider,
page = 1,
pageSize = 10,
): Promise<TokenListResponse> {
return invoke<TokenListResponse>('fetch_provider_tokens', { provider, page, pageSize });
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export const ImportFromProviderDialog = forwardRef<
setLoadingTokens(true);
const result = await fetchProviderTokens(provider);
// 自动为没有 sk- 前缀的令牌添加前缀
const normalizedTokens = result.map((token) => ({
const normalizedTokens = result.items.map((token: RemoteToken) => ({
...token,
key: token.key.startsWith('sk-') ? token.key : `sk-${token.key}`,
}));
Expand Down Expand Up @@ -325,7 +325,7 @@ export const ImportFromProviderDialog = forwardRef<
// 重新加载令牌列表并获取最新数据
const updatedTokens = await fetchProviderTokens(selectedProvider);
// 自动为没有 sk- 前缀的令牌添加前缀
const normalizedTokens = updatedTokens.map((token) => ({
const normalizedTokens = updatedTokens.items.map((token: RemoteToken) => ({
...token,
key: token.key.startsWith('sk-') ? token.key : `sk-${token.key}`,
}));
Expand All @@ -337,7 +337,7 @@ export const ImportFromProviderDialog = forwardRef<
? result.api_key
: `sk-${result.api_key}`;
if (normalizedTokens.length > 0) {
const newToken = normalizedTokens.find((t) => t.key === normalizedApiKey);
const newToken = normalizedTokens.find((t: RemoteToken) => t.key === normalizedApiKey);
if (newToken) {
setTokenId(newToken.id);
} else {
Expand Down Expand Up @@ -531,15 +531,15 @@ export const ImportFromProviderDialog = forwardRef<
// 重新获取令牌列表
const updatedTokens = await fetchProviderTokens(selectedProvider);
// 自动为没有 sk- 前缀的令牌添加前缀
const normalizedTokens = updatedTokens.map((token) => ({
const normalizedTokens = updatedTokens.items.map((token: RemoteToken) => ({
...token,
key: token.key.startsWith('sk-') ? token.key : `sk-${token.key}`,
}));

// 按 ID 降序排序,找到名称匹配的第一个(最新创建的)
const sortedTokens = normalizedTokens
.filter((t) => t.name === newTokenName)
.sort((a, b) => b.id - a.id);
.filter((t: RemoteToken) => t.name === newTokenName)
.sort((a: RemoteToken, b: RemoteToken) => b.id - a.id);

if (sortedTokens.length === 0) {
toast({
Expand Down
20 changes: 8 additions & 12 deletions src/pages/ProfileManagementPage/components/TokenDetailCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ export function TokenDetailCard({ token, group }: TokenDetailCardProps) {
};

/**
* 格式化额度(microdollars 转美元
* 格式化额度(500000 = $1
*/
const formatQuota = (microdollars: number): string => {
return `$${(microdollars / 1000000).toFixed(2)}`;
const formatQuota = (quota: number): string => {
return `$${(quota / 500000).toFixed(2)}`;
};

return (
Expand All @@ -57,20 +57,16 @@ export function TokenDetailCard({ token, group }: TokenDetailCardProps) {
</div>
</div>

{/* 剩余额度 */}
{/* 剩余额度 / 总额度 */}
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">剩余额度:</span>
<span className="text-muted-foreground">额度:</span>
<span className="font-medium">
{token.unlimited_quota ? '无限' : formatQuota(token.remain_quota)}
{token.unlimited_quota
? '无限'
: `${formatQuota(token.remain_quota)} / ${formatQuota(token.remain_quota + token.used_quota)}`}
</span>
</div>

{/* 已用额度 */}
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">已用额度:</span>
<span className="font-medium">{formatQuota(token.used_quota)}</span>
</div>

{/* 过期时间 */}
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">过期时间:</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,11 @@ export function ImportTokenDialog({
<span>{token.group}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">剩余额度:</span>
<span className="text-muted-foreground">额度:</span>
<span>
{token.unlimited_quota ? '无限' : `$${(token.remain_quota / 1000000).toFixed(2)}`}
{token.unlimited_quota
? '无限'
: `$${(token.remain_quota / 500000).toFixed(2)} / $${((token.remain_quota + token.used_quota) / 500000).toFixed(2)}`}
</span>
</div>
</div>
Expand Down
Loading