feat: add icon for ai provider (#12722)
@@ -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) => {
|
||||||
|
|||||||
BIN
frontend/src/assets/images/ai-providers/aliyun.webp
Normal file
|
After Width: | Height: | Size: 281 B |
BIN
frontend/src/assets/images/ai-providers/anthropic.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
frontend/src/assets/images/ai-providers/deepseek.png
Normal file
|
After Width: | Height: | Size: 908 B |
BIN
frontend/src/assets/images/ai-providers/gemini.webp
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
4
frontend/src/assets/images/ai-providers/kimi.svg
Normal 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 |
BIN
frontend/src/assets/images/ai-providers/minimax.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
frontend/src/assets/images/ai-providers/ollama.webp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
1
frontend/src/assets/images/ai-providers/openai.svg
Normal 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 |
BIN
frontend/src/assets/images/ai-providers/openrouter.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
1
frontend/src/assets/images/ai-providers/vllm.svg
Normal 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 |
BIN
frontend/src/assets/images/ai-providers/volcengine.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
frontend/src/assets/images/ai-providers/xiaomi.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
frontend/src/assets/images/ai-providers/zai.webp
Normal file
|
After Width: | Height: | Size: 992 B |
56
frontend/src/components/agent-provider-logo/index.vue
Normal 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>
|
||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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 明細はありません',
|
||||||
|
|||||||
@@ -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 상세가 없습니다',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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 нет',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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 明細',
|
||||||
|
|||||||
@@ -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 明细',
|
||||||
|
|||||||
168
frontend/src/utils/agent-provider-logo.ts
Normal 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),
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||