feat: add icon support for speech providers and update related configurations

This commit is contained in:
aki
2026-04-19 00:19:49 +09:00
parent f0a0706d75
commit 9e237e63d4
20 changed files with 131 additions and 6 deletions
@@ -35,6 +35,8 @@ import {
Lmstudio,
Meta,
MetaColor,
Microsoft,
MicrosoftColor,
Minimax,
MinimaxColor,
Mistral,
@@ -105,6 +107,8 @@ export const iconMap: Record<string, Component> = {
'cohere-color': CohereColor,
'azure': Azure,
'azure-color': AzureColor,
'microsoft': Microsoft,
'microsoft-color': MicrosoftColor,
'nvidia': Nvidia,
'nvidia-color': NvidiaColor,
'fireworks': Fireworks,
@@ -1,7 +1,19 @@
<template>
<div class="p-4">
<section class="flex items-center gap-3">
<Volume2 class="size-5" />
<span class="flex size-10 shrink-0 items-center justify-center rounded-full bg-muted">
<ProviderIcon
v-if="curProvider?.icon"
:icon="curProvider.icon"
size="1.5em"
/>
<span
v-else
class="text-xs font-medium text-muted-foreground"
>
{{ getInitials(curProvider?.name) }}
</span>
</span>
<div class="min-w-0">
<h2 class="text-sm font-semibold truncate">
{{ curProvider?.name }}
@@ -191,7 +203,7 @@ import {
Switch,
} from '@memohai/ui'
import ModelConfigEditor from './model-config-editor.vue'
import { ChevronDown, ChevronUp, Eye, EyeOff, Volume2 } from 'lucide-vue-next'
import { ChevronDown, ChevronUp, Eye, EyeOff } from 'lucide-vue-next'
import { computed, inject, reactive, ref, watch } from 'vue'
import { toast } from 'vue-sonner'
import { useI18n } from 'vue-i18n'
@@ -199,6 +211,7 @@ import { useQuery, useQueryCache } from '@pinia/colada'
import { getSpeechModels, getSpeechProvidersMeta, putModelsById, putProvidersById } from '@memohai/sdk'
import type { TtsSpeechModelResponse, TtsSpeechProviderResponse } from '@memohai/sdk'
import LoadingButton from '@/components/loading-button/index.vue'
import ProviderIcon from '@/components/provider-icon/index.vue'
interface SpeechFieldSchema {
key: string
@@ -233,6 +246,11 @@ interface SpeechProviderMeta {
models?: SpeechModelMeta[]
}
function getInitials(name: string | undefined) {
const label = name?.trim() ?? ''
return label ? label.slice(0, 2).toUpperCase() : '?'
}
const { t } = useI18n()
const curProvider = inject('curTtsProvider', ref<TtsSpeechProviderResponse>())
const curProviderId = computed(() => curProvider.value?.id)
+16 -2
View File
@@ -18,6 +18,12 @@ import type { TtsSpeechProviderResponse } from '@memohai/sdk'
import ProviderSetting from './components/provider-setting.vue'
import { Volume2 } from 'lucide-vue-next'
import MasterDetailSidebarLayout from '@/components/master-detail-sidebar-layout/index.vue'
import ProviderIcon from '@/components/provider-icon/index.vue'
function getInitials(name: string | undefined) {
const label = name?.trim() ?? ''
return label ? label.slice(0, 2).toUpperCase() : '?'
}
const { data: providerData } = useQuery({
key: () => ['speech-providers'],
@@ -79,9 +85,17 @@ watch(filteredProviders, (list) => {
>
<span class="relative shrink-0">
<span class="flex size-7 items-center justify-center rounded-full bg-muted">
<Volume2
class="size-3.5 text-muted-foreground"
<ProviderIcon
v-if="item.icon"
:icon="item.icon"
size="1.25em"
/>
<span
v-else
class="text-xs font-medium text-muted-foreground"
>
{{ getInitials(item.name) }}
</span>
</span>
<span
v-if="item.enable !== false"
+8
View File
@@ -0,0 +1,8 @@
name: Alibaba Cloud Speech
client_type: alibabacloud-speech
icon: bailian-color
models:
- model_id: cosyvoice-tts
name: CosyVoice TTS
type: speech
+7
View File
@@ -0,0 +1,7 @@
name: Deepgram Speech
client_type: deepgram-speech
models:
- model_id: deepgram-tts
name: Deepgram TTS
type: speech
+1 -1
View File
@@ -1,6 +1,6 @@
name: Edge
client_type: edge-speech
icon: edge
icon: microsoft
models:
- model_id: edge-read-aloud
+7
View File
@@ -0,0 +1,7 @@
name: ElevenLabs Speech
client_type: elevenlabs-speech
models:
- model_id: elevenlabs-tts
name: ElevenLabs TTS
type: speech
+8
View File
@@ -0,0 +1,8 @@
name: Microsoft Speech
client_type: microsoft-speech
icon: azure-color
models:
- model_id: microsoft-tts
name: Microsoft TTS
type: speech
+8
View File
@@ -0,0 +1,8 @@
name: MiniMax Speech
client_type: minimax-speech
icon: minimax-color
models:
- model_id: minimax-tts
name: MiniMax TTS
type: speech
+8
View File
@@ -0,0 +1,8 @@
name: OpenAI Speech
client_type: openai-speech
icon: openai
models:
- model_id: gpt-4o-mini-tts
name: GPT-4o Mini TTS
type: speech
+8
View File
@@ -0,0 +1,8 @@
name: OpenRouter Speech
client_type: openrouter-speech
icon: openrouter
models:
- model_id: openrouter-tts
name: OpenRouter TTS
type: speech
+8
View File
@@ -0,0 +1,8 @@
name: Volcengine Speech
client_type: volcengine-speech
icon: volcengine-color
models:
- model_id: sami-tts
name: SAMI TTS
type: speech
+5 -1
View File
@@ -17,11 +17,15 @@ func SyncRegistry(ctx context.Context, logger *slog.Logger, queries *sqlc.Querie
if err != nil {
return fmt.Errorf("marshal speech provider config: %w", err)
}
var icon pgtype.Text
if def.Icon != "" {
icon = pgtype.Text{String: def.Icon, Valid: true}
}
provider, err := queries.UpsertRegistryProvider(ctx, sqlc.UpsertRegistryProviderParams{
Name: def.DisplayName,
ClientType: string(def.ClientType),
Icon: pgtype.Text{},
Icon: icon,
Config: configJSON,
})
if err != nil {
+8
View File
@@ -25,6 +25,7 @@ type ProviderFactory func(config map[string]any) (sdk.SpeechProvider, error)
type ProviderDefinition struct {
ClientType models.ClientType
DisplayName string
Icon string
Description string
ConfigSchema ConfigSchema
DefaultModel string
@@ -123,6 +124,7 @@ func defaultProviderDefinitions() []ProviderDefinition {
{
ClientType: models.ClientTypeEdgeSpeech,
DisplayName: "Microsoft Edge",
Icon: "microsoft",
Description: "Free Edge Read Aloud TTS",
ConfigSchema: ConfigSchema{Fields: []FieldSchema{stringField("base_url", "Base URL", "Override the Edge WebSocket endpoint", false, "", 10)}},
DefaultModel: "edge-read-aloud",
@@ -163,6 +165,7 @@ func defaultProviderDefinitions() []ProviderDefinition {
{
ClientType: models.ClientTypeOpenAISpeech,
DisplayName: "OpenAI Speech",
Icon: "openai",
Description: "OpenAI /audio/speech compatible TTS",
ConfigSchema: ConfigSchema{Fields: []FieldSchema{
secretField("api_key", "API Key", "Bearer API key", true, 10),
@@ -204,6 +207,7 @@ func defaultProviderDefinitions() []ProviderDefinition {
{
ClientType: models.ClientTypeOpenRouterSpeech,
DisplayName: "OpenRouter Speech",
Icon: "openrouter",
Description: "OpenRouter audio modality TTS",
ConfigSchema: ConfigSchema{Fields: []FieldSchema{
secretField("api_key", "API Key", "OpenRouter API key", true, 10),
@@ -333,6 +337,7 @@ func defaultProviderDefinitions() []ProviderDefinition {
{
ClientType: models.ClientTypeMiniMaxSpeech,
DisplayName: "MiniMax Speech",
Icon: "minimax-color",
Description: "MiniMax TTS",
ConfigSchema: ConfigSchema{Fields: []FieldSchema{
secretField("api_key", "API Key", "MiniMax API key", true, 10),
@@ -380,6 +385,7 @@ func defaultProviderDefinitions() []ProviderDefinition {
{
ClientType: models.ClientTypeVolcengineSpeech,
DisplayName: "Volcengine Speech",
Icon: "volcengine-color",
Description: "Volcengine SAMI TTS",
ConfigSchema: ConfigSchema{Fields: []FieldSchema{
secretField("access_key", "Access Key", "Volcengine access key", true, 10),
@@ -431,6 +437,7 @@ func defaultProviderDefinitions() []ProviderDefinition {
{
ClientType: models.ClientTypeAlibabaSpeech,
DisplayName: "Alibaba Cloud Speech",
Icon: "bailian-color",
Description: "DashScope CosyVoice TTS",
ConfigSchema: ConfigSchema{Fields: []FieldSchema{
secretField("api_key", "API Key", "DashScope API key", true, 10),
@@ -478,6 +485,7 @@ func defaultProviderDefinitions() []ProviderDefinition {
{
ClientType: models.ClientTypeMicrosoftSpeech,
DisplayName: "Microsoft Speech",
Icon: "azure-color",
Description: "Azure Cognitive Services TTS",
ConfigSchema: ConfigSchema{Fields: []FieldSchema{
secretField("api_key", "API Key", "Azure speech subscription key", true, 10),
+5
View File
@@ -209,10 +209,15 @@ func mergeConfig(parts ...map[string]any) map[string]any {
}
func toSpeechProviderResponse(row sqlc.Provider) SpeechProviderResponse {
icon := ""
if row.Icon.Valid {
icon = row.Icon.String
}
return SpeechProviderResponse{
ID: row.ID.String(),
Name: row.Name,
ClientType: row.ClientType,
Icon: icon,
Enable: row.Enable,
CreatedAt: row.CreatedAt.Time,
UpdatedAt: row.UpdatedAt.Time,
+1
View File
@@ -17,6 +17,7 @@ type SpeechProviderResponse struct {
ID string `json:"id"`
Name string `json:"name"`
ClientType string `json:"client_type"`
Icon string `json:"icon,omitempty"`
Enable bool `json:"enable"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
+1
View File
@@ -1709,6 +1709,7 @@ export type TtsSpeechProviderResponse = {
client_type?: string;
created_at?: string;
enable?: boolean;
icon?: string;
id?: string;
name?: string;
updated_at?: string;
+3
View File
@@ -13111,6 +13111,9 @@ const docTemplate = `{
"enable": {
"type": "boolean"
},
"icon": {
"type": "string"
},
"id": {
"type": "string"
},
+3
View File
@@ -13102,6 +13102,9 @@
"enable": {
"type": "boolean"
},
"icon": {
"type": "string"
},
"id": {
"type": "string"
},
+2
View File
@@ -2880,6 +2880,8 @@ definitions:
type: string
enable:
type: boolean
icon:
type: string
id:
type: string
name: