mirror of
https://github.com/wisdgod/cursor-api.git
synced 2025-10-06 07:06:51 +08:00
v0.1.3-rc.3.3
限制 `TOKEN_DELIMITER` 为 ASCII 避免问题
This commit is contained in:
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -2,9 +2,9 @@ name: Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
# push:
|
||||||
tags:
|
# tags:
|
||||||
- 'v*'
|
# - 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
19
.github/workflows/docker.yml
vendored
19
.github/workflows/docker.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
type=raw,value=latest,enable=${{ github.event_name == 'workflow_dispatch' && inputs.update_latest }}
|
type=raw,value=latest,enable=${{ github.event_name == 'workflow_dispatch' && inputs.update_latest }}
|
||||||
type=raw,value=${{ steps.cargo_version.outputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
|
type=raw,value=${{ steps.cargo_version.outputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
|
||||||
type=ref,event=tag,enable=${{ github.event_name == 'push' }}
|
type=raw,value=${{ github.ref_name }},enable=${{ github.event_name == 'push' }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.8.0
|
uses: docker/setup-buildx-action@v3.8.0
|
||||||
@@ -70,17 +70,24 @@ jobs:
|
|||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
outputs: type=local,dest=./dist,enable=${{ github.event_name == 'workflow_dispatch' && inputs.upload_artifacts }}
|
outputs: type=local,dest=./dist,enable=${{ (github.event_name == 'workflow_dispatch' && inputs.upload_artifacts) || github.event_name == 'push'}}
|
||||||
|
|
||||||
- name: Prepare artifacts
|
- name: Prepare artifacts
|
||||||
if: github.event_name == 'workflow_dispatch' && inputs.upload_artifacts
|
if: github.event_name == 'workflow_dispatch' && inputs.upload_artifacts
|
||||||
run: |
|
run: |
|
||||||
mkdir -p artifacts/amd64 artifacts/arm64
|
mkdir -p artifacts
|
||||||
cp dist/linux_amd64/app/cursor-api artifacts/amd64/
|
cp dist/linux_amd64/app/cursor-api artifacts/cursor-api-x86_64-${{ steps.cargo_version.outputs.version }}
|
||||||
cp dist/linux_arm64/app/cursor-api artifacts/arm64/
|
cp dist/linux_arm64/app/cursor-api artifacts/cursor-api-aarch64-${{ steps.cargo_version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Prepare artifacts
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
run: |
|
||||||
|
mkdir -p artifacts
|
||||||
|
cp dist/linux_amd64/app/cursor-api artifacts/cursor-api-x86_64-${{ github.event_name }}
|
||||||
|
cp dist/linux_arm64/app/cursor-api artifacts/cursor-api-aarch64-${{ github.event_name }}
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
if: github.event_name == 'workflow_dispatch' && inputs.upload_artifacts
|
if: (github.event_name == 'workflow_dispatch' && inputs.upload_artifacts) || github.event_name == 'push'
|
||||||
uses: actions/upload-artifact@v4.6.0
|
uses: actions/upload-artifact@v4.6.0
|
||||||
with:
|
with:
|
||||||
name: cursor-api-binaries
|
name: cursor-api-binaries
|
||||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -327,7 +327,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cursor-api"
|
name = "cursor-api"
|
||||||
version = "0.1.3-rc.3.2"
|
version = "0.1.3-rc.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"base64",
|
"base64",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cursor-api"
|
name = "cursor-api"
|
||||||
version = "0.1.3-rc.3.2"
|
version = "0.1.3-rc.3.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["wisdgod <nav@wisdgod.com>"]
|
authors = ["wisdgod <nav@wisdgod.com>"]
|
||||||
description = "OpenAI format compatibility layer for the Cursor API"
|
description = "OpenAI format compatibility layer for the Cursor API"
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# AMD64 构建阶段
|
# AMD64 构建阶段
|
||||||
FROM --platform=linux/amd64 rust:1.83.0-slim-bookworm as builder-amd64
|
FROM --platform=linux/amd64 rust:1.84.0-slim-bookworm as builder-amd64
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
@@ -11,7 +11,7 @@ RUN cargo build --release && \
|
|||||||
cp target/release/cursor-api /app/cursor-api
|
cp target/release/cursor-api /app/cursor-api
|
||||||
|
|
||||||
# ARM64 构建阶段
|
# ARM64 构建阶段
|
||||||
FROM --platform=linux/arm64 rust:1.83.0-slim-bookworm as builder-arm64
|
FROM --platform=linux/arm64 rust:1.84.0-slim-bookworm as builder-arm64
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
|
@@ -3,7 +3,7 @@ use crate::{
|
|||||||
CURSOR_API2_HOST, CURSOR_HOST, DEFAULT_TOKEN_FILE_NAME, DEFAULT_TOKEN_LIST_FILE_NAME,
|
CURSOR_API2_HOST, CURSOR_HOST, DEFAULT_TOKEN_FILE_NAME, DEFAULT_TOKEN_LIST_FILE_NAME,
|
||||||
EMPTY_STRING,
|
EMPTY_STRING,
|
||||||
},
|
},
|
||||||
common::utils::{parse_char_from_env, parse_string_from_env},
|
common::utils::{parse_ascii_char_from_env, parse_string_from_env},
|
||||||
};
|
};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
@@ -55,8 +55,18 @@ def_pub_static!(SHARED_AUTH_TOKEN, env: "SHARED_AUTH_TOKEN", default: EMPTY_STRI
|
|||||||
|
|
||||||
pub static USE_SHARE: LazyLock<bool> = LazyLock::new(|| !SHARED_AUTH_TOKEN.is_empty());
|
pub static USE_SHARE: LazyLock<bool> = LazyLock::new(|| !SHARED_AUTH_TOKEN.is_empty());
|
||||||
|
|
||||||
pub static TOKEN_DELIMITER: LazyLock<char> =
|
pub static TOKEN_DELIMITER: LazyLock<char> = LazyLock::new(|| {
|
||||||
LazyLock::new(|| parse_char_from_env("TOKEN_DELIMITER", ','));
|
let delimiter = parse_ascii_char_from_env("TOKEN_DELIMITER", ',');
|
||||||
|
if delimiter.is_ascii_alphabetic()
|
||||||
|
|| delimiter.is_ascii_digit()
|
||||||
|
|| delimiter == '+'
|
||||||
|
|| delimiter == '/'
|
||||||
|
{
|
||||||
|
','
|
||||||
|
} else {
|
||||||
|
delimiter
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
pub static TOKEN_DELIMITER_LEN: LazyLock<usize> = LazyLock::new(|| TOKEN_DELIMITER.len_utf8());
|
pub static TOKEN_DELIMITER_LEN: LazyLock<usize> = LazyLock::new(|| TOKEN_DELIMITER.len_utf8());
|
||||||
|
|
||||||
|
@@ -24,12 +24,12 @@ pub fn parse_string_from_env(key: &str, default: &str) -> String {
|
|||||||
std::env::var(key).unwrap_or_else(|_| default.to_string())
|
std::env::var(key).unwrap_or_else(|_| default.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_char_from_env(key: &str, default: char) -> char {
|
pub fn parse_ascii_char_from_env(key: &str, default: char) -> char {
|
||||||
std::env::var(key)
|
std::env::var(key)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|v| {
|
.and_then(|v| {
|
||||||
let chars: Vec<char> = v.chars().collect();
|
let chars: Vec<char> = v.chars().collect();
|
||||||
if chars.len() == 1 {
|
if chars.len() == 1 && chars[0].is_ascii() {
|
||||||
Some(chars[0])
|
Some(chars[0])
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@@ -307,24 +307,13 @@
|
|||||||
// 添加用户基本信息
|
// 添加用户基本信息
|
||||||
if (tokenInfo.user || calibInfo) {
|
if (tokenInfo.user || calibInfo) {
|
||||||
const user = tokenInfo.user || {};
|
const user = tokenInfo.user || {};
|
||||||
userDetails.innerHTML += `
|
userDetails.innerHTML += `<p>用户ID: ${calibInfo ? calibInfo.user_id : user.id}</p><p>邮箱: ${user.email || ''}</p><p>用户名: ${user.name || ''}</p>${user.updated_at ? `<p>更新时间: ${new Date(user.updated_at).toLocaleString()}</p>` : ''}${calibInfo ? `<p>令牌创建时间: ${new Date(calibInfo.create_at).toLocaleString()}</p>` : ''}${calibInfo && calibInfo.checksum_time ? `<p>校验和时间区间: ${new Date(calibInfo.checksum_time * 1e6).toLocaleString()} - ${new Date((calibInfo.checksum_time + 1) * 1e6 - 1).toLocaleString()}</p>` : ''}`;
|
||||||
<p>用户ID: ${calibInfo ? calibInfo.user_id : user.id}</p>
|
|
||||||
<p>邮箱: ${user.email || ''}</p>
|
|
||||||
<p>用户名: ${user.name || ''}</p>
|
|
||||||
${user.updated_at ? `<p>更新时间: ${new Date(user.updated_at).toLocaleString()}</p>` : ''}
|
|
||||||
${calibInfo ? `<p>令牌创建时间: ${new Date(calibInfo.create_at).toLocaleString()}</p>` : ''}
|
|
||||||
${calibInfo && calibInfo.checksum_time ? `<p>校验和时间区间: ${new Date(calibInfo.checksum_time * 1e6).toLocaleString()} - ${new Date((calibInfo.checksum_time + 1) * 1e6 - 1).toLocaleString()}</p>` : ''}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加 Stripe 会员信息
|
// 添加 Stripe 会员信息
|
||||||
if (tokenInfo.stripe) {
|
if (tokenInfo.stripe) {
|
||||||
const stripe = tokenInfo.stripe;
|
const stripe = tokenInfo.stripe;
|
||||||
userDetails.innerHTML += `
|
userDetails.innerHTML += `<p>会员类型: ${stripe.membership_type}</p>${stripe.payment_id ? `<p>付款 ID: ${stripe.payment_id}</p>` : ''}<p>试用剩余: ${stripe.days_remaining_on_trial} 天</p>`;
|
||||||
<p>会员类型: ${stripe.membership_type}</p>
|
|
||||||
${stripe.payment_id ? `<p>付款 ID: ${stripe.payment_id}</p>` : ''}
|
|
||||||
<p>试用剩余: ${stripe.days_remaining_on_trial} 天</p>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加使用情况进度条
|
// 添加使用情况进度条
|
||||||
@@ -342,15 +331,7 @@
|
|||||||
const percentage = isUnlimited ? 100 : (data.requests / data.max_requests * 100).toFixed(1);
|
const percentage = isUnlimited ? 100 : (data.requests / data.max_requests * 100).toFixed(1);
|
||||||
const progressClass = isUnlimited ? 'unlimited' : getProgressBarClass(parseFloat(percentage));
|
const progressClass = isUnlimited ? 'unlimited' : getProgressBarClass(parseFloat(percentage));
|
||||||
|
|
||||||
progressContainer.innerHTML += `
|
progressContainer.innerHTML += `<div><p>${modelName}: ${data.requests}/${isUnlimited ? '∞' : data.max_requests} 请求 ${isUnlimited ? '' : `(${percentage}%)`}, ${data.tokens} tokens</p><div class="usage-progress-container"><div class="usage-progress-bar ${progressClass}" style="width: ${percentage}%"></div></div></div>`;
|
||||||
<div>
|
|
||||||
<p>${modelName}: ${data.requests}/${isUnlimited ? '∞' : data.max_requests} 请求
|
|
||||||
${isUnlimited ? '' : `(${percentage}%)`}, ${data.tokens} tokens</p>
|
|
||||||
<div class="usage-progress-container">
|
|
||||||
<div class="usage-progress-bar ${progressClass}" style="width: ${percentage}%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -584,9 +584,7 @@
|
|||||||
const progressDiv = document.createElement('div');
|
const progressDiv = document.createElement('div');
|
||||||
progressDiv.className = 'usage-progress-container';
|
progressDiv.className = 'usage-progress-container';
|
||||||
const colorClass = getProgressBarClass(parseFloat(percentage));
|
const colorClass = getProgressBarClass(parseFloat(percentage));
|
||||||
progressDiv.innerHTML = `
|
progressDiv.innerHTML = `<div class="usage-progress-bar ${colorClass}" style="width: ${percentage}%"></div>`;
|
||||||
<div class="usage-progress-bar ${colorClass}" style="width: ${percentage}%"></div>
|
|
||||||
`;
|
|
||||||
container.appendChild(progressDiv);
|
container.appendChild(progressDiv);
|
||||||
} else {
|
} else {
|
||||||
element.textContent = `${requests} requests, ${tokens} tokens`;
|
element.textContent = `${requests} requests, ${tokens} tokens`;
|
||||||
@@ -629,54 +627,14 @@
|
|||||||
['Premium', premiumUsage]
|
['Premium', premiumUsage]
|
||||||
];
|
];
|
||||||
|
|
||||||
return rows.map(([label, value]) => `
|
return rows.map(([label, value]) => `<div class="tooltip-info-row"><span class="label">${label}:</span><span class="value">${value}</span></div>`).join('');
|
||||||
<div class="tooltip-info-row">
|
|
||||||
<span class="label">${label}:</span>
|
|
||||||
<span class="value">${value}</span>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTable(data) {
|
function updateTable(data) {
|
||||||
const tbody = document.getElementById('logsBody');
|
const tbody = document.getElementById('logsBody');
|
||||||
updateStats(data);
|
updateStats(data);
|
||||||
|
|
||||||
tbody.innerHTML = data.logs.map(log => `
|
tbody.innerHTML = data.logs.map(log => `<tr><td>${log.id}</td><td>${new Date(log.timestamp).toLocaleString()}</td><td>${log.model}</td><td><div class="token-info-tooltip"><button class="info-button" onclick='showTokenModal(${JSON.stringify(log.token_info)})'>查看详情<div class="tooltip-content">${formatSimpleTokenInfo(log.token_info)}</div></button></div></td><td>${log.prompt ?`<div class="token-info-tooltip prompt-preview"><button class="info-button" onclick="showPromptModal(decodeURIComponent('${encodeURIComponent(log.prompt).replace(/'/g, "\\'")}'))">查看对话<div class="tooltip-content">${formatPromptPreview(log.prompt)}</div></button></div>` :'-'}</td><td>${formatTiming(log.timing.total, log.timing.first)}</td><td>${log.stream ? '是' : '否'}</td><td>${log.status}</td><td>${log.error || '-'}</td></tr>`).join('');
|
||||||
<tr>
|
|
||||||
<td>${log.id}</td>
|
|
||||||
<td>${new Date(log.timestamp).toLocaleString()}</td>
|
|
||||||
<td>${log.model}</td>
|
|
||||||
<td>
|
|
||||||
<div class="token-info-tooltip">
|
|
||||||
<button class="info-button" onclick='showTokenModal(${JSON.stringify(log.token_info)})'>
|
|
||||||
查看详情
|
|
||||||
<div class="tooltip-content">
|
|
||||||
${formatSimpleTokenInfo(log.token_info)}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
${log.prompt ?
|
|
||||||
`<div class="token-info-tooltip prompt-preview">
|
|
||||||
<button class="info-button" onclick="showPromptModal(decodeURIComponent('${encodeURIComponent(log.prompt).replace(/'/g, "\\'")}'))">
|
|
||||||
查看对话
|
|
||||||
<div class="tooltip-content">
|
|
||||||
${formatPromptPreview(log.prompt)}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>` :
|
|
||||||
'-'
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
${formatTiming(log.timing.total, log.timing.first)}
|
|
||||||
</td>
|
|
||||||
<td>${log.stream ? '是' : '否'}</td>
|
|
||||||
<td>${log.status}</td>
|
|
||||||
<td>${log.error || '-'}</td>
|
|
||||||
</tr>
|
|
||||||
`).join('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTiming(total, first) {
|
function formatTiming(total, first) {
|
||||||
@@ -700,15 +658,7 @@
|
|||||||
'assistant': '助手'
|
'assistant': '助手'
|
||||||
};
|
};
|
||||||
|
|
||||||
return `
|
return `<div class="message-meta">最后一条消息 (${roleLabels[lastMessage.role] || lastMessage.role}):</div><div class="last-message">${lastMessage.content.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br>')}</div>`;
|
||||||
<div class="message-meta">最后一条消息 (${roleLabels[lastMessage.role] || lastMessage.role}):</div>
|
|
||||||
<div class="last-message">${lastMessage.content
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/\n/g, '<br>')
|
|
||||||
}</div>
|
|
||||||
`;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('预览对话内容失败:', e);
|
console.error('预览对话内容失败:', e);
|
||||||
return '无法解析对话内容';
|
return '无法解析对话内容';
|
||||||
|
@@ -214,24 +214,7 @@ function formatPromptToTable(messages) {
|
|||||||
return escaped;
|
return escaped;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
return `<table class="message-table"><thead><tr><th>角色</th><th>内容</th></tr></thead><tbody>${messages.map(msg => `<tr><td>${roleLabels[msg.role] || msg.role}</td><td>${escapeHtml(msg.content).replace(/\n/g, '<br>')}</td></tr>`).join('')}</tbody></table>`;
|
||||||
<table class="message-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>角色</th>
|
|
||||||
<th>内容</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
${messages.map(msg => `
|
|
||||||
<tr>
|
|
||||||
<td>${roleLabels[msg.role] || msg.role}</td>
|
|
||||||
<td>${escapeHtml(msg.content).replace(/\n/g, '<br>')}</td>
|
|
||||||
</tr>
|
|
||||||
`).join('')}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user