feat: add icon for ai provider (#12722)

This commit is contained in:
CityFun
2026-05-13 18:37:30 +08:00
committed by zhengkunwang223
parent aa10760658
commit d522d837ff
29 changed files with 296 additions and 11 deletions

View File

@@ -200,8 +200,13 @@ export const updateAgentAccount = (req: AI.AgentAccountUpdateReq) => {
return http.post(`/ai/agents/accounts/update`, req); return http.post(`/ai/agents/accounts/update`, req);
}; };
export const pageAgentAccounts = (req: AI.AgentAccountSearch) => { export const pageAgentAccounts = (req: AI.AgentAccountSearch, currentNode?: string) => {
return http.post<ResPage<AI.AgentAccountItem>>(`/ai/agents/accounts/search`, req); return http.post<ResPage<AI.AgentAccountItem>>(
`/ai/agents/accounts/search`,
req,
undefined,
currentNode ? { CurrentNode: currentNode } : undefined,
);
}; };
export const getAgentAccountModels = (req: AI.AgentAccountModelReq) => { export const getAgentAccountModels = (req: AI.AgentAccountModelReq) => {

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 908 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,4 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.7202 0.939941C22.9502 0.939941 23.9502 1.93994 23.9502 3.16994C23.9502 4.39994 22.9502 5.39994 21.7202 5.39994H19.7502C19.6002 5.39994 19.4902 5.27994 19.4902 5.13994V3.16994C19.4902 1.93994 20.4902 0.939941 21.7202 0.939941Z" fill="#1783FF"/>
<path d="M9.39 13.9501L17.82 5.59012C17.98 5.43012 17.89 5.12012 17.68 5.12012H13.14C13.14 5.12012 13.04 5.14012 13 5.18012L3.92 14.1901C3.78 14.3301 3.57 14.2101 3.57 13.9801V5.39012C3.57 5.24012 3.47 5.12012 3.35 5.12012H0.219999C0.0999993 5.12012 0 5.24012 0 5.39012V23.9201C0 24.0701 0.0999993 24.1901 0.219999 24.1901H3.35C3.47 24.1901 3.57 24.0701 3.57 23.9201V20.1401C3.57 20.0601 3.6 19.9801 3.65 19.9301L6.47 17.1401C6.54 17.0701 6.63 17.0601 6.71 17.1101L14.24 22.6501C15.47 23.4801 16.85 23.9901 18.25 24.1401C18.37 24.1501 18.48 24.0301 18.48 23.8701V20.3101C18.48 20.1701 18.4 20.0601 18.29 20.0501C17.47 19.9201 16.66 19.6001 15.94 19.1101L9.42 14.3901C9.28 14.3001 9.27 14.0701 9.39 13.9501Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M11.248 18.25q-.825 0-1.568-.314a4.3 4.3 0 0 1-1.32-.874 4 4 0 0 1-1.304.214 4 4 0 0 1-2.046-.544 4.27 4.27 0 0 1-1.518-1.485 4 4 0 0 1-.56-2.095q0-.48.131-1.04A4.4 4.4 0 0 1 2.04 10.71a4.07 4.07 0 0 1 .017-3.4 4.2 4.2 0 0 1 1.056-1.418 3.8 3.8 0 0 1 1.6-.842 3.9 3.9 0 0 1 .76-1.683q.593-.759 1.451-1.188a4.04 4.04 0 0 1 1.832-.429q.825 0 1.567.313.742.314 1.32.875a4 4 0 0 1 1.304-.215q1.106 0 2.046.545a4.14 4.14 0 0 1 1.501 1.485q.578.941.578 2.095 0 .48-.132 1.04.66.61 1.023 1.419.363.792.363 1.666 0 .892-.38 1.717a4.3 4.3 0 0 1-1.072 1.435 3.8 3.8 0 0 1-1.584.825 3.8 3.8 0 0 1-.775 1.683 4.06 4.06 0 0 1-1.436 1.188 4.04 4.04 0 0 1-1.832.429m-4.076-2.062q.825 0 1.435-.347l3.103-1.782a.36.36 0 0 0 .164-.313v-1.42L7.881 14.62a.67.67 0 0 1-.726 0l-3.118-1.798a.5.5 0 0 1-.017.115v.198q0 .841.396 1.551.413.693 1.139 1.089a3.2 3.2 0 0 0 1.617.412m.165-2.69a.4.4 0 0 0 .181.05q.083 0 .165-.05l1.238-.71-3.977-2.31a.7.7 0 0 1-.363-.643v-3.58q-.825.362-1.32 1.122a2.9 2.9 0 0 0-.495 1.65q0 .809.413 1.55.412.743 1.072 1.123zm3.91 3.663q.875 0 1.585-.396a2.96 2.96 0 0 0 1.534-2.64v-3.564a.32.32 0 0 0-.165-.297l-1.254-.726v4.604a.7.7 0 0 1-.363.643l-3.119 1.799a3 3 0 0 0 1.783.577m.627-6.039V8.878L10.01 7.822 8.129 8.878v2.244l1.881 1.056zM7.057 5.859a.7.7 0 0 1 .363-.644l3.119-1.798a3 3 0 0 0-1.782-.578q-.874 0-1.584.396A2.96 2.96 0 0 0 6.05 4.324a3.07 3.07 0 0 0-.396 1.551v3.547q0 .199.165.314l1.237.726zm8.383 7.887q.825-.364 1.303-1.123.495-.758.495-1.65a3.15 3.15 0 0 0-.412-1.55q-.413-.743-1.073-1.123l-3.086-1.782q-.099-.065-.181-.049a.3.3 0 0 0-.165.05l-1.238.692 3.993 2.327a.6.6 0 0 1 .264.264.64.64 0 0 1 .1.363zm-3.317-8.382a.63.63 0 0 1 .726 0l3.135 1.831v-.297q0-.792-.396-1.501a2.86 2.86 0 0 0-1.105-1.155q-.71-.43-1.65-.43-.825 0-1.436.347L8.294 5.941a.36.36 0 0 0-.165.314v1.418z"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1 @@
<svg version="1.1" viewBox="0.0 0.0 96.0 96.0" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><clipPath id="g31e21232314_0_33.0"><path d="m0 0l96.0 0l0 96.0l-96.0 0l0 -96.0z" clip-rule="nonzero"/></clipPath><g clip-path="url(#g31e21232314_0_33.0)"><path fill="#d9d9d9" d="m41.04961 80.271324l1.8897629 0l0 2.3307114l-1.8897629 0z" fill-rule="evenodd"/><path fill="#d9d9d9" d="m42.221855 81.45145l1.8897629 0l0 2.3307037l-1.8897629 0z" fill-rule="evenodd"/><g filter="url(#shadowFilter-g31e21232314_0_33.1)"><use xlink:href="#g31e21232314_0_33.1" transform="matrix(1.0 0.0 0.0 1.0 0.0 2.0)"/></g><defs><filter id="shadowFilter-g31e21232314_0_33.1" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" stdDeviation="2.0" result="blur"/><feComponentTransfer in="blur" color-interpolation-filters="sRGB"><feFuncR type="linear" slope="0" intercept="0.0"/><feFuncG type="linear" slope="0" intercept="0.0"/><feFuncB type="linear" slope="0" intercept="0.0"/><feFuncA type="linear" slope="0.0" intercept="0"/></feComponentTransfer></filter></defs><g id="g31e21232314_0_33.1"><path fill="#d9d9d9" d="m42.22417 28.470434l0 55.307083l-27.653543 -55.307083z" fill-rule="evenodd"/></g><g filter="url(#shadowFilter-g31e21232314_0_33.2)"><use xlink:href="#g31e21232314_0_33.2" transform="matrix(1.0 0.0 0.0 1.0 0.0 2.0)"/></g><defs><filter id="shadowFilter-g31e21232314_0_33.2" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" stdDeviation="2.0" result="blur"/><feComponentTransfer in="blur" color-interpolation-filters="sRGB"><feFuncR type="linear" slope="0" intercept="0.0"/><feFuncG type="linear" slope="0" intercept="0.0"/><feFuncB type="linear" slope="0" intercept="0.0"/><feFuncA type="linear" slope="0.0" intercept="0"/></feComponentTransfer></filter></defs><g id="g31e21232314_0_33.2"><path fill="#d9d9d9" d="m42.223038 83.77752l21.729656 0l18.653545 -70.385826l-25.574802 13.461943z" fill-rule="evenodd"/></g><g filter="url(#shadowFilter-g31e21232314_0_33.3)"><use xlink:href="#g31e21232314_0_33.3" transform="matrix(1.0 0.0 0.0 1.0 0.0 2.0)"/></g><defs><filter id="shadowFilter-g31e21232314_0_33.3" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" stdDeviation="2.0" result="blur"/><feComponentTransfer in="blur" color-interpolation-filters="sRGB"><feFuncR type="linear" slope="0" intercept="0.0"/><feFuncG type="linear" slope="0" intercept="0.0"/><feFuncB type="linear" slope="0" intercept="0.0"/><feFuncA type="linear" slope="0.0" intercept="0"/></feComponentTransfer></filter></defs><g id="g31e21232314_0_33.3"><path fill="#fdb515" d="m41.0477 27.293962l0 55.30709l-27.653542 -55.30709z" fill-rule="evenodd"/></g><g filter="url(#shadowFilter-g31e21232314_0_33.4)"><use xlink:href="#g31e21232314_0_33.4" transform="matrix(1.0 0.0 0.0 1.0 0.0 2.0)"/></g><defs><filter id="shadowFilter-g31e21232314_0_33.4" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" stdDeviation="2.0" result="blur"/><feComponentTransfer in="blur" color-interpolation-filters="sRGB"><feFuncR type="linear" slope="0" intercept="0.0"/><feFuncG type="linear" slope="0" intercept="0.0"/><feFuncB type="linear" slope="0" intercept="0.0"/><feFuncA type="linear" slope="0.0" intercept="0"/></feComponentTransfer></filter></defs><g id="g31e21232314_0_33.4"><path fill="#30a2ff" d="m41.046566 82.60105l21.72966 0l18.653545 -70.385826l-25.574806 13.461943z" fill-rule="evenodd"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 992 B

View File

@@ -0,0 +1,56 @@
<template>
<span class="agent-provider-logo" :style="logoStyle" :aria-label="`${displayName || provider} logo`" role="img">
<img v-if="logo.src" class="agent-provider-logo__image" :src="logo.src" :alt="displayName || provider" />
<span v-else>{{ logo.mark }}</span>
</span>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { getAgentProviderLogo } from '@/utils/agent-provider-logo';
const props = withDefaults(
defineProps<{
provider: string;
displayName?: string;
size?: number;
}>(),
{
displayName: '',
size: 16,
},
);
const logo = computed(() => getAgentProviderLogo(props.provider, props.displayName));
const logoStyle = computed(() => ({
width: `${props.size}px`,
height: `${props.size}px`,
minWidth: `${props.size}px`,
background: logo.value.background,
color: logo.value.color,
fontSize: `${Math.max(10, Math.floor(props.size * 0.48))}px`,
}));
</script>
<style scoped lang="scss">
.agent-provider-logo {
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 700;
line-height: 1;
letter-spacing: 0;
user-select: none;
overflow: hidden;
box-sizing: border-box;
vertical-align: middle;
flex: 0 0 auto;
}
.agent-provider-logo__image {
display: block;
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
</style>

View File

@@ -1,14 +1,9 @@
<template> <template>
<MdEditor <MdPreview :model-value="props.content" :sanitize="sanitizeHtml" :theme="isDarkTheme ? 'dark' : 'light'" />
previewOnly
:model-value="props.content"
:sanitize="sanitizeHtml"
:theme="isDarkTheme ? 'dark' : 'light'"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { MdEditor } from 'md-editor-v3'; import { MdPreview } from 'md-editor-v3';
import 'md-editor-v3/lib/style.css'; import 'md-editor-v3/lib/style.css';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';

View File

@@ -974,6 +974,7 @@ const message = {
failedRequests: 'Failed Requests', failedRequests: 'Failed Requests',
averageTokenPerRequest: 'Avg Tokens/Request', averageTokenPerRequest: 'Avg Tokens/Request',
callLogs: 'Call Logs', callLogs: 'Call Logs',
usageKeywordPlaceholder: 'Search Request ID / model / upstream model / error message',
attemptChain: 'Attempt Chain', attemptChain: 'Attempt Chain',
attemptIndex: 'Call Order', attemptIndex: 'Call Order',
noAttemptDetails: 'This request hit once; no fallback details', noAttemptDetails: 'This request hit once; no fallback details',

View File

@@ -988,6 +988,7 @@ const message = {
failedRequests: 'Solicitudes fallidas', failedRequests: 'Solicitudes fallidas',
averageTokenPerRequest: 'Tokens promedio/solicitud', averageTokenPerRequest: 'Tokens promedio/solicitud',
callLogs: 'Registros de llamadas', callLogs: 'Registros de llamadas',
usageKeywordPlaceholder: 'Buscar Request ID / modelo / modelo upstream / mensaje de error',
attemptChain: 'Cadena de intentos', attemptChain: 'Cadena de intentos',
attemptIndex: 'Orden de llamada', attemptIndex: 'Orden de llamada',
noAttemptDetails: 'Esta solicitud acertó en el primer intento; no hay detalles de fallback', noAttemptDetails: 'Esta solicitud acertó en el primer intento; no hay detalles de fallback',

View File

@@ -978,6 +978,7 @@ const message = {
failedRequests: '失敗リクエスト', failedRequests: '失敗リクエスト',
averageTokenPerRequest: '平均 Token/リクエスト', averageTokenPerRequest: '平均 Token/リクエスト',
callLogs: '呼び出しログ', callLogs: '呼び出しログ',
usageKeywordPlaceholder: 'Request ID / モデル / 上流モデル / エラーメッセージを検索',
attemptChain: '試行チェーン', attemptChain: '試行チェーン',
attemptIndex: '呼び出し順', attemptIndex: '呼び出し順',
noAttemptDetails: 'このリクエストは 1 回で命中しましたfallback 明細はありません', noAttemptDetails: 'このリクエストは 1 回で命中しましたfallback 明細はありません',

View File

@@ -962,6 +962,7 @@ const message = {
failedRequests: '실패한 요청', failedRequests: '실패한 요청',
averageTokenPerRequest: '평균 Token/요청', averageTokenPerRequest: '평균 Token/요청',
callLogs: '호출 로그', callLogs: '호출 로그',
usageKeywordPlaceholder: 'Request ID / 모델 / 업스트림 모델 / 오류 메시지 검색',
attemptChain: '시도 체인', attemptChain: '시도 체인',
attemptIndex: '호출 순서', attemptIndex: '호출 순서',
noAttemptDetails: ' 요청은 번에 적중했으며 fallback 상세가 없습니다', noAttemptDetails: ' 요청은 번에 적중했으며 fallback 상세가 없습니다',

View File

@@ -987,6 +987,7 @@ const message = {
failedRequests: 'Permintaan gagal', failedRequests: 'Permintaan gagal',
averageTokenPerRequest: 'Purata Token/permintaan', averageTokenPerRequest: 'Purata Token/permintaan',
callLogs: 'Log panggilan', callLogs: 'Log panggilan',
usageKeywordPlaceholder: 'Cari Request ID / model / model upstream / mesej ralat',
attemptChain: 'Rantaian percubaan', attemptChain: 'Rantaian percubaan',
attemptIndex: 'Urutan panggilan', attemptIndex: 'Urutan panggilan',
noAttemptDetails: 'Permintaan ini berjaya sekali percubaan; tiada butiran fallback', noAttemptDetails: 'Permintaan ini berjaya sekali percubaan; tiada butiran fallback',

View File

@@ -983,6 +983,7 @@ const message = {
failedRequests: 'Requisições com falha', failedRequests: 'Requisições com falha',
averageTokenPerRequest: 'Média de Token/requisição', averageTokenPerRequest: 'Média de Token/requisição',
callLogs: 'Logs de chamadas', callLogs: 'Logs de chamadas',
usageKeywordPlaceholder: 'Pesquisar Request ID / modelo / modelo upstream / mensagem de erro',
attemptChain: 'Cadeia de tentativas', attemptChain: 'Cadeia de tentativas',
attemptIndex: 'Ordem de chamada', attemptIndex: 'Ordem de chamada',
noAttemptDetails: 'Esta requisição acertou na primeira tentativa; sem detalhes de fallback', noAttemptDetails: 'Esta requisição acertou na primeira tentativa; sem detalhes de fallback',

View File

@@ -977,6 +977,7 @@ const message = {
failedRequests: 'Неуспешные запросы', failedRequests: 'Неуспешные запросы',
averageTokenPerRequest: 'Среднее Token/запрос', averageTokenPerRequest: 'Среднее Token/запрос',
callLogs: 'Журнал вызовов', callLogs: 'Журнал вызовов',
usageKeywordPlaceholder: 'Поиск по Request ID / модели / upstream-модели / сообщению об ошибке',
attemptChain: 'Цепочка попыток', attemptChain: 'Цепочка попыток',
attemptIndex: 'Порядок вызова', attemptIndex: 'Порядок вызова',
noAttemptDetails: 'Запрос выполнен с первой попытки, деталей fallback нет', noAttemptDetails: 'Запрос выполнен с первой попытки, деталей fallback нет',

View File

@@ -985,6 +985,7 @@ const message = {
failedRequests: 'Başarısız istekler', failedRequests: 'Başarısız istekler',
averageTokenPerRequest: 'Ortalama Token/istek', averageTokenPerRequest: 'Ortalama Token/istek',
callLogs: 'Çağrı günlükleri', callLogs: 'Çağrı günlükleri',
usageKeywordPlaceholder: 'Request ID / model / upstream model / hata mesajı ara',
attemptChain: 'Deneme zinciri', attemptChain: 'Deneme zinciri',
attemptIndex: 'Çağrı sırası', attemptIndex: 'Çağrı sırası',
noAttemptDetails: 'Bu istek tek denemede isabet etti; fallback ayrıntısı yok', noAttemptDetails: 'Bu istek tek denemede isabet etti; fallback ayrıntısı yok',

View File

@@ -922,6 +922,7 @@ const message = {
failedRequests: '失敗請求', failedRequests: '失敗請求',
averageTokenPerRequest: '平均 Token/請求', averageTokenPerRequest: '平均 Token/請求',
callLogs: '呼叫日誌', callLogs: '呼叫日誌',
usageKeywordPlaceholder: '搜尋 Request ID / 模型 / 上游模型 / 錯誤資訊',
attemptChain: '呼叫鏈路', attemptChain: '呼叫鏈路',
attemptIndex: '調用順序', attemptIndex: '調用順序',
noAttemptDetails: '本次請求一次命中,無 fallback 明細', noAttemptDetails: '本次請求一次命中,無 fallback 明細',

View File

@@ -916,6 +916,7 @@ const message = {
failedRequests: '失败请求', failedRequests: '失败请求',
averageTokenPerRequest: '平均 Token/请求', averageTokenPerRequest: '平均 Token/请求',
callLogs: '调用日志', callLogs: '调用日志',
usageKeywordPlaceholder: '搜索 Request ID / 模型 / 上游模型 / 错误信息',
attemptChain: '调用链路', attemptChain: '调用链路',
attemptIndex: '调用顺序', attemptIndex: '调用顺序',
noAttemptDetails: '本次请求一次命中,无 fallback 明细', noAttemptDetails: '本次请求一次命中,无 fallback 明细',

View File

@@ -0,0 +1,168 @@
export interface AgentProviderLogo {
src?: string;
mark: string;
background: string;
color: string;
borderColor?: string;
source?: string;
}
const asset = (name: string) => new URL(`../assets/images/ai-providers/${name}`, import.meta.url).href;
const providerLogos: Record<string, AgentProviderLogo> = {
ollama: {
src: asset('ollama.webp'),
mark: 'OL',
background: '#ffffff',
color: '#111827',
borderColor: '#dcdfe6',
source: 'https://github.com/ollama/ollama/blob/main/docs/ollama-logo.svg',
},
vllm: {
src: asset('vllm.svg'),
mark: 'v',
background: '#ffffff',
color: '#4338ca',
borderColor: '#dcdfe6',
source: 'https://vllm.ai/vLLM-Logo.svg',
},
deepseek: {
src: asset('deepseek.png'),
mark: 'DS',
background: '#ffffff',
color: '#4d6bfe',
borderColor: '#dcdfe6',
source: 'https://cdn.deepseek.com/logo.png',
},
'bailian-coding-plan': {
src: asset('aliyun.webp'),
mark: 'QW',
background: '#ffffff',
color: '#ff6a00',
borderColor: '#dcdfe6',
source: 'https://www.alibabacloud.com/',
},
'ark-coding-plan': {
src: asset('volcengine.png'),
mark: 'AR',
background: '#ffffff',
color: '#1664ff',
borderColor: '#dcdfe6',
source: 'https://www.volcengine.com/',
},
zai: {
src: asset('zai.webp'),
mark: 'Z',
background: '#ffffff',
color: '#111827',
borderColor: '#dcdfe6',
source: 'https://z.ai/',
},
minimax: {
src: asset('minimax.ico'),
mark: 'MM',
background: '#ffffff',
color: '#2563eb',
borderColor: '#dcdfe6',
source: 'https://www.minimax.io/',
},
xiaomi: {
src: asset('xiaomi.ico'),
mark: 'MI',
background: '#ffffff',
color: '#ff6900',
borderColor: '#dcdfe6',
source: 'https://www.mi.com/favicon.ico',
},
kimi: {
src: asset('kimi.svg'),
mark: 'KM',
background: '#ffffff',
color: '#111827',
borderColor: '#dcdfe6',
source: 'https://moonshotai.github.io/Branding-Guide/',
},
'kimi-coding': {
src: asset('kimi.svg'),
mark: 'KC',
background: '#ffffff',
color: '#111827',
borderColor: '#dcdfe6',
source: 'https://moonshotai.github.io/Branding-Guide/',
},
openai: {
src: asset('openai.svg'),
mark: 'AI',
background: '#ffffff',
color: '#111827',
borderColor: '#dcdfe6',
source: 'https://openai.com/brand/',
},
openrouter: {
src: asset('openrouter.png'),
mark: 'OR',
background: '#ffffff',
color: '#101828',
borderColor: '#dcdfe6',
source: 'https://openrouter.ai/',
},
anthropic: {
src: asset('anthropic.png'),
mark: 'A',
background: '#ffffff',
color: '#141413',
borderColor: '#dcdfe6',
source: 'https://www.anthropic.com/',
},
gemini: {
src: asset('gemini.webp'),
mark: 'G',
background: '#ffffff',
color: '#1a73e8',
borderColor: '#dcdfe6',
source: 'https://gemini.google.com/',
},
moonshot: {
src: asset('kimi.svg'),
mark: 'MS',
background: '#ffffff',
color: '#111827',
borderColor: '#dcdfe6',
source: 'https://moonshotai.github.io/Branding-Guide/',
},
};
const fallbackLogo: AgentProviderLogo = {
mark: 'AI',
background: '#f5f7fa',
color: '#606266',
borderColor: '#dcdfe6',
};
const normalizeProvider = (provider?: string) =>
String(provider || '')
.trim()
.toLowerCase();
const buildFallbackMark = (provider?: string, displayName?: string) => {
const source = String(displayName || provider || '').trim();
const asciiWords = source.match(/[a-zA-Z0-9]+/g) || [];
if (asciiWords.length >= 2) {
return `${asciiWords[0][0]}${asciiWords[1][0]}`.toUpperCase();
}
if (asciiWords.length === 1) {
return asciiWords[0].slice(0, 2).toUpperCase();
}
return fallbackLogo.mark;
};
export const getAgentProviderLogo = (provider?: string, displayName?: string): AgentProviderLogo => {
const logo = providerLogos[normalizeProvider(provider)];
if (logo) {
return logo;
}
return {
...fallbackLogo,
mark: buildFallbackMark(provider, displayName),
};
};

View File

@@ -11,7 +11,12 @@
:key="item.value" :key="item.value"
:label="item.label" :label="item.label"
:value="item.value" :value="item.value"
/> >
<div class="provider-option">
<ProviderLogo :provider="item.value" :display-name="item.label" />
<span>{{ item.label }}</span>
</div>
</el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="API Key" prop="apiKey"> <el-form-item label="API Key" prop="apiKey">
@@ -88,6 +93,7 @@ import i18n from '@/lang';
import { getAgentProviderDisplayName } from '@/utils/agent'; import { getAgentProviderDisplayName } from '@/utils/agent';
import { MsgError } from '@/utils/message'; import { MsgError } from '@/utils/message';
import { useGlobalStore } from '@/composables/useGlobalStore'; import { useGlobalStore } from '@/composables/useGlobalStore';
import ProviderLogo from '@/components/agent-provider-logo/index.vue';
const emit = defineEmits(['search']); const emit = defineEmits(['search']);
@@ -355,3 +361,19 @@ defineExpose({
open: openDrawer, open: openDrawer,
}); });
</script> </script>
<style scoped lang="scss">
.provider-option {
display: inline-flex;
align-items: center;
gap: 6px;
min-width: 0;
white-space: nowrap;
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>

View File

@@ -15,7 +15,13 @@
<el-table-column :label="$t('commons.table.name')" prop="name" min-width="200" /> <el-table-column :label="$t('commons.table.name')" prop="name" min-width="200" />
<el-table-column :label="$t('aiTools.agents.provider')" prop="provider" width="200"> <el-table-column :label="$t('aiTools.agents.provider')" prop="provider" width="200">
<template #default="{ row }"> <template #default="{ row }">
{{ getAgentProviderDisplayName(row.provider, row.providerName) }} <div class="provider-cell">
<ProviderLogo
:provider="row.provider"
:display-name="getAgentProviderDisplayName(row.provider, row.providerName)"
/>
<span>{{ getAgentProviderDisplayName(row.provider, row.providerName) }}</span>
</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="Base URL" prop="baseUrl" min-width="200" /> <el-table-column label="Base URL" prop="baseUrl" min-width="200" />
@@ -57,6 +63,7 @@ import { deleteAgentAccount, pageAgentAccounts } from '@/api/modules/ai';
import { AI } from '@/api/interface/ai'; import { AI } from '@/api/interface/ai';
import AddDialog from '@/views/ai/agents/model/add/index.vue'; import AddDialog from '@/views/ai/agents/model/add/index.vue';
import ModelPoolDialog from '@/views/ai/agents/model/pool/index.vue'; import ModelPoolDialog from '@/views/ai/agents/model/pool/index.vue';
import ProviderLogo from '@/components/agent-provider-logo/index.vue';
import { ElMessageBox } from 'element-plus'; import { ElMessageBox } from 'element-plus';
import i18n from '@/lang'; import i18n from '@/lang';
import { dateFormat } from '@/utils/date'; import { dateFormat } from '@/utils/date';
@@ -177,3 +184,19 @@ onMounted(async () => {
await search(); await search();
}); });
</script> </script>
<style scoped lang="scss">
.provider-cell {
display: inline-flex;
align-items: center;
gap: 6px;
min-width: 0;
white-space: nowrap;
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>