Skip to content

Commit 4dfa3f4

Browse files
committed
refactor: providers directory structure
1 parent 6b3204a commit 4dfa3f4

File tree

20 files changed

+167
-69
lines changed

20 files changed

+167
-69
lines changed

ui/src/components/AddKeyDialog.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import { useI18n } from 'vue-i18n'
33
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'
4-
import { useProvidersStore } from '@/stores'
4+
import { useProviders } from '@/lib/providers'
55
import ManualConfigCard from './ManualConfigCard.vue'
66
import ProviderCard from './ProviderCard.vue'
77
@@ -12,7 +12,7 @@ const emit = defineEmits<{
1212
const open = defineModel<boolean>('open')
1313
1414
const { t } = useI18n()
15-
const providersStore = useProvidersStore()
15+
const providers = useProviders()
1616
1717
async function handleConfigured(key: string) {
1818
emit('configured', key)
@@ -31,7 +31,7 @@ async function handleConfigured(key: string) {
3131
</DialogHeader>
3232

3333
<div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6">
34-
<ProviderCard v-for="provider in providersStore.list" :key="provider.id" :provider="provider" @configured="handleConfigured" />
34+
<ProviderCard v-for="provider in providers" :key="provider.id" :provider="provider" @configured="handleConfigured" />
3535
<ManualConfigCard @configured="handleConfigured" />
3636
</div>
3737
</DialogContent>

ui/src/components/KeySelector.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { onMounted, ref, shallowRef, watchEffect } from 'vue'
55
import { useI18n } from 'vue-i18n'
66
import { Badge } from '@/components/ui/badge'
77
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
8-
import { useKeysStore, useProvidersStore } from '@/stores'
8+
import { useProviders } from '@/lib/providers'
9+
import { useKeysStore } from '@/stores'
910
import AddKeyDialog from './AddKeyDialog.vue'
1011
import ManualConfigCard from './ManualConfigCard.vue'
1112
import ProviderConfigDialog from './ProviderConfigDialog.vue'
@@ -16,7 +17,7 @@ const props = defineProps<{
1617
}>()
1718
const selectedKeyId = defineModel<string>()
1819
19-
const providersStore = useProvidersStore()
20+
const providers = useProviders()
2021
const keysStore = useKeysStore()
2122
const { t } = useI18n()
2223
@@ -52,7 +53,7 @@ watchEffect(() => {
5253
</template>
5354
<template v-else>
5455
<div class="grid gap-4 grid-cols-2 mb-4">
55-
<Card v-for="provider in providersStore.list" :key="provider.id" class="relative gap-2">
56+
<Card v-for="provider in providers" :key="provider.id" class="relative gap-2">
5657
<CardHeader>
5758
<div class="flex items-center justify-between">
5859
<CardTitle class="text-lg">

ui/src/components/KeysSection.vue

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@ import { Badge } from '@/components/ui/badge'
1212
import { Button } from '@/components/ui/button'
1313
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
1414
import { Skeleton } from '@/components/ui/skeleton'
15-
import { useKeysStore, useProvidersStore } from '@/stores'
15+
import { useProviders } from '@/lib/providers'
16+
import { useKeysStore } from '@/stores'
1617
1718
const { t } = useI18n()
1819
const keysStore = useKeysStore()
19-
const providersStore = useProvidersStore()
20+
const providers = useProviders()
2021
2122
const showProviderDialog = shallowRef<Provider | null>(null)
2223
const showEditDialog = ref(false)
2324
const editingKey = ref<APIKey | null>(null)
2425
2526
function editKey(key: APIKey) {
26-
if (providersStore.map[key.type]) {
27-
showProviderDialog.value = providersStore.map[key.type]
27+
if (providers[key.type]) {
28+
showProviderDialog.value = providers[key.type]
2829
return
2930
}
3031
editingKey.value = key
@@ -66,7 +67,7 @@ onMounted(() => {
6667
<!-- Static cards for configuration (not draggable) -->
6768
<div class="grid gap-4 grid-cols-2 lg:grid-cols-3 mb-4">
6869
<!-- SiliconFlow configuration card (if not configured) -->
69-
<Card v-for="provider in providersStore.list" :key="provider.id" class="relative gap-2">
70+
<Card v-for="provider in providers" :key="provider.id" class="relative gap-2">
7071
<CardHeader>
7172
<div class="flex items-center justify-between">
7273
<CardTitle class="text-lg">
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<script setup lang="ts">
2+
import type { Provider } from '@/lib/providers'
3+
import { shallowRef } from 'vue'
4+
import { useI18n } from 'vue-i18n'
5+
import ManualConfigCard from '@/components/ManualConfigCard.vue'
6+
import ProviderConfigDialog from '@/components/ProviderConfigDialog.vue'
7+
import { Button } from '@/components/ui/button'
8+
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
9+
import { useProviders } from '@/lib/providers'
10+
11+
const { t } = useI18n()
12+
const providers = useProviders()
13+
14+
const showProviderDialog = shallowRef<Provider | null>(null)
15+
</script>
16+
17+
<template>
18+
<div class="space-y-6 flex-grow flex flex-col min-h-100">
19+
<div class="flex items-center justify-between">
20+
<h2 class="text-2xl font-bold">
21+
{{ t('title') }}
22+
</h2>
23+
</div>
24+
25+
<div class="flex-grow flex flex-col">
26+
<!-- Static cards for configuration (not draggable) -->
27+
<div class="grid gap-4 grid-cols-2 lg:grid-cols-3 mb-4">
28+
<!-- SiliconFlow configuration card (if not configured) -->
29+
<Card v-for="provider in providers" :key="provider.id" class="relative gap-2">
30+
<CardHeader>
31+
<div class="flex items-center justify-between">
32+
<CardTitle class="text-lg">
33+
{{ provider.name }}
34+
</CardTitle>
35+
<div v-show="provider.user !== undefined" class="flex items-center gap-2">
36+
<span
37+
class="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium text-accent-foreground"
38+
:class="{
39+
'bg-emerald-200 dark:bg-green-500/60': !!provider.user,
40+
'bg-accent': !provider.user,
41+
}"
42+
>
43+
{{ provider.user ? t('loggedIn') : t('loggedOut') }}
44+
</span>
45+
</div>
46+
</div>
47+
</CardHeader>
48+
49+
<CardContent>
50+
<div class="text-sm text-muted-foreground">
51+
<p>
52+
{{ t('description1') }}
53+
<a :href="provider.homepage" target="_blank" class="text-blue-900 dark:text-blue-200 hover:underline">
54+
{{ provider.name }}
55+
</a>
56+
{{ t('description2') }}
57+
</p>
58+
</div>
59+
</CardContent>
60+
61+
<CardFooter>
62+
<Button class="w-full" @click="showProviderDialog = provider">
63+
{{ t('configure', [provider.name]) }}
64+
</Button>
65+
</CardFooter>
66+
</Card>
67+
68+
<!-- Manual Configuration Provider Card -->
69+
<ManualConfigCard class="relative gap-2" />
70+
</div>
71+
</div>
72+
73+
<ProviderConfigDialog
74+
v-if="showProviderDialog != null"
75+
:provider="showProviderDialog"
76+
@close="showProviderDialog = null"
77+
/>
78+
</div>
79+
</template>
80+
81+
<i18n lang="yaml">
82+
zh-CN:
83+
title: 模型供应商
84+
loadFailed: 加载失败
85+
description1: 通过
86+
description2: 购买和配置 API
87+
loggedIn: 已登录
88+
loggedOut: 未登录
89+
configure: 配置 {0}
90+
91+
en-US:
92+
title: Model Providers
93+
loadFailed: Failed to load
94+
description1: Purchase and configure API through
95+
description2: ''
96+
loggedIn: Logged In
97+
loggedOut: Logged Out
98+
configure: Configure {0}
99+
</i18n>

ui/src/components/ShumeiCaptcha.vue renamed to ui/src/components/captcha/ShumeiCaptcha.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useScriptTag } from '@vueuse/core'
33
import { VisuallyHidden } from 'reka-ui'
44
import { computed, ref } from 'vue'
55
import { useI18n } from 'vue-i18n'
6-
import { Dialog, DialogContent, DialogDescription, DialogTitle } from './ui/dialog'
6+
import { Dialog, DialogContent, DialogDescription, DialogTitle } from '../ui/dialog'
77
88
const state = ref<'loading' | 'init' | 'pending' | 'success'>('loading')
99

ui/src/components/DeepSeekLoginCard.vue renamed to ui/src/lib/providers/deepseek/Login.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
import { computed, onUnmounted, ref } from 'vue'
33
import { useI18n } from 'vue-i18n'
44
import { toast } from 'vue-sonner'
5+
import ShumeiCaptcha, { deviceId } from '@/components/captcha/ShumeiCaptcha.vue'
56
import { Button } from '@/components/ui/button'
67
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
78
import { Input } from '@/components/ui/input'
8-
import { useDeepSeekProvider } from '@/lib/providers/deepseek'
99
import { useKeysStore } from '@/stores'
10-
import ShumeiCaptcha, { deviceId } from './ShumeiCaptcha.vue'
10+
import { useDeepSeekProvider } from '.'
1111
1212
const { t, locale } = useI18n()
1313
const keysStore = useKeysStore()

ui/src/lib/providers/deepseek.ts renamed to ui/src/lib/providers/deepseek/index.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { ProviderUserInfo } from './index'
1+
import type { ProviderUserInfo } from '@/lib/providers'
22
import { defineAsyncComponent, ref } from 'vue'
3+
import { useI18n } from '@/lib/locals'
4+
import { defineProvider, useProviderSession } from '@/lib/providers'
35
import { useServiceStore } from '@/stores'
4-
import { useI18n } from '../locals'
5-
import { defineProvider, useProviderSession } from './index'
66

77
export const useDeepSeekProvider = defineProvider(() => {
88
const { t } = useI18n({
@@ -52,6 +52,9 @@ export const useDeepSeekProvider = defineProvider(() => {
5252
get name() {
5353
return t('providerName')
5454
},
55+
get logo() {
56+
return import.meta.resolve('./logo.png')
57+
},
5558
homepage: 'https://www.deepseek.com/',
5659

5760
get user() {
@@ -90,7 +93,7 @@ export const useDeepSeekProvider = defineProvider(() => {
9093
}
9194
},
9295

93-
Login: defineAsyncComponent(() => import('@/components/DeepSeekLoginCard.vue')),
96+
Login: defineAsyncComponent(() => import('@/lib/providers/deepseek/Login.vue')),
9497
async logout() {
9598
await session.delete()
9699
user.value = null
8.1 KB
Loading

ui/src/lib/providers/index.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import type { Component } from 'vue'
22
import { createSharedComposable } from '@vueuse/core'
3+
import { markRaw } from 'vue'
34
import { defineDbStore } from '@/stores/db'
5+
import { useDeepSeekProvider } from './deepseek'
6+
import { useOpenRouterProvider } from './openrouter'
7+
import { useSiliconFlowProvider } from './siliconflow'
48

59
export interface ProviderUserInfo {
610
name: string
@@ -17,9 +21,13 @@ export interface ProviderVerificationInfo {
1721
time?: number
1822
}
1923

20-
export interface Provider<A> {
24+
export interface Provider<A = unknown> {
2125
readonly id: string
2226
readonly name: string
27+
/**
28+
* Reference: https://github.com/CherryHQ/cherry-studio/tree/main/src/renderer/src/assets/images/providers
29+
*/
30+
readonly logo: string
2331
readonly homepage: string
2432

2533
/**
@@ -89,3 +97,18 @@ export function useProviderSession<T>(providerId: string) {
8997
export function defineProvider<A>(provider: () => Provider<A>): () => Provider<A> {
9098
return createSharedComposable(provider)
9199
}
100+
101+
export const useProviders = createSharedComposable(() => {
102+
const list = [
103+
useSiliconFlowProvider(),
104+
useDeepSeekProvider(),
105+
useOpenRouterProvider(),
106+
]
107+
const map = Object.fromEntries(list.map(p => [p.id, markRaw(p)])) as Record<string, Provider>
108+
109+
for (const provider of list) {
110+
provider.refreshUser()
111+
}
112+
113+
return map
114+
})

0 commit comments

Comments
 (0)