Skip to content

Commit 728dccf

Browse files
committed
feat(provider): add SSYCloud chat completion provider
1 parent a9bb8a6 commit 728dccf

11 files changed

Lines changed: 204 additions & 5 deletions

File tree

astrbot/core/config/default.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,6 +1377,18 @@
13771377
"proxy": "",
13781378
"custom_headers": {},
13791379
},
1380+
"SSYCloud(胜算云)": {
1381+
"id": "ssycloud",
1382+
"provider": "ssycloud",
1383+
"type": "ssycloud_chat_completion",
1384+
"provider_type": "chat_completion",
1385+
"enable": True,
1386+
"key": [],
1387+
"timeout": 120,
1388+
"api_base": "https://router.shengsuanyun.com/api/v1",
1389+
"proxy": "",
1390+
"custom_headers": {"X-Title": "AstrBot"},
1391+
},
13801392
"NVIDIA": {
13811393
"id": "nvidia",
13821394
"provider": "nvidia",

astrbot/core/provider/manager.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,10 @@ def dynamic_import_provider(self, type: str) -> None:
397397
from .sources.openrouter_source import (
398398
ProviderOpenRouter as ProviderOpenRouter,
399399
)
400+
case "ssycloud_chat_completion":
401+
from .sources.ssycloud_source import (
402+
ProviderSSYCloud as ProviderSSYCloud,
403+
)
400404
case "anthropic_chat_completion":
401405
from .sources.anthropic_source import (
402406
ProviderAnthropic as ProviderAnthropic,
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from openai._exceptions import NotFoundError
2+
3+
from ..register import register_provider_adapter
4+
from .openai_source import ProviderOpenAIOfficial
5+
from .request_retry import retry_provider_request
6+
7+
8+
@register_provider_adapter(
9+
"ssycloud_chat_completion",
10+
"SSYCloud Chat Completion Provider Adapter",
11+
)
12+
class ProviderSSYCloud(ProviderOpenAIOfficial):
13+
"""SSYCloud provider using its OpenAI-compatible Chat Completions API."""
14+
15+
def __init__(self, provider_config: dict, provider_settings: dict) -> None:
16+
"""Initialize the SSYCloud client with provider defaults.
17+
18+
Args:
19+
provider_config: AstrBot provider source configuration.
20+
provider_settings: Global provider settings.
21+
"""
22+
if not provider_config.get("api_base"):
23+
provider_config["api_base"] = "https://router.shengsuanyun.com/api/v1"
24+
custom_headers = provider_config.get("custom_headers")
25+
if not isinstance(custom_headers, dict):
26+
custom_headers = {}
27+
provider_config["custom_headers"] = custom_headers
28+
custom_headers.setdefault("X-Title", "AstrBot")
29+
super().__init__(provider_config, provider_settings)
30+
31+
async def get_models(self) -> list[str]:
32+
"""Return models compatible with the Chat Completions API.
33+
34+
Returns:
35+
Sorted model IDs. Models without ``support_apis`` metadata are kept
36+
for compatibility with older SSYCloud responses.
37+
38+
Raises:
39+
Exception: If the SSYCloud model catalog endpoint is unavailable.
40+
"""
41+
try:
42+
response = await retry_provider_request(
43+
"SSYCloud",
44+
lambda: self.client.models.list(),
45+
)
46+
model_ids: list[str] = []
47+
for model in response.data:
48+
support_apis = getattr(model, "support_apis", None)
49+
if support_apis is None:
50+
model_extra = getattr(model, "model_extra", None)
51+
if isinstance(model_extra, dict):
52+
support_apis = model_extra.get("support_apis")
53+
if not isinstance(support_apis, list) or (
54+
"/v1/chat/completions" in support_apis
55+
):
56+
model_ids.append(model.id)
57+
return sorted(model_ids)
58+
except NotFoundError as exc:
59+
raise Exception(f"Failed to fetch SSYCloud model list: {exc}") from exc

dashboard/src/components/provider/ProviderChatCompletionPanel.vue

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<div v-if="selectedProviderSource" class="provider-config-shell">
2525
<div class="provider-config-header">
2626
<div class="provider-config-headline">
27-
<div class="provider-config-title">{{ selectedProviderSource.id }}</div>
27+
<div class="provider-config-title">{{ getSourceDisplayName(selectedProviderSource) }}</div>
2828
<div class="provider-config-subtitle">
2929
{{ selectedProviderSource.api_base || 'N/A' }}
3030
</div>
@@ -56,6 +56,7 @@
5656
v-if="basicSourceConfig"
5757
:iterable="basicSourceConfig"
5858
:metadata="providerSourceSchema"
59+
:field-links="providerSourceFieldLinks"
5960
metadataKey="provider"
6061
:is-editing="true"
6162
/>
@@ -184,7 +185,7 @@
184185
</template>
185186

186187
<script setup>
187-
import { ref } from 'vue'
188+
import { computed, ref } from 'vue'
188189
import { useModuleI18n } from '@/i18n/composables'
189190
import AstrBotConfig from '@/components/shared/AstrBotConfig.vue'
190191
import ProviderModelsPanel from '@/components/provider/ProviderModelsPanel.vue'
@@ -253,6 +254,17 @@ const {
253254
showMessage
254255
})
255256
257+
const providerSourceFieldLinks = computed(() => (
258+
selectedProviderSource.value?.provider === 'ssycloud'
259+
? {
260+
key: {
261+
label: tm('providerSources.getApiKey'),
262+
href: 'https://www.shengsuanyun.com/?from=CH_T70U2X9L'
263+
}
264+
}
265+
: {}
266+
))
267+
256268
const showManualModelDialog = ref(false)
257269
258270
const {

dashboard/src/components/shared/AstrBotConfig.vue

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ const props = defineProps({
4141
enableDefaultReset: {
4242
type: Boolean,
4343
default: false
44+
},
45+
fieldLinks: {
46+
type: Object,
47+
default: () => ({})
4448
}
4549
})
4650
@@ -274,9 +278,19 @@ function hasVisibleItemsAfter(items, currentIndex) {
274278
</v-list-item-title>
275279
276280
<v-list-item-subtitle class="property-hint">
277-
<span v-if="metadata[metadataKey].items[key]?.obvious_hint && getItemHint(key, metadata[metadataKey].items[key])"
278-
class="important-hint">‼️</span>
279-
{{ resolveConfigText(getItemPath(key), 'hint', getItemHint(key, metadata[metadataKey].items[key])) }}
281+
<span :class="{ 'property-hint__content--linked': fieldLinks[key] }">
282+
<span v-if="metadata[metadataKey].items[key]?.obvious_hint && getItemHint(key, metadata[metadataKey].items[key])"
283+
class="important-hint">‼️</span>
284+
<span>{{ resolveConfigText(getItemPath(key), 'hint', getItemHint(key, metadata[metadataKey].items[key])) }}</span>
285+
<a
286+
v-if="fieldLinks[key]"
287+
class="property-link"
288+
:href="fieldLinks[key].href"
289+
target="_blank"
290+
rel="noopener noreferrer"
291+
@click.stop
292+
>{{ fieldLinks[key].label }}</a>
293+
</span>
280294
</v-list-item-subtitle>
281295
</v-list-item>
282296
</v-col>
@@ -459,12 +473,31 @@ function hasVisibleItemsAfter(items, currentIndex) {
459473
color: var(--v-theme-primaryText);
460474
}
461475
476+
.property-link {
477+
color: rgb(var(--v-theme-primary));
478+
font-size: 0.75rem;
479+
font-weight: 500;
480+
text-decoration: none;
481+
white-space: nowrap;
482+
}
483+
484+
.property-link:hover {
485+
text-decoration: underline;
486+
}
487+
462488
.property-hint {
463489
font-size: 0.75rem;
464490
color: var(--v-theme-secondaryText);
465491
margin-top: 2px;
466492
}
467493
494+
.property-hint__content--linked {
495+
display: inline-flex;
496+
align-items: center;
497+
gap: 8px;
498+
white-space: nowrap;
499+
}
500+
468501
.type-indicator {
469502
display: flex;
470503
justify-content: center;

dashboard/src/composables/useProviderSources.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ export function useProviderSources(options: UseProviderSourcesOptions) {
299299
function getSourceDisplayName(source: any) {
300300
if (!source) return ''
301301
if (source.isPlaceholder) return source.templateKey || source.id || ''
302+
if (source.id === 'ssycloud') return 'ssycloud(胜算云)'
302303
return source.id
303304
}
304305

dashboard/src/i18n/locales/en-US/features/provider.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
"save": "Save Configuration",
101101
"saveAndFetchModels": "Save and Fetch Models",
102102
"fetchModels": "Fetch Model List",
103+
"getApiKey": "Get API Key",
103104
"saveSuccess": "Provider source saved successfully",
104105
"saveError": "Failed to save provider source",
105106
"deleteConfirm": "Are you sure you want to delete provider source {id}? This will also delete all associated model configurations.",

dashboard/src/i18n/locales/ru-RU/features/provider.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
"save": "Сохранить конфиг",
102102
"saveAndFetchModels": "Сохранить и загрузить модели",
103103
"fetchModels": "Загрузить список моделей",
104+
"getApiKey": "Получить API-ключ",
104105
"saveSuccess": "Источник успешно сохранен",
105106
"saveError": "Ошибка сохранения источника",
106107
"deleteConfirm": "Вы уверены, что хотите удалить источник «{id}»? Все связанные конфигурации моделей будут удалены.",

dashboard/src/i18n/locales/zh-CN/features/provider.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
"save": "保存配置",
102102
"saveAndFetchModels": "保存并获取模型",
103103
"fetchModels": "获取模型列表",
104+
"getApiKey": "获取 API Key",
104105
"saveSuccess": "提供商源保存成功",
105106
"saveError": "提供商源保存失败",
106107
"deleteConfirm": "确定要删除提供商源 {id} 吗?这将同时删除关联的所有模型配置。",

dashboard/src/utils/providerUtils.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export function getProviderIcon(type) {
4343
'groq': 'https://cdn.jsdelivr.net/npm/@lobehub/icons-static-svg@latest/icons/groq.svg',
4444
'aihubmix': 'https://cdn.jsdelivr.net/npm/@lobehub/icons-static-svg@latest/icons/aihubmix-color.svg',
4545
'openrouter': 'https://cdn.jsdelivr.net/npm/@lobehub/icons-static-svg@latest/icons/openrouter.svg',
46+
'ssycloud': 'https://admin.shengsuanyun.com/assets/logo-BoujJhP-.png',
4647
"tokenpony": "https://tokenpony.cn/tokenpony-web/logo.png",
4748
"compshare": "https://compshare.cn/favicon.ico",
4849
"xinference": "https://cdn.jsdelivr.net/npm/@lobehub/icons-static-svg@latest/icons/xinference-color.svg",

0 commit comments

Comments
 (0)