mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-03 00:02:28 +08:00
Compare commits
2 Commits
96094cfde2
...
4a35d78c8d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a35d78c8d | ||
|
|
26b281271e |
15
LICENSE
15
LICENSE
@@ -5,12 +5,17 @@ Aether 非商业开源许可证
|
|||||||
特此授予任何获得本软件及其相关文档文件(以下简称"软件")副本的人免费使用、
|
特此授予任何获得本软件及其相关文档文件(以下简称"软件")副本的人免费使用、
|
||||||
复制、修改、合并、发布和分发本软件的权限,但须遵守以下条件:
|
复制、修改、合并、发布和分发本软件的权限,但须遵守以下条件:
|
||||||
|
|
||||||
1. 仅限非商业用途
|
1. 仅限非盈利用途
|
||||||
本软件不得用于商业目的。商业目的包括但不限于:
|
本软件不得用于盈利目的。盈利目的包括但不限于:
|
||||||
- 出售本软件或任何衍生作品
|
- 出售本软件或任何衍生作品
|
||||||
- 使用本软件提供付费服务
|
- 使用本软件提供付费服务
|
||||||
- 将本软件用于商业产品或服务
|
- 将本软件用于以盈利为目的的商业产品或服务
|
||||||
- 将本软件用于任何旨在获取商业利益或金钱报酬的活动
|
|
||||||
|
以下用途被明确允许:
|
||||||
|
- 个人学习和研究
|
||||||
|
- 教育机构的教学和研究
|
||||||
|
- 非盈利组织的内部使用
|
||||||
|
- 企业内部非盈利性质的使用(如内部工具、测试环境等)
|
||||||
|
|
||||||
2. 署名要求
|
2. 署名要求
|
||||||
上述版权声明和本许可声明应包含在本软件的所有副本或主要部分中。
|
上述版权声明和本许可声明应包含在本软件的所有副本或主要部分中。
|
||||||
@@ -22,7 +27,7 @@ Aether 非商业开源许可证
|
|||||||
您不得以不同的条款将本软件再许可给他人。
|
您不得以不同的条款将本软件再许可给他人。
|
||||||
|
|
||||||
5. 商业许可
|
5. 商业许可
|
||||||
如需商业使用,请联系版权持有人以获取单独的商业许可。
|
如需将本软件用于盈利目的,请联系版权持有人以获取单独的商业许可。
|
||||||
|
|
||||||
本软件按"原样"提供,不提供任何明示或暗示的保证,包括但不限于对适销性、
|
本软件按"原样"提供,不提供任何明示或暗示的保证,包括但不限于对适销性、
|
||||||
特定用途适用性和非侵权性的保证。在任何情况下,作者或版权持有人均不对任何
|
特定用途适用性和非侵权性的保证。在任何情况下,作者或版权持有人均不对任何
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -143,7 +143,7 @@ cd frontend && npm install && npm run dev
|
|||||||
- **模型级别**: 在模型管理中针对指定模型开启 1H缓存策略
|
- **模型级别**: 在模型管理中针对指定模型开启 1H缓存策略
|
||||||
- **密钥级别**: 在密钥管理中针对指定密钥使用 1H缓存策略
|
- **密钥级别**: 在密钥管理中针对指定密钥使用 1H缓存策略
|
||||||
|
|
||||||
> **注意**: 若对密钥设置强制 1H缓存, 则该密钥只能调用支持 1H缓存的模型
|
> **注意**: 若对密钥设置强制 1H缓存, 则该密钥只能使用支持 1H缓存的模型, 匹配提供商Key, 将会导致这个Key无法同时用于Claude Code、Codex、GeminiCLI, 因为更推荐使用模型开启1H缓存.
|
||||||
|
|
||||||
### Q: 如何配置负载均衡?
|
### Q: 如何配置负载均衡?
|
||||||
|
|
||||||
@@ -162,4 +162,16 @@ cd frontend && npm install && npm run dev
|
|||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
本项目采用 [Aether 非商业开源许可证](LICENSE)。
|
本项目采用 [Aether 非商业开源许可证](LICENSE)。允许个人学习、教育研究、非盈利组织及企业内部非盈利性质的使用;禁止用于盈利目的。商业使用请联系获取商业许可。
|
||||||
|
|
||||||
|
## 联系作者
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="docs/author/qq_qrcode.jpg" width="200" alt="QQ二维码">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
[](https://star-history.com/#fawney19/Aether&Date)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
BIN
docs/author/qq_qrcode.jpg
Normal file
BIN
docs/author/qq_qrcode.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 266 KiB |
BIN
docs/author/wechat_payment.jpg
Normal file
BIN
docs/author/wechat_payment.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import type { User, LoginResponse } from '@/api/auth'
|
import type { User, LoginResponse } from '@/api/auth'
|
||||||
import type { DashboardStatsResponse, RecentRequest, ProviderStatus, DailyStatsResponse } from '@/api/dashboard'
|
import type { DashboardStatsResponse, RecentRequest, ProviderStatus, DailyStatsResponse } from '@/api/dashboard'
|
||||||
import type { User as AdminUser, ApiKey } from '@/api/users'
|
import type { User as AdminUser } from '@/api/users'
|
||||||
import type { AdminApiKeysResponse } from '@/api/admin'
|
import type { AdminApiKeysResponse } from '@/api/admin'
|
||||||
import type { Profile, UsageResponse } from '@/api/me'
|
import type { Profile, UsageResponse } from '@/api/me'
|
||||||
import type { ProviderWithEndpointsSummary, GlobalModelResponse } from '@/api/endpoints/types'
|
import type { ProviderWithEndpointsSummary, GlobalModelResponse } from '@/api/endpoints/types'
|
||||||
@@ -185,18 +185,20 @@ export const MOCK_DASHBOARD_STATS: DashboardStatsResponse = {
|
|||||||
output: 700000,
|
output: 700000,
|
||||||
cache_creation: 50000,
|
cache_creation: 50000,
|
||||||
cache_read: 200000
|
cache_read: 200000
|
||||||
}
|
},
|
||||||
|
// 普通用户专用字段
|
||||||
|
monthly_cost: 45.67
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MOCK_RECENT_REQUESTS: RecentRequest[] = [
|
export const MOCK_RECENT_REQUESTS: RecentRequest[] = [
|
||||||
{ id: 'req-001', user: 'alice', model: 'claude-sonnet-4-20250514', tokens: 15234, time: '2 分钟前' },
|
{ id: 'req-001', user: 'alice', model: 'claude-sonnet-4-5-20250929', tokens: 15234, time: '2 分钟前' },
|
||||||
{ id: 'req-002', user: 'bob', model: 'gpt-4o', tokens: 8765, time: '5 分钟前' },
|
{ id: 'req-002', user: 'bob', model: 'gpt-5.1', tokens: 8765, time: '5 分钟前' },
|
||||||
{ id: 'req-003', user: 'charlie', model: 'claude-opus-4-20250514', tokens: 32100, time: '8 分钟前' },
|
{ id: 'req-003', user: 'charlie', model: 'claude-opus-4-5-20251101', tokens: 32100, time: '8 分钟前' },
|
||||||
{ id: 'req-004', user: 'diana', model: 'gemini-2.0-flash', tokens: 4521, time: '12 分钟前' },
|
{ id: 'req-004', user: 'diana', model: 'gemini-3-pro-preview', tokens: 4521, time: '12 分钟前' },
|
||||||
{ id: 'req-005', user: 'eve', model: 'claude-sonnet-4-20250514', tokens: 9876, time: '15 分钟前' },
|
{ id: 'req-005', user: 'eve', model: 'claude-sonnet-4-5-20250929', tokens: 9876, time: '15 分钟前' },
|
||||||
{ id: 'req-006', user: 'frank', model: 'gpt-4o-mini', tokens: 2345, time: '18 分钟前' },
|
{ id: 'req-006', user: 'frank', model: 'gpt-5.1-codex-mini', tokens: 2345, time: '18 分钟前' },
|
||||||
{ id: 'req-007', user: 'grace', model: 'claude-haiku-3-5-20241022', tokens: 6789, time: '22 分钟前' },
|
{ id: 'req-007', user: 'grace', model: 'claude-haiku-4-5-20251001', tokens: 6789, time: '22 分钟前' },
|
||||||
{ id: 'req-008', user: 'henry', model: 'gemini-2.5-pro', tokens: 12345, time: '25 分钟前' }
|
{ id: 'req-008', user: 'henry', model: 'gemini-3-pro-preview', tokens: 12345, time: '25 分钟前' }
|
||||||
]
|
]
|
||||||
|
|
||||||
export const MOCK_PROVIDER_STATUS: ProviderStatus[] = [
|
export const MOCK_PROVIDER_STATUS: ProviderStatus[] = [
|
||||||
@@ -231,11 +233,11 @@ function generateDailyStats(): DailyStatsResponse {
|
|||||||
unique_models: 8 + Math.floor(Math.random() * 5),
|
unique_models: 8 + Math.floor(Math.random() * 5),
|
||||||
unique_providers: 4 + Math.floor(Math.random() * 3),
|
unique_providers: 4 + Math.floor(Math.random() * 3),
|
||||||
model_breakdown: [
|
model_breakdown: [
|
||||||
{ model: 'claude-sonnet-4-20250514', requests: Math.floor(baseRequests * 0.35), tokens: Math.floor(baseTokens * 0.35), cost: Number((baseCost * 0.35).toFixed(2)) },
|
{ model: 'claude-sonnet-4-5-20250929', requests: Math.floor(baseRequests * 0.35), tokens: Math.floor(baseTokens * 0.35), cost: Number((baseCost * 0.35).toFixed(2)) },
|
||||||
{ model: 'gpt-4o', requests: Math.floor(baseRequests * 0.25), tokens: Math.floor(baseTokens * 0.25), cost: Number((baseCost * 0.25).toFixed(2)) },
|
{ model: 'gpt-5.1', requests: Math.floor(baseRequests * 0.25), tokens: Math.floor(baseTokens * 0.25), cost: Number((baseCost * 0.25).toFixed(2)) },
|
||||||
{ model: 'claude-opus-4-20250514', requests: Math.floor(baseRequests * 0.15), tokens: Math.floor(baseTokens * 0.15), cost: Number((baseCost * 0.20).toFixed(2)) },
|
{ model: 'claude-opus-4-5-20251101', requests: Math.floor(baseRequests * 0.15), tokens: Math.floor(baseTokens * 0.15), cost: Number((baseCost * 0.20).toFixed(2)) },
|
||||||
{ model: 'gemini-2.0-flash', requests: Math.floor(baseRequests * 0.15), tokens: Math.floor(baseTokens * 0.15), cost: Number((baseCost * 0.10).toFixed(2)) },
|
{ model: 'gemini-3-pro-preview', requests: Math.floor(baseRequests * 0.15), tokens: Math.floor(baseTokens * 0.15), cost: Number((baseCost * 0.10).toFixed(2)) },
|
||||||
{ model: 'claude-haiku-3-5-20241022', requests: Math.floor(baseRequests * 0.10), tokens: Math.floor(baseTokens * 0.10), cost: Number((baseCost * 0.10).toFixed(2)) }
|
{ model: 'claude-haiku-4-5-20251001', requests: Math.floor(baseRequests * 0.10), tokens: Math.floor(baseTokens * 0.10), cost: Number((baseCost * 0.10).toFixed(2)) }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -243,11 +245,11 @@ function generateDailyStats(): DailyStatsResponse {
|
|||||||
return {
|
return {
|
||||||
daily_stats: dailyStats,
|
daily_stats: dailyStats,
|
||||||
model_summary: [
|
model_summary: [
|
||||||
{ model: 'claude-sonnet-4-20250514', requests: 2456, tokens: 8500000, cost: 125.45, avg_response_time: 1.2, cost_per_request: 0.051, tokens_per_request: 3461 },
|
{ model: 'claude-sonnet-4-5-20250929', requests: 2456, tokens: 8500000, cost: 125.45, avg_response_time: 1.2, cost_per_request: 0.051, tokens_per_request: 3461 },
|
||||||
{ model: 'gpt-4o', requests: 1823, tokens: 6200000, cost: 98.32, avg_response_time: 0.9, cost_per_request: 0.054, tokens_per_request: 3401 },
|
{ model: 'gpt-5.1', requests: 1823, tokens: 6200000, cost: 98.32, avg_response_time: 0.9, cost_per_request: 0.054, tokens_per_request: 3401 },
|
||||||
{ model: 'claude-opus-4-20250514', requests: 987, tokens: 4100000, cost: 156.78, avg_response_time: 2.1, cost_per_request: 0.159, tokens_per_request: 4154 },
|
{ model: 'claude-opus-4-5-20251101', requests: 987, tokens: 4100000, cost: 156.78, avg_response_time: 2.1, cost_per_request: 0.159, tokens_per_request: 4154 },
|
||||||
{ model: 'gemini-2.0-flash', requests: 1234, tokens: 3800000, cost: 28.56, avg_response_time: 0.6, cost_per_request: 0.023, tokens_per_request: 3079 },
|
{ model: 'gemini-3-pro-preview', requests: 1234, tokens: 3800000, cost: 28.56, avg_response_time: 0.6, cost_per_request: 0.023, tokens_per_request: 3079 },
|
||||||
{ model: 'claude-haiku-3-5-20241022', requests: 2100, tokens: 5200000, cost: 32.10, avg_response_time: 0.5, cost_per_request: 0.015, tokens_per_request: 2476 }
|
{ model: 'claude-haiku-4-5-20251001', requests: 2100, tokens: 5200000, cost: 32.10, avg_response_time: 0.5, cost_per_request: 0.015, tokens_per_request: 2476 }
|
||||||
],
|
],
|
||||||
period: {
|
period: {
|
||||||
start_date: dailyStats[0].date,
|
start_date: dailyStats[0].date,
|
||||||
@@ -336,7 +338,7 @@ export const MOCK_ALL_USERS: AdminUser[] = [
|
|||||||
|
|
||||||
// ========== API Key 数据 ==========
|
// ========== API Key 数据 ==========
|
||||||
|
|
||||||
export const MOCK_USER_API_KEYS: ApiKey[] = [
|
export const MOCK_USER_API_KEYS = [
|
||||||
{
|
{
|
||||||
id: 'key-uuid-001',
|
id: 'key-uuid-001',
|
||||||
key_display: 'sk-ae...x7f9',
|
key_display: 'sk-ae...x7f9',
|
||||||
@@ -346,7 +348,8 @@ export const MOCK_USER_API_KEYS: ApiKey[] = [
|
|||||||
is_active: true,
|
is_active: true,
|
||||||
is_standalone: false,
|
is_standalone: false,
|
||||||
total_requests: 1234,
|
total_requests: 1234,
|
||||||
total_cost_usd: 45.67
|
total_cost_usd: 45.67,
|
||||||
|
force_capabilities: null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'key-uuid-002',
|
id: 'key-uuid-002',
|
||||||
@@ -357,7 +360,8 @@ export const MOCK_USER_API_KEYS: ApiKey[] = [
|
|||||||
is_active: true,
|
is_active: true,
|
||||||
is_standalone: false,
|
is_standalone: false,
|
||||||
total_requests: 5678,
|
total_requests: 5678,
|
||||||
total_cost_usd: 123.45
|
total_cost_usd: 123.45,
|
||||||
|
force_capabilities: { cache_1h: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'key-uuid-003',
|
id: 'key-uuid-003',
|
||||||
@@ -367,7 +371,8 @@ export const MOCK_USER_API_KEYS: ApiKey[] = [
|
|||||||
is_active: false,
|
is_active: false,
|
||||||
is_standalone: false,
|
is_standalone: false,
|
||||||
total_requests: 100,
|
total_requests: 100,
|
||||||
total_cost_usd: 2.34
|
total_cost_usd: 2.34,
|
||||||
|
force_capabilities: null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -813,16 +818,16 @@ export const MOCK_USAGE_RESPONSE: UsageResponse = {
|
|||||||
quota_usd: 100,
|
quota_usd: 100,
|
||||||
used_usd: 45.32,
|
used_usd: 45.32,
|
||||||
summary_by_model: [
|
summary_by_model: [
|
||||||
{ model: 'claude-sonnet-4-20250514', requests: 456, input_tokens: 650000, output_tokens: 250000, total_tokens: 900000, total_cost_usd: 18.50, actual_total_cost_usd: 13.50 },
|
{ model: 'claude-sonnet-4-5-20250929', requests: 456, input_tokens: 650000, output_tokens: 250000, total_tokens: 900000, total_cost_usd: 18.50, actual_total_cost_usd: 13.50 },
|
||||||
{ model: 'gpt-4o', requests: 312, input_tokens: 480000, output_tokens: 180000, total_tokens: 660000, total_cost_usd: 12.30, actual_total_cost_usd: 9.20 },
|
{ model: 'gpt-5.1', requests: 312, input_tokens: 480000, output_tokens: 180000, total_tokens: 660000, total_cost_usd: 12.30, actual_total_cost_usd: 9.20 },
|
||||||
{ model: 'claude-haiku-3-5-20241022', requests: 289, input_tokens: 420000, output_tokens: 170000, total_tokens: 590000, total_cost_usd: 8.50, actual_total_cost_usd: 6.30 },
|
{ model: 'claude-haiku-4-5-20251001', requests: 289, input_tokens: 420000, output_tokens: 170000, total_tokens: 590000, total_cost_usd: 8.50, actual_total_cost_usd: 6.30 },
|
||||||
{ model: 'gemini-2.0-flash', requests: 177, input_tokens: 250000, output_tokens: 100000, total_tokens: 350000, total_cost_usd: 6.37, actual_total_cost_usd: 4.33 }
|
{ model: 'gemini-3-pro-preview', requests: 177, input_tokens: 250000, output_tokens: 100000, total_tokens: 350000, total_cost_usd: 6.37, actual_total_cost_usd: 4.33 }
|
||||||
],
|
],
|
||||||
records: [
|
records: [
|
||||||
{
|
{
|
||||||
id: 'usage-001',
|
id: 'usage-001',
|
||||||
provider: 'anthropic',
|
provider: 'anthropic',
|
||||||
model: 'claude-sonnet-4-20250514',
|
model: 'claude-sonnet-4-5-20250929',
|
||||||
input_tokens: 1500,
|
input_tokens: 1500,
|
||||||
output_tokens: 800,
|
output_tokens: 800,
|
||||||
total_tokens: 2300,
|
total_tokens: 2300,
|
||||||
@@ -837,7 +842,7 @@ export const MOCK_USAGE_RESPONSE: UsageResponse = {
|
|||||||
{
|
{
|
||||||
id: 'usage-002',
|
id: 'usage-002',
|
||||||
provider: 'openai',
|
provider: 'openai',
|
||||||
model: 'gpt-4o',
|
model: 'gpt-5.1',
|
||||||
input_tokens: 2000,
|
input_tokens: 2000,
|
||||||
output_tokens: 500,
|
output_tokens: 500,
|
||||||
total_tokens: 2500,
|
total_tokens: 2500,
|
||||||
|
|||||||
@@ -405,10 +405,10 @@ function getUsageRecords() {
|
|||||||
|
|
||||||
// Mock 映射数据
|
// Mock 映射数据
|
||||||
const MOCK_ALIASES = [
|
const MOCK_ALIASES = [
|
||||||
{ id: 'alias-001', source_model: 'claude-4-sonnet', target_global_model_id: 'gm-001', target_global_model_name: 'claude-sonnet-4-20250514', target_global_model_display_name: 'Claude Sonnet 4', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
|
{ id: 'alias-001', source_model: 'claude-4-sonnet', target_global_model_id: 'gm-003', target_global_model_name: 'claude-sonnet-4-5-20250929', target_global_model_display_name: 'Claude Sonnet 4.5', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
|
||||||
{ id: 'alias-002', source_model: 'claude-4-opus', target_global_model_id: 'gm-002', target_global_model_name: 'claude-opus-4-20250514', target_global_model_display_name: 'Claude Opus 4', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
|
{ id: 'alias-002', source_model: 'claude-4-opus', target_global_model_id: 'gm-002', target_global_model_name: 'claude-opus-4-5-20251101', target_global_model_display_name: 'Claude Opus 4.5', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
|
||||||
{ id: 'alias-003', source_model: 'gpt4o', target_global_model_id: 'gm-004', target_global_model_name: 'gpt-4o', target_global_model_display_name: 'GPT-4o', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
|
{ id: 'alias-003', source_model: 'gpt5', target_global_model_id: 'gm-006', target_global_model_name: 'gpt-5.1', target_global_model_display_name: 'GPT-5.1', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
|
||||||
{ id: 'alias-004', source_model: 'gemini-flash', target_global_model_id: 'gm-005', target_global_model_name: 'gemini-2.0-flash', target_global_model_display_name: 'Gemini 2.0 Flash', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' }
|
{ id: 'alias-004', source_model: 'gemini-pro', target_global_model_id: 'gm-005', target_global_model_name: 'gemini-3-pro-preview', target_global_model_display_name: 'Gemini 3 Pro Preview', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' }
|
||||||
]
|
]
|
||||||
|
|
||||||
// Mock Endpoint Keys
|
// Mock Endpoint Keys
|
||||||
@@ -2172,10 +2172,10 @@ function generateIntervalTimelineData(
|
|||||||
|
|
||||||
// 模型列表(用于按模型区分颜色)
|
// 模型列表(用于按模型区分颜色)
|
||||||
const models = [
|
const models = [
|
||||||
'claude-sonnet-4-20250514',
|
'claude-sonnet-4-5-20250929',
|
||||||
'claude-3-5-sonnet-20241022',
|
'claude-haiku-4-5-20251001',
|
||||||
'claude-3-5-haiku-20241022',
|
'claude-opus-4-5-20251101',
|
||||||
'claude-opus-4-20250514'
|
'gpt-5.1'
|
||||||
]
|
]
|
||||||
|
|
||||||
// 生成模拟的请求间隔数据
|
// 生成模拟的请求间隔数据
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ authors = [
|
|||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: Other/Proprietary License",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
|
|||||||
Reference in New Issue
Block a user