From ba3acfc2f114ccb6f8eccaf4914ff36ed9e5ddfe Mon Sep 17 00:00:00 2001 From: _Kerman Date: Mon, 10 Nov 2025 14:23:10 +0800 Subject: [PATCH 1/8] refactor: move db and proxy logic to ui --- service/server/auth.go | 7 +- service/server/deep-seek/deep-seek.go | 1 + service/server/proxy.go | 59 +++++ service/server/server.go | 9 +- service/server/store.go | 109 ++++++++++ service/store/store.go | 45 ++-- ui/src/App.vue | 2 +- ui/src/components/DeepSeekLoginCard.vue | 11 +- ui/src/components/OpenRouterLoginCard.vue | 2 +- ui/src/components/ShumeiCaptcha.vue | 41 +++- ui/src/components/SiliconFlowLoginCard.vue | 63 +++--- ui/src/lib/providers/deepseek.ts | 16 +- ui/src/lib/providers/index.ts | 24 +++ ui/src/lib/providers/openrouter.ts | 40 ++-- ui/src/lib/providers/siliconflow.ts | 240 ++++++++++++++++++--- ui/src/stores/app.ts | 2 +- ui/src/stores/auth.ts | 4 +- ui/src/stores/db.ts | 39 ++++ ui/src/stores/keys.ts | 10 +- ui/src/stores/presets.ts | 2 +- ui/src/stores/service.ts | 91 ++++---- ui/src/views/Usage.vue | 2 +- 22 files changed, 636 insertions(+), 183 deletions(-) create mode 100644 service/server/proxy.go create mode 100644 service/server/store.go create mode 100644 ui/src/stores/db.ts diff --git a/service/server/auth.go b/service/server/auth.go index 4532691..07de66c 100644 --- a/service/server/auth.go +++ b/service/server/auth.go @@ -139,9 +139,12 @@ func handleRegister(c *gin.Context) { func RequireUserLogin() gin.HandlerFunc { return func(c *gin.Context) { + if c.Request.Method == "OPTIONS" { + c.Next() + return + } authHeader := c.GetHeader("Authorization") if authHeader == "" { - c.Header("X-Uni-Token-Error", "missing_authorization_header") c.Status(http.StatusUnauthorized) c.Abort() return @@ -149,7 +152,6 @@ func RequireUserLogin() gin.HandlerFunc { tokenParts := strings.Split(authHeader, " ") if len(tokenParts) != 2 || tokenParts[0] != "Bearer" { - c.Header("X-Uni-Token-Error", "missing_authorization_header") c.Status(http.StatusUnauthorized) c.Abort() return @@ -157,7 +159,6 @@ func RequireUserLogin() gin.HandlerFunc { claims, err := logic.ValidateJWT(tokenParts[1]) if err != nil { - c.Header("X-Uni-Token-Error", "invalid_token") c.Status(http.StatusUnauthorized) c.Abort() return diff --git a/service/server/deep-seek/deep-seek.go b/service/server/deep-seek/deep-seek.go index 87a23ee..ded8a62 100644 --- a/service/server/deep-seek/deep-seek.go +++ b/service/server/deep-seek/deep-seek.go @@ -105,6 +105,7 @@ func setCommonHeaders(req *http.Request) { req.Header.Set("sec-fetch-dest", "empty") req.Header.Set("sec-fetch-mode", "cors") req.Header.Set("sec-fetch-site", "same-origin") + req.Header.Set("x-app-version", "20240425.0") req.Header.Set("user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0") } diff --git a/service/server/proxy.go b/service/server/proxy.go new file mode 100644 index 0000000..ea78989 --- /dev/null +++ b/service/server/proxy.go @@ -0,0 +1,59 @@ +package server + +import ( + "bytes" + "net/http" + + "github.com/gin-gonic/gin" +) + +func SetupProxyAPI(router gin.IRouter) { + api := router.Group("/proxy").Use(RequireUserLogin()) + { + api.GET("", handleProxy) + api.POST("", handleProxy) + api.DELETE("", handleProxy) + api.PUT("", handleProxy) + } +} + +type ProxyRequest struct { + Url string `json:"url" binding:"required"` + Headers map[string]string `json:"headers"` + Body string `json:"body"` +} + +func handleProxy(c *gin.Context) { + println("Handle proxy") + + var req ProxyRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + client := &http.Client{} + proxyReq, err := http.NewRequest(c.Request.Method, req.Url, bytes.NewReader([]byte(req.Body))) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create proxy request"}) + return + } + + for key, value := range req.Headers { + proxyReq.Header.Set(key, value) + } + + resp, err := client.Do(proxyReq) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to perform proxy request"}) + return + } + defer resp.Body.Close() + + for key, values := range resp.Header { + for _, value := range values { + c.Writer.Header().Add(key, value) + } + } + c.Writer.WriteHeader(resp.StatusCode) +} diff --git a/service/server/server.go b/service/server/server.go index f2e05d2..52425b9 100644 --- a/service/server/server.go +++ b/service/server/server.go @@ -7,9 +7,6 @@ import ( "strconv" "time" "uni-token-service/logic" - deepSeek "uni-token-service/server/deep-seek" - siliconFlow "uni-token-service/server/silicon-flow" - openRouter "uni-token-service/server/open-router" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" @@ -50,10 +47,8 @@ func setupRoutes(router *gin.Engine) { SetupPresetsAPI(router) SetupUsageAPI(router) SetupAuthAPI(router) - - siliconFlow.SetupAPI(router.Group("/siliconflow").Use(RequireUserLogin())) - deepSeek.SetupAPI(router.Group("/deepseek").Use(RequireUserLogin())) - openRouter.SetupAPI(router.Group("/openrouter").Use(RequireUserLogin())) + SetupProxyAPI(router) + SetupStoreAPI(router) } func isPortAvailable(port int) bool { diff --git a/service/server/store.go b/service/server/store.go new file mode 100644 index 0000000..5bbf86e --- /dev/null +++ b/service/server/store.go @@ -0,0 +1,109 @@ +package server + +import ( + "io" + "uni-token-service/store" + + "github.com/gin-gonic/gin" + "go.etcd.io/bbolt" +) + +func SetupStoreAPI(router gin.IRouter) { + api := router.Group("/store").Use(RequireUserLogin()) + { + api.DELETE("/:name", handleStoreDeleteAll) + api.GET("/:name/:key", handleStoreGet) + api.POST("/:name/:key", handleStorePut) + api.DELETE("/:name/:key", handleStoreDelete) + } +} + +var createdBuckets = map[string]bool{} + +func ensureBucket(name string) error { + if created, exists := createdBuckets[name]; exists && created { + return nil + } + return store.Db.Update(func(tx *bbolt.Tx) error { + createdBuckets[name] = true + _, err := tx.CreateBucketIfNotExists([]byte(name)) + return err + }) +} + +func handleStoreDeleteAll(c *gin.Context) { + name := c.Param("name") + err := store.DeleteBucket(name) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + c.Status(200) +} + +func handleStoreGet(c *gin.Context) { + name := c.Param("name") + key := c.Param("key") + err := ensureBucket(name) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + err = store.Db.View(func(tx *bbolt.Tx) error { + b := tx.Bucket([]byte(name)) + v := b.Get([]byte(key)) + if v == nil { + c.JSON(200, nil) + return nil + } + c.Data(200, "application/json", v) + return nil + }) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } +} + +func handleStorePut(c *gin.Context) { + name := c.Param("name") + key := c.Param("key") + err := ensureBucket(name) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + body, err := io.ReadAll(c.Request.Body) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + err = store.Db.Update(func(tx *bbolt.Tx) error { + b := tx.Bucket([]byte(name)) + return b.Put([]byte(key), body) + }) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + c.Status(200) +} + +func handleStoreDelete(c *gin.Context) { + name := c.Param("name") + key := c.Param("key") + err := ensureBucket(name) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + err = store.Db.Update(func(tx *bbolt.Tx) error { + b := tx.Bucket([]byte(name)) + return b.Delete([]byte(key)) + }) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + c.Status(200) +} diff --git a/service/store/store.go b/service/store/store.go index 4d2711d..9fc399c 100644 --- a/service/store/store.go +++ b/service/store/store.go @@ -10,18 +10,27 @@ import ( type Bucket[T any] struct { bucketName string - db *bbolt.DB } -func NewBucket[T any](bucketName string, db *bbolt.DB) Bucket[T] { +func CreateBucket[T any](bucketName string) (Bucket[T], error) { b := Bucket[T]{ bucketName: bucketName, - db: db, } - err := b.db.Update(func(tx *bbolt.Tx) error { + err := Db.Update(func(tx *bbolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte(b.bucketName)) return err }) + return b, err +} + +func DeleteBucket(bucketName string) error { + return Db.Update(func(tx *bbolt.Tx) error { + return tx.DeleteBucket([]byte(bucketName)) + }) +} + +func InitBucket[T any](bucketName string) Bucket[T] { + b, err := CreateBucket[T](bucketName) if err != nil { log.Fatal("Failed to create bucket:", err) } @@ -30,7 +39,7 @@ func NewBucket[T any](bucketName string, db *bbolt.DB) Bucket[T] { func (b *Bucket[T]) List() ([]T, error) { result := make([]T, 0) - return result, b.db.View(func(tx *bbolt.Tx) error { + return result, Db.View(func(tx *bbolt.Tx) error { b := tx.Bucket([]byte(b.bucketName)) return b.ForEach(func(k, v []byte) error { var data T @@ -50,7 +59,7 @@ func (b *Bucket[T]) Put(key string, data T) error { return err } - return b.db.Update(func(tx *bbolt.Tx) error { + return Db.Update(func(tx *bbolt.Tx) error { b := tx.Bucket([]byte(b.bucketName)) return b.Put([]byte(key), v) }) @@ -58,7 +67,7 @@ func (b *Bucket[T]) Put(key string, data T) error { func (b *Bucket[T]) Get(key string) (T, error) { var data T - err := b.db.View(func(tx *bbolt.Tx) error { + err := Db.View(func(tx *bbolt.Tx) error { b := tx.Bucket([]byte(b.bucketName)) v := b.Get([]byte(key)) return json.Unmarshal(v, &data) @@ -67,14 +76,14 @@ func (b *Bucket[T]) Get(key string) (T, error) { } func (b *Bucket[T]) Delete(key string) error { - return b.db.Update(func(tx *bbolt.Tx) error { + return Db.Update(func(tx *bbolt.Tx) error { b := tx.Bucket([]byte(b.bucketName)) return b.Delete([]byte(key)) }) } func (b *Bucket[T]) Clear() error { - return b.db.Update(func(tx *bbolt.Tx) error { + return Db.Update(func(tx *bbolt.Tx) error { err := tx.DeleteBucket([]byte(b.bucketName)) if err != nil { return err @@ -86,7 +95,7 @@ func (b *Bucket[T]) Clear() error { func (b *Bucket[T]) Count() (int, error) { var count int - err := b.db.View(func(tx *bbolt.Tx) error { + err := Db.View(func(tx *bbolt.Tx) error { b := tx.Bucket([]byte(b.bucketName)) count = b.Inspect().KeyN return nil @@ -95,7 +104,7 @@ func (b *Bucket[T]) Count() (int, error) { } var ( - db *bbolt.DB + Db *bbolt.DB Users Bucket[UserInfo] Apps Bucket[AppInfo] @@ -107,17 +116,17 @@ var ( func Init(dbPath string) { var err error - db, err = bbolt.Open(dbPath, 0600, &bbolt.Options{Timeout: 1 * time.Second}) + Db, err = bbolt.Open(dbPath, 0600, &bbolt.Options{Timeout: 1 * time.Second}) if err != nil { log.Fatal("Failed to open database:", err) } - Users = NewBucket[UserInfo]("users", db) - Usage = NewBucket[TokenUsage]("usage", db) - Apps = NewBucket[AppInfo]("apps", db) - LLMKeys = NewBucket[LLMKey]("llm_keys", db) - AppPresets = NewBucket[AppPreset]("app_presets", db) - Providers = NewBucket[[]byte]("providers", db) + Users = InitBucket[UserInfo]("users") + Usage = InitBucket[TokenUsage]("usage") + Apps = InitBucket[AppInfo]("apps") + LLMKeys = InitBucket[LLMKey]("llm_keys") + AppPresets = InitBucket[AppPreset]("app_presets") + Providers = InitBucket[[]byte]("providers") count, err := AppPresets.Count() if err != nil { diff --git a/ui/src/App.vue b/ui/src/App.vue index 57a9b20..b96e597 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -48,7 +48,7 @@ async function onUIOpened() { async function pingActive() { try { - const resp = await serviceStore.fetch('ui/active', { + const resp = await serviceStore.api('ui/active', { method: 'POST', body: JSON.stringify({ session }), }) diff --git a/ui/src/components/DeepSeekLoginCard.vue b/ui/src/components/DeepSeekLoginCard.vue index fba5f8f..783e9c7 100644 --- a/ui/src/components/DeepSeekLoginCard.vue +++ b/ui/src/components/DeepSeekLoginCard.vue @@ -12,7 +12,7 @@ import ShumeiCaptcha from './ShumeiCaptcha.vue' const { t, locale } = useI18n() const keysStore = useKeysStore() const provider = useDeepSeekProvider() -const { fetch } = useServiceStore() +const { api: fetch } = useServiceStore() const captchaConfig = { organization: 'P9usCUBauxft8eAmUXaZ', @@ -50,7 +50,7 @@ function startCountdown() { }, 1000) } -async function sendSMS(rid: string) { +async function sendSMS(rid: string, device_id: string) { if (!canSendCode.value) return @@ -61,10 +61,11 @@ async function sendSMS(rid: string) { const res = await fetch('deepseek/sms', { body: JSON.stringify({ locale: locale.value === 'zh-CN' ? 'zh_CN' : 'en_US', - mobile_number: phoneNumber.value, turnstile_token: '', - shumei_verification: { region: 'GLOBAL', rid }, - device_id: 'BpeI75x/8jEyx0Cf8+ceENFycckj5NmfAgbRg/za+xaDDzFfBlTiLwSJAqAg0PpFarvtePSmNZWgonTdCjntvWw==', + shumei_verification: { region: 'CN', rid }, + device_id, + scenario: 'login', + mobile_number: phoneNumber.value, }), method: 'POST', }) diff --git a/ui/src/components/OpenRouterLoginCard.vue b/ui/src/components/OpenRouterLoginCard.vue index a3fb61d..6ff2e25 100644 --- a/ui/src/components/OpenRouterLoginCard.vue +++ b/ui/src/components/OpenRouterLoginCard.vue @@ -7,7 +7,7 @@ import { useOpenRouterProvider } from '@/lib/providers/openrouter' import { useKeysStore, useServiceStore } from '@/stores' const { t } = useI18n() -const { fetch } = useServiceStore() +const { api: fetch } = useServiceStore() const provider = useOpenRouterProvider() const keysStore = useKeysStore() diff --git a/ui/src/components/ShumeiCaptcha.vue b/ui/src/components/ShumeiCaptcha.vue index 235a64e..5571984 100644 --- a/ui/src/components/ShumeiCaptcha.vue +++ b/ui/src/components/ShumeiCaptcha.vue @@ -10,7 +10,7 @@ const props = defineProps<{ config: any }>() const emits = defineEmits<{ - next: [result: any] + next: [result: any, device_id: string] }>() const { t } = useI18n() @@ -18,25 +18,54 @@ const { t } = useI18n() const state = ref<'loading' | 'init' | 'pending' | 'success'>('loading') const captcha = ref(null) +const window_ = window as unknown as { + initSMCaptcha: any + _smConf: any + _smReadyFuncs: any[] + SMSdk: any +} + useScriptTag('https://castatic.fengkongcloud.cn/pr/v1.0.4/smcp.min.js', () => { state.value = 'init' }) +window_._smReadyFuncs = [] +window_.SMSdk = { + ready(fn: any) { + fn && window_._smReadyFuncs.push(fn) + }, +} +window_._smConf = { + organization: 'P9usCUBauxft8eAmUXaZ', + appId: 'default', + publicKey: 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDetfEgYD4aE1ZjmWJ6/jnPurhzI+yeRoJHWrnNtQMte3stQ4VjG3yu21FuN75E6cDpA9KtDXwcB2M/FiGUAe3G0rNotbWI8+SjZfUbW/OILFTzY0uaeEkmVGW5WyJ6weQbbr1xTCPa2OO3YIMeZljWUYHG5h21WAm/PATg8im8cQIDAQAB', + staticHost: 'static.portal101.cn', + protocol: 'https', + apiHost: 'fp-it-acc.portal101.cn', +} +useScriptTag('https://static.portal101.cn/dist/web/v3.0.0/fp.min.js') + +function getDeviceId() { + return new Promise((resolve) => { + window_.SMSdk.ready(() => { + resolve(window_.SMSdk.getDeviceId?.() || '') + }) + }) +} function onClick() { - // @ts-expect-error global - window.initSMCaptcha({ + window_.initSMCaptcha({ ...props.config, appendTo: 'sm-captcha', }, (c: any) => { captcha.value = c state.value = 'pending' - c.onSuccess((e: any) => { + c.onSuccess(async (e: any) => { if (e.pass) { state.value = 'success' - emits('next', e.rid) + emits('next', e.rid, await getDeviceId()) } else { - // Failed + console.error('Captcha verification failed') } }) }) diff --git a/ui/src/components/SiliconFlowLoginCard.vue b/ui/src/components/SiliconFlowLoginCard.vue index 9e327c6..718cb2e 100644 --- a/ui/src/components/SiliconFlowLoginCard.vue +++ b/ui/src/components/SiliconFlowLoginCard.vue @@ -9,13 +9,12 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { useSiliconFlowProvider } from '@/lib/providers/siliconflow' -import { useKeysStore, useServiceStore, useThemeStore } from '@/stores' +import { useKeysStore, useThemeStore } from '@/stores' const { t, locale } = useI18n() const themeStore = useThemeStore() const keysStore = useKeysStore() const provider = useSiliconFlowProvider() -const { fetch } = useServiceStore() const isEmailLogin = ref(false) const phoneNumber = ref('') @@ -38,24 +37,18 @@ const captchaConfig = { async function sendSMS(result: any) { if (isEmailLogin.value) { // Send email verification code - await fetch('siliconflow/email', { - body: JSON.stringify({ - email: email.value, - ...result, - }), - method: 'POST', - }) + await provider.sendEmail(JSON.stringify({ + email: email.value, + ...result, + })) } else { // Send SMS verification code - await fetch('siliconflow/sms', { - body: JSON.stringify({ - area: '+86', - phone: phoneNumber.value, - ...result, - }), - method: 'POST', - }) + await provider.sendSms(JSON.stringify({ + area: '+86', + phone: phoneNumber.value, + ...result, + })) } } @@ -66,27 +59,21 @@ async function login() { try { isLoading.value = true const res = isEmailLogin.value - ? await fetch('siliconflow/login/email', { - body: JSON.stringify({ - email: email.value, - code: smsCode.value, - agree: agreed.value, - keep: true, - area: '+86', - }), - method: 'POST', - }) - : await fetch('siliconflow/login', { - body: JSON.stringify({ - phone: phoneNumber.value, - code: smsCode.value, - shareCode: '', - agree: agreed.value, - keep: true, - area: '+86', - }), - method: 'POST', - }) + ? await provider.loginByEmail(JSON.stringify({ + email: email.value, + code: smsCode.value, + agree: agreed.value, + keep: true, + area: '+86', + })) + : await provider.login(JSON.stringify({ + phone: phoneNumber.value, + code: smsCode.value, + shareCode: '', + agree: agreed.value, + keep: true, + area: '+86', + })) if (res.ok) { await provider.refreshUser() diff --git a/ui/src/lib/providers/deepseek.ts b/ui/src/lib/providers/deepseek.ts index 867d92e..c962630 100644 --- a/ui/src/lib/providers/deepseek.ts +++ b/ui/src/lib/providers/deepseek.ts @@ -16,7 +16,7 @@ export const useDeepSeekProvider = createSharedComposable((): Provider => { mainlandIdCard: 'Mainland China ID Card', }, }) - const { fetch } = useServiceStore() + const { api } = useServiceStore() const user = ref() @@ -31,7 +31,7 @@ export const useDeepSeekProvider = createSharedComposable((): Provider => { return user.value }, async refreshUser() { - const res = await fetch('deepseek/status', { + const res = await api('deepseek/status', { method: 'GET', }) @@ -53,7 +53,7 @@ export const useDeepSeekProvider = createSharedComposable((): Provider => { Login: markRaw(DeepSeekLoginCard), async logout() { - const res = await fetch('deepseek/logout', { + const res = await api('deepseek/logout', { method: 'POST', }) @@ -65,7 +65,7 @@ export const useDeepSeekProvider = createSharedComposable((): Provider => { verification: { async check() { - const res = await fetch('deepseek/auth/info', { + const res = await api('deepseek/auth/info', { method: 'GET', }) @@ -89,7 +89,7 @@ export const useDeepSeekProvider = createSharedComposable((): Provider => { }, async submit(data) { - const res = await fetch('deepseek/auth/save', { + const res = await api('deepseek/auth/save', { body: JSON.stringify({ name: data.name.trim(), id: data.cardId.trim(), @@ -112,7 +112,7 @@ export const useDeepSeekProvider = createSharedComposable((): Provider => { // Generate a random UUID for request_id const requestId = crypto.randomUUID() - const res = await fetch('deepseek/payment/create', { + const res = await api('deepseek/payment/create', { body: JSON.stringify({ order_info: { payment_method_type: 'WECHAT', @@ -138,7 +138,7 @@ export const useDeepSeekProvider = createSharedComposable((): Provider => { }, async checkWeChatPay(options) { - const res = await fetch(`deepseek/payment/status?order=${options.orderId}`, { + const res = await api(`deepseek/payment/status?order=${options.orderId}`, { method: 'GET', }) @@ -163,7 +163,7 @@ export const useDeepSeekProvider = createSharedComposable((): Provider => { baseURL: 'https://api.deepseek.com/v1', async createKey() { - const res = await fetch('deepseek/apikey/create', { + const res = await api('deepseek/apikey/create', { body: JSON.stringify({ action: 'create', name: 'Generated by UniToken', diff --git a/ui/src/lib/providers/index.ts b/ui/src/lib/providers/index.ts index 7c006f4..31c8fa8 100644 --- a/ui/src/lib/providers/index.ts +++ b/ui/src/lib/providers/index.ts @@ -1,4 +1,6 @@ import type { Component } from 'vue' +import { createSharedComposable } from '@vueuse/core' +import { defineDbStore } from '@/stores/db' export interface ProviderUserInfo { name: string @@ -58,3 +60,25 @@ export interface Provider { readonly baseURL: string readonly createKey: () => Promise } + +const useProviderSessionsDb = defineDbStore('provider_sessions') + +export function useProviderSession(providerId: string) { + const db = useProviderSessionsDb() + + return { + get() { + return db.get(providerId) as Promise + }, + put(session: T) { + return db.put(providerId, session) + }, + delete() { + return db.delete(providerId) + }, + } +} + +export function defineProvider

(provider: () => P): () => P { + return createSharedComposable(provider) +} diff --git a/ui/src/lib/providers/openrouter.ts b/ui/src/lib/providers/openrouter.ts index fb2f77b..5a4554b 100644 --- a/ui/src/lib/providers/openrouter.ts +++ b/ui/src/lib/providers/openrouter.ts @@ -1,11 +1,11 @@ -import type { Provider, ProviderUserInfo } from './index' -import { createSharedComposable } from '@vueuse/core' +import type { ProviderUserInfo } from './index' import { markRaw, ref } from 'vue' import OpenRouterLoginCard from '@/components/OpenRouterLoginCard.vue' import { useServiceStore } from '@/stores' import { useI18n } from '../locals' +import { defineProvider, useProviderSession } from './index' -export const useOpenRouterProvider = createSharedComposable((): Provider => { +export const useOpenRouterProvider = defineProvider(() => { const { t } = useI18n({ 'zh-CN': { providerName: 'OpenRouter', @@ -14,7 +14,12 @@ export const useOpenRouterProvider = createSharedComposable((): Provider => { providerName: 'OpenRouter', }, }) - const { fetch } = useServiceStore() + const { proxy } = useServiceStore() + + const session = useProviderSession<{ + key: string + userId: string + }>('openrouter') const user = ref() @@ -29,8 +34,15 @@ export const useOpenRouterProvider = createSharedComposable((): Provider => { return user.value }, async refreshUser() { - const res = await fetch('openrouter/status', { + const s = await session.get() + if (!s) { + return + } + const res = await proxy('https://openrouter.ai/api/v1/credits', { method: 'GET', + headers: { + Authorization: `Bearer ${s.key}`, + }, }) if (res.ok) { @@ -48,25 +60,17 @@ export const useOpenRouterProvider = createSharedComposable((): Provider => { Login: markRaw(OpenRouterLoginCard), async logout() { - const res = await fetch('openrouter/logout', { - method: 'POST', - }) - - if (!res.ok) { - throw new Error('Logout failed') - } + await session.delete() user.value = null }, baseURL: 'https://openrouter.ai/api/v1', async createKey() { - const res = await fetch('openrouter/key') - - if (res.ok) { - const data = await res.json() - return data.key + const s = await session.get() + if (!s) { + throw new Error('No session') } - throw new Error('API Key creation failed') + return s.key }, } }) diff --git a/ui/src/lib/providers/siliconflow.ts b/ui/src/lib/providers/siliconflow.ts index 28ce214..9fe69db 100644 --- a/ui/src/lib/providers/siliconflow.ts +++ b/ui/src/lib/providers/siliconflow.ts @@ -1,11 +1,11 @@ -import type { Provider, ProviderUserInfo } from './index' -import { createSharedComposable } from '@vueuse/core' +import type { ProviderUserInfo } from './index' import { markRaw, ref } from 'vue' import SiliconFlowLoginCard from '@/components/SiliconFlowLoginCard.vue' import { useServiceStore } from '@/stores' import { useI18n } from '../locals' +import { defineProvider, useProviderSession } from './index' -export const useSiliconFlowProvider = createSharedComposable((): Provider => { +export const useSiliconFlowProvider = defineProvider(() => { const { t } = useI18n({ 'zh-CN': { providerName: '硅基流动', @@ -28,10 +28,29 @@ export const useSiliconFlowProvider = createSharedComposable((): Provider => { otherType: 'Other Type', }, }) - const { fetch } = useServiceStore() + const { proxy } = useServiceStore() + + const session = useProviderSession<{ + cookie: string + subjectID: string + }>('siliconflow') const user = ref() + const commonHeaders = { + 'Accept': '*/*', + 'Accept-Language': 'zh-CN,zh;q=0.9', + 'Priority': 'u=1, i', + 'Sec-CH-UA': `"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"`, + 'Sec-CH-UA-Mobile': '?0', + 'Sec-CH-UA-Platform': `"Linux"`, + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0', + 'Origin': 'https://cloud.siliconflow.cn', + } + return { id: 'siliconflow', get name() { @@ -43,41 +62,78 @@ export const useSiliconFlowProvider = createSharedComposable((): Provider => { return user.value }, async refreshUser() { - const res = await fetch('siliconflow/status', { - method: 'GET', + const s = await session.get() + if (!s) { + user.value = null + return + } + + const meRes = await proxy('https://cloud.siliconflow.cn/me', { + headers: { + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Accept-Language': 'zh-CN,zh;q=0.9', + 'Priority': 'u=0, i', + 'Referer': 'https://account.siliconflow.cn/', + 'Sec-CH-UA': `"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"`, + 'Sec-CH-UA-Mobile': '?0', + 'Sec-CH-UA-Platform': `"Linux"`, + 'Sec-Fetch-Dest': 'document', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-Site': 'same-site', + 'Sec-Fetch-User': '?1', + 'Upgrade-Insecure-Requests': '1', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0', + 'Cookie': s.cookie, + }, }) - if (res.ok) { - const data = await res.json() - if (data.code === 20000 && data.status && data.data) { - user.value = { - name: data.data.name, - verified: data.data.auth === 1, - phone: data.data.phone, - email: data.data.email, - balance: data.data.balance, + if (meRes.ok) { + const userInfoRes = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/user/info', { + headers: { + ...commonHeaders, + 'Referer': 'https://cloud.siliconflow.cn/me/account/info', + 'X-Subject-ID': s.subjectID || '', + 'Cookie': s.cookie || '', + }, + }) + + if (userInfoRes.ok) { + const data = await userInfoRes.json() + if (data.code === 20000 && data.status && data.data) { + user.value = { + name: data.data.name, + verified: data.data.auth === 1, + phone: data.data.phone, + email: data.data.email, + balance: data.data.balance, + } + return } - return } } + user.value = null }, Login: markRaw(SiliconFlowLoginCard), async logout() { - const res = await fetch('siliconflow/logout', { - method: 'POST', - }) - - if (!res.ok) { - throw new Error('Logout failed') - } + await session.delete() }, verification: { async check() { - const res = await fetch('siliconflow/auth/info', { - method: 'GET', + const s = await session.get() + if (!s) { + throw new Error('No session found') + } + const res = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/subject/auth/info', { + headers: { + ...commonHeaders, + 'Content-Type': 'application/json', + 'Referer': 'https://cloud.siliconflow.cn/me/account/authentication/personal', + 'X-Subject-ID': s.subjectID || '', + 'Cookie': s.cookie || '', + }, }) if (res.ok) { @@ -116,7 +172,19 @@ export const useSiliconFlowProvider = createSharedComposable((): Provider => { }, async submit(data) { - const res = await fetch('siliconflow/auth/save', { + const s = await session.get() + if (!s) { + throw new Error('No session found') + } + const res = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/subject/auth/save', { + method: 'POST', + headers: { + ...commonHeaders, + 'Content-Type': 'application/json', + 'Referer': 'https://cloud.siliconflow.cn/me/account/authentication/personal', + 'X-Subject-ID': s.subjectID || '', + 'Cookie': s.cookie || '', + }, body: JSON.stringify({ username: data.name.trim(), cardType: data.cardType, @@ -126,9 +194,7 @@ export const useSiliconFlowProvider = createSharedComposable((): Provider => { industry: '其他', authOperationType: 1, }), - method: 'POST', }) - if (res.ok) { const data = await res.json() if (data.code === 20000 && data.status && data.data) { @@ -143,12 +209,23 @@ export const useSiliconFlowProvider = createSharedComposable((): Provider => { payment: { async createWeChatPay(options) { - const res = await fetch('siliconflow/payment/create', { + const s = await session.get() + if (!s) { + throw new Error('No session found') + } + const res = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/pay/transactions', { + method: 'POST', body: JSON.stringify({ platform: 'wx', amount: String(options.amount), }), - method: 'POST', + headers: { + ...commonHeaders, + 'Content-Type': 'application/json', + 'Referer': 'https://cloud.siliconflow.cn/me/account/recharge', + 'X-Subject-ID': s.subjectID || '', + 'Cookie': s.cookie || '', + }, }) if (res.ok) { @@ -165,8 +242,17 @@ export const useSiliconFlowProvider = createSharedComposable((): Provider => { }, async checkWeChatPay(options) { - const res = await fetch(`siliconflow/payment/status?order=${options.orderId}`, { + const s = await session.get() + if (!s) { + throw new Error('No session found') + } + const res = await proxy(`https://cloud.siliconflow.cn/biz-server/api/v1/pay/status?order=${options.orderId}`, { method: 'GET', + headers: { + ...commonHeaders, + 'X-Subject-ID': s.subjectID || '', + 'Cookie': s.cookie || '', + }, }) if (res.ok) { @@ -187,11 +273,22 @@ export const useSiliconFlowProvider = createSharedComposable((): Provider => { baseURL: 'https://api.siliconflow.cn/v1', async createKey() { - const res = await fetch('siliconflow/apikey/create', { + const s = await session.get() + if (!s) { + throw new Error('No session found') + } + const res = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/apikey/create', { body: JSON.stringify({ description: 'Generated by UniToken', }), method: 'POST', + headers: { + ...commonHeaders, + 'Content-Type': 'application/json', + 'Referer': 'https://cloud.siliconflow.cn/me/account/ak', + 'X-Subject-ID': s.subjectID || '', + 'Cookie': s.cookie || '', + }, }) if (res.ok) { @@ -202,5 +299,84 @@ export const useSiliconFlowProvider = createSharedComposable((): Provider => { throw new Error('API Key creation failed') } }, + + async sendSms(payload: string) { + return proxy('https://account.siliconflow.cn/api/open/sms', { + method: 'POST', + headers: commonHeaders, + body: payload, + }) + }, + + async sendEmail(payload: string) { + return proxy('https://account.siliconflow.cn/api/open/email', { + method: 'POST', + headers: commonHeaders, + body: payload, + }) + }, + + async login(payload: string) { + const res = await proxy('https://account.siliconflow.cn/api/open/login/user', { + method: 'POST', + headers: commonHeaders, + body: payload, + }) + + if (res.ok) { + const setCookieHeader = res.headers.get('Set-Cookie') + if (setCookieHeader) { + const cookie = setCookieHeader.split(';').map(c => c.trim()).join('; ') + const meRes = await proxy('https://cloud.siliconflow.cn/me', { + headers: { + ...commonHeaders, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Referer': 'https://account.siliconflow.cn/', + 'Sec-Fetch-Dest': 'document', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-Site': 'same-site', + 'Sec-Fetch-User': '?1', + 'Upgrade-Insecure-Requests': '1', + 'Cookie': cookie, + }, + }) + const subjectID = meRes.headers.get('X-Subject-ID') || '' + await session.put({ cookie, subjectID }) + } + } + return res + }, + + async loginByEmail(payload: string) { + const res = await proxy('https://account.siliconflow.cn/api/open/login/email', { + method: 'POST', + headers: commonHeaders, + body: payload, + }) + + if (res.ok) { + const setCookieHeader = res.headers.get('Set-Cookie') + if (setCookieHeader) { + const cookie = setCookieHeader.split(';').map(c => c.trim()).join('; ') + const meRes = await proxy('https://cloud.siliconflow.cn/me', { + headers: { + ...commonHeaders, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Referer': 'https://account.siliconflow.cn/', + 'Sec-Fetch-Dest': 'document', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-Site': 'same-site', + 'Sec-Fetch-User': '?1', + 'Upgrade-Insecure-Requests': '1', + 'Cookie': cookie, + }, + }) + const subjectID = meRes.headers.get('X-Subject-ID') || '' + await session.put({ cookie, subjectID }) + } + } + return res + }, + } }) diff --git a/ui/src/stores/app.ts b/ui/src/stores/app.ts index 289fbc3..4b3f879 100644 --- a/ui/src/stores/app.ts +++ b/ui/src/stores/app.ts @@ -15,7 +15,7 @@ export interface App { } export const useAppStore = defineStore('app', () => { - const { fetch } = useServiceStore() + const { api: fetch } = useServiceStore() const { t } = useI18n({ 'zh-CN': { appDeleted: '应用已删除', diff --git a/ui/src/stores/auth.ts b/ui/src/stores/auth.ts index a7fda23..e86729d 100644 --- a/ui/src/stores/auth.ts +++ b/ui/src/stores/auth.ts @@ -28,7 +28,7 @@ export const useAuthStore = defineStore('auth', () => { async function login(username: string, password: string): Promise { isLoading.value = true try { - const response = await serviceStore.fetch('auth/login', { + const response = await serviceStore.api('auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -65,7 +65,7 @@ export const useAuthStore = defineStore('auth', () => { ): Promise { isLoading.value = true try { - const response = await serviceStore.fetch('auth/register', { + const response = await serviceStore.api('auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/ui/src/stores/db.ts b/ui/src/stores/db.ts new file mode 100644 index 0000000..0c9cc0e --- /dev/null +++ b/ui/src/stores/db.ts @@ -0,0 +1,39 @@ +import { defineStore } from 'pinia' +import { useServiceStore } from './service' + +export function defineDbStore(name: string) { + return defineStore(`db:${name}`, () => { + const serviceStore = useServiceStore() + return { + async get(key: string): Promise { + const resp = await serviceStore.api(`store/${name}/${key}`) + if (resp.status === 200) { + return await resp.json() as T + } + else if (resp.status === 404) { + return null + } + else { + throw new Error(`Error fetching key ${key} from store ${name}: ${resp.statusText}`) + } + }, + async put(key: string, value: T): Promise { + const resp = await serviceStore.api(`store/${name}/${key}`, { + method: 'PUT', + body: JSON.stringify(value), + }) + if (resp.status !== 200) { + throw new Error(`Error putting key ${key} to store ${name}: ${resp.statusText}`) + } + }, + async delete(key: string): Promise { + const resp = await serviceStore.api(`store/${name}/${key}`, { + method: 'DELETE', + }) + if (resp.status !== 200) { + throw new Error(`Error deleting key ${key} from store ${name}: ${resp.statusText}`) + } + }, + } + }) +} diff --git a/ui/src/stores/keys.ts b/ui/src/stores/keys.ts index 460e653..0a571f4 100644 --- a/ui/src/stores/keys.ts +++ b/ui/src/stores/keys.ts @@ -15,7 +15,7 @@ export interface APIKey { } export const useKeysStore = defineStore('keys', () => { - const { fetch } = useServiceStore() + const { api } = useServiceStore() const { t } = useI18n({ 'en-US': { addKeyFailed: 'Failed to add key', @@ -39,7 +39,7 @@ export const useKeysStore = defineStore('keys', () => { loadingError.value = null try { - const response = await fetch('keys/list') + const response = await api('keys/list') if (response.ok) { const data = await response.json() keys.value = data.data @@ -69,7 +69,7 @@ export const useKeysStore = defineStore('keys', () => { async function addKey(key: Omit & { name?: string }): Promise { try { - const response = await fetch('keys/add', { + const response = await api('keys/add', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -94,7 +94,7 @@ export const useKeysStore = defineStore('keys', () => { async function updateKey(keyId: string, key: APIKey) { try { - const response = await fetch(`keys/update/${encodeURIComponent(keyId)}`, { + const response = await api(`keys/update/${encodeURIComponent(keyId)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', @@ -119,7 +119,7 @@ export const useKeysStore = defineStore('keys', () => { async function deleteKey(keyId: string) { try { - const response = await fetch(`keys/delete/${encodeURIComponent(keyId)}`, { + const response = await api(`keys/delete/${encodeURIComponent(keyId)}`, { method: 'DELETE', }) diff --git a/ui/src/stores/presets.ts b/ui/src/stores/presets.ts index c63d58f..622a729 100644 --- a/ui/src/stores/presets.ts +++ b/ui/src/stores/presets.ts @@ -13,7 +13,7 @@ export interface AppPreset { } export const usePresetsStore = defineStore('presets', () => { - const { fetch } = useServiceStore() + const { api: fetch } = useServiceStore() const { t } = useI18n({ 'en-US': { preset: 'Preset', diff --git a/ui/src/stores/service.ts b/ui/src/stores/service.ts index 8292962..a878f3b 100644 --- a/ui/src/stores/service.ts +++ b/ui/src/stores/service.ts @@ -61,49 +61,68 @@ export const useServiceStore = defineStore('service', () => { requireFindService = true }, 5000) + async function api(path: string, options?: RequestInit) { + await initialLoad + if (!serviceUrl.value) { + throw new Error('Service not available') + } + try { + const resp = await fetch( + `${serviceUrl.value}${path}`, + token.value + ? { + ...options, + headers: { + Authorization: `Bearer ${token.value}`, + ...options?.headers, + }, + } + : options, + ) + + if (resp.status === 401) { + console.error('Unauthorized access, please check your token') + authStore.currentUser = null + token.value = null + } + + return resp + } + catch (error) { + requireFindService = true + console.error(`Error fetching ${path}:`, error) + throw error + } + } + + async function proxy(url: string, options?: { + method?: string + headers?: { [key: string]: string } + body?: string + }) { + return await fetch(`${serviceUrl.value}proxy`, { + method: options?.method || 'GET', + headers: { + Authorization: `Bearer ${token.value}`, + }, + body: JSON.stringify({ + url, + headers: options?.headers || {}, + body: options?.body || null, + }), + }) + } + return { serverConnected, serviceHost, serviceUrl, servicePort, + token, refreshService: () => { requireFindService = true }, - token, - fetch: async (path: string, options?: RequestInit) => { - await initialLoad - if (!serviceUrl.value) { - throw new Error('Service not available') - } - try { - const resp = await fetch( - `${serviceUrl.value}${path}`, - token.value - ? { - ...options, - headers: { - Authorization: `Bearer ${token.value}`, - ...options?.headers, - }, - } - : options, - ) - - if (resp.status === 401) { - if (resp.headers.get('X-Uni-Token-Error')) { - console.error('Unauthorized access, please check your token') - authStore.currentUser = null - token.value = null - } - } - - return resp - } - catch (error) { - requireFindService = true - console.error(`Error fetching ${path}:`, error) - throw error - } - }, + api, + proxy, } }) diff --git a/ui/src/views/Usage.vue b/ui/src/views/Usage.vue index 5996fe1..001cc11 100644 --- a/ui/src/views/Usage.vue +++ b/ui/src/views/Usage.vue @@ -43,7 +43,7 @@ interface UsageStats { } const { t } = useI18n() -const { fetch } = useServiceStore() +const { api: fetch } = useServiceStore() const stats = ref(null) const loading = ref(false) const error = ref(null) From bf8f7cbfafaf3bb3d5d6d487c73b0338178966fc Mon Sep 17 00:00:00 2001 From: _Kerman Date: Mon, 10 Nov 2025 15:51:59 +0800 Subject: [PATCH 2/8] fix --- service/server/proxy.go | 33 ++- service/server/store.go | 2 +- ui/src/components/SiliconFlowLoginCard.vue | 61 ++--- ui/src/lib/providers/index.ts | 5 + ui/src/lib/providers/siliconflow.ts | 280 ++++++++++----------- ui/src/stores/providers.ts | 3 +- ui/src/stores/service.ts | 24 +- 7 files changed, 208 insertions(+), 200 deletions(-) diff --git a/service/server/proxy.go b/service/server/proxy.go index ea78989..6df4de3 100644 --- a/service/server/proxy.go +++ b/service/server/proxy.go @@ -10,22 +10,24 @@ import ( func SetupProxyAPI(router gin.IRouter) { api := router.Group("/proxy").Use(RequireUserLogin()) { - api.GET("", handleProxy) api.POST("", handleProxy) - api.DELETE("", handleProxy) - api.PUT("", handleProxy) } } type ProxyRequest struct { + Method string `json:"method" binding:"required"` Url string `json:"url" binding:"required"` Headers map[string]string `json:"headers"` Body string `json:"body"` } -func handleProxy(c *gin.Context) { - println("Handle proxy") +type ProxyResponse struct { + Status int `json:"status"` + Headers map[string]string `json:"headers"` + Body string `json:"body"` +} +func handleProxy(c *gin.Context) { var req ProxyRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) @@ -33,7 +35,7 @@ func handleProxy(c *gin.Context) { } client := &http.Client{} - proxyReq, err := http.NewRequest(c.Request.Method, req.Url, bytes.NewReader([]byte(req.Body))) + proxyReq, err := http.NewRequest(req.Method, req.Url, bytes.NewReader([]byte(req.Body))) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create proxy request"}) return @@ -50,10 +52,23 @@ func handleProxy(c *gin.Context) { } defer resp.Body.Close() + var respBody bytes.Buffer + _, err = respBody.ReadFrom(resp.Body) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read proxy response body"}) + return + } + + respHeaders := make(map[string]string) for key, values := range resp.Header { - for _, value := range values { - c.Writer.Header().Add(key, value) + if len(values) > 0 { + respHeaders[key] = values[0] } } - c.Writer.WriteHeader(resp.StatusCode) + + c.JSON(http.StatusOK, ProxyResponse{ + Status: resp.StatusCode, + Headers: respHeaders, + Body: respBody.String(), + }) } diff --git a/service/server/store.go b/service/server/store.go index 5bbf86e..6e188ec 100644 --- a/service/server/store.go +++ b/service/server/store.go @@ -13,7 +13,7 @@ func SetupStoreAPI(router gin.IRouter) { { api.DELETE("/:name", handleStoreDeleteAll) api.GET("/:name/:key", handleStoreGet) - api.POST("/:name/:key", handleStorePut) + api.PUT("/:name/:key", handleStorePut) api.DELETE("/:name/:key", handleStoreDelete) } } diff --git a/ui/src/components/SiliconFlowLoginCard.vue b/ui/src/components/SiliconFlowLoginCard.vue index 718cb2e..e6f1b22 100644 --- a/ui/src/components/SiliconFlowLoginCard.vue +++ b/ui/src/components/SiliconFlowLoginCard.vue @@ -58,43 +58,36 @@ async function login() { try { isLoading.value = true - const res = isEmailLogin.value - ? await provider.loginByEmail(JSON.stringify({ - email: email.value, - code: smsCode.value, - agree: agreed.value, - keep: true, - area: '+86', - })) - : await provider.login(JSON.stringify({ - phone: phoneNumber.value, - code: smsCode.value, - shareCode: '', - agree: agreed.value, - keep: true, - area: '+86', - })) - - if (res.ok) { - await provider.refreshUser() - // Clear login form - phoneNumber.value = '' - email.value = '' - smsCode.value = '' - toast.success(t('loginSuccess')) - - // Automatically create API key after successful login - await keysStore.createAndAddKey(provider) + if (isEmailLogin.value) { + await provider.loginViaEmail({ + email: email.value, + code: smsCode.value, + agree: agreed.value, + keep: true, + area: '+86', + }) } else { - const errorData = await res.json() - if (res.status === 401) { - toast.error(errorData.message || t('invalidCode')) - } - else { - toast.error(errorData.message || t('loginFailed')) - } + await provider.loginViaSms({ + phone: phoneNumber.value, + code: smsCode.value, + shareCode: '', + agree: agreed.value, + keep: true, + area: '+86', + }) } + + await provider.refreshUser() + + // Clear login form + phoneNumber.value = '' + email.value = '' + smsCode.value = '' + toast.success(t('loginSuccess')) + + // Automatically create API key after successful login + await keysStore.createAndAddKey(provider) } catch (error) { console.error('Login error:', error) diff --git a/ui/src/lib/providers/index.ts b/ui/src/lib/providers/index.ts index 31c8fa8..8065bb1 100644 --- a/ui/src/lib/providers/index.ts +++ b/ui/src/lib/providers/index.ts @@ -22,6 +22,11 @@ export interface Provider { readonly name: string readonly homepage: string + /** + * - `undefined`: loading + * - `null`: not logged in + * - `ProviderUserInfo`: logged in + */ readonly user: undefined | null | ProviderUserInfo readonly refreshUser: () => Promise diff --git a/ui/src/lib/providers/siliconflow.ts b/ui/src/lib/providers/siliconflow.ts index 9fe69db..a81e7c0 100644 --- a/ui/src/lib/providers/siliconflow.ts +++ b/ui/src/lib/providers/siliconflow.ts @@ -1,6 +1,6 @@ import type { ProviderUserInfo } from './index' -import { markRaw, ref } from 'vue' -import SiliconFlowLoginCard from '@/components/SiliconFlowLoginCard.vue' +import { defineAsyncComponent, ref } from 'vue' +import { toast } from 'vue-sonner' import { useServiceStore } from '@/stores' import { useI18n } from '../locals' import { defineProvider, useProviderSession } from './index' @@ -16,6 +16,8 @@ export const useSiliconFlowProvider = defineProvider(() => { taiwanResidence: '台湾居民居住证', foreignerPermit: '外国人永久居留证', otherType: '其他类型用户', + invalidCode: '验证码无效,请重试', + loginFailed: '登录失败,请稍后重试', }, 'en-US': { providerName: 'SiliconFlow', @@ -26,6 +28,8 @@ export const useSiliconFlowProvider = defineProvider(() => { taiwanResidence: 'Taiwan Residence Permit', foreignerPermit: 'Foreigner Permanent Residence Permit', otherType: 'Other Type', + invalidCode: 'Invalid code, please try again', + loginFailed: 'Login failed, please try again later', }, }) const { proxy } = useServiceStore() @@ -68,54 +72,32 @@ export const useSiliconFlowProvider = defineProvider(() => { return } - const meRes = await proxy('https://cloud.siliconflow.cn/me', { + const { ok, json } = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/user/info', { headers: { - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'Accept-Language': 'zh-CN,zh;q=0.9', - 'Priority': 'u=0, i', - 'Referer': 'https://account.siliconflow.cn/', - 'Sec-CH-UA': `"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"`, - 'Sec-CH-UA-Mobile': '?0', - 'Sec-CH-UA-Platform': `"Linux"`, - 'Sec-Fetch-Dest': 'document', - 'Sec-Fetch-Mode': 'navigate', - 'Sec-Fetch-Site': 'same-site', - 'Sec-Fetch-User': '?1', - 'Upgrade-Insecure-Requests': '1', - 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0', + ...commonHeaders, + 'Referer': 'https://cloud.siliconflow.cn/me/account/info', + 'X-Subject-ID': s.subjectID, 'Cookie': s.cookie, }, }) - if (meRes.ok) { - const userInfoRes = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/user/info', { - headers: { - ...commonHeaders, - 'Referer': 'https://cloud.siliconflow.cn/me/account/info', - 'X-Subject-ID': s.subjectID || '', - 'Cookie': s.cookie || '', - }, - }) + if (!ok) { + user.value = null + return + } - if (userInfoRes.ok) { - const data = await userInfoRes.json() - if (data.code === 20000 && data.status && data.data) { - user.value = { - name: data.data.name, - verified: data.data.auth === 1, - phone: data.data.phone, - email: data.data.email, - balance: data.data.balance, - } - return - } + if (json.code === 20000 && json.status && json.data) { + user.value = { + name: json.data.name, + verified: json.data.auth === 1, + phone: json.data.phone, + email: json.data.email, + balance: json.data.balance, } } - - user.value = null }, - Login: markRaw(SiliconFlowLoginCard), + Login: defineAsyncComponent(() => import('@/components/SiliconFlowLoginCard.vue')), async logout() { await session.delete() }, @@ -126,36 +108,33 @@ export const useSiliconFlowProvider = defineProvider(() => { if (!s) { throw new Error('No session found') } - const res = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/subject/auth/info', { + const { ok, json } = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/subject/auth/info', { headers: { ...commonHeaders, - 'Content-Type': 'application/json', 'Referer': 'https://cloud.siliconflow.cn/me/account/authentication/personal', - 'X-Subject-ID': s.subjectID || '', - 'Cookie': s.cookie || '', + 'X-Subject-ID': s.subjectID, + 'Cookie': s.cookie, }, }) - if (res.ok) { - const data = await res.json() - if (data.code === 20000 && data.status && data.data?.auth) { - let name = data.data.username - if (name.length > 1) { - name = name.charAt(0) + '*'.repeat(name.length - 1) - } - - let cardId = data.data.cardId - if (cardId.length > 8) { - cardId = cardId.substring(0, 6) + '*'.repeat(cardId.length - 10) + cardId.substring(cardId.length - 4) - } - - return { - name, - cardId, - time: data.data.authTime?.seconds ? data.data.authTime.seconds * 1000 : undefined, - } + if (ok && json.code === 20000 && json.status && json.data?.auth) { + let name = json.data.username + if (name.length > 1) { + name = name.charAt(0) + '*'.repeat(name.length - 1) + } + + let cardId = json.data.cardId + if (cardId.length > 8) { + cardId = cardId.substring(0, 6) + '*'.repeat(cardId.length - 10) + cardId.substring(cardId.length - 4) + } + + return { + name, + cardId, + time: json.data.authTime?.seconds ? json.data.authTime.seconds * 1000 : undefined, } } + return null }, @@ -176,14 +155,14 @@ export const useSiliconFlowProvider = defineProvider(() => { if (!s) { throw new Error('No session found') } - const res = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/subject/auth/save', { + + const { ok, json } = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/subject/auth/save', { method: 'POST', headers: { ...commonHeaders, - 'Content-Type': 'application/json', 'Referer': 'https://cloud.siliconflow.cn/me/account/authentication/personal', - 'X-Subject-ID': s.subjectID || '', - 'Cookie': s.cookie || '', + 'X-Subject-ID': s.subjectID, + 'Cookie': s.cookie, }, body: JSON.stringify({ username: data.name.trim(), @@ -195,12 +174,9 @@ export const useSiliconFlowProvider = defineProvider(() => { authOperationType: 1, }), }) - if (res.ok) { - const data = await res.json() - if (data.code === 20000 && data.status && data.data) { - return { - qrcUrl: data.data.authUrl, - } + if (ok && json.code === 20000 && json.status && json.data) { + return { + qrcUrl: json.data.authUrl, } } return 'failed' @@ -213,7 +189,7 @@ export const useSiliconFlowProvider = defineProvider(() => { if (!s) { throw new Error('No session found') } - const res = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/pay/transactions', { + const { ok, json } = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/pay/transactions', { method: 'POST', body: JSON.stringify({ platform: 'wx', @@ -221,21 +197,17 @@ export const useSiliconFlowProvider = defineProvider(() => { }), headers: { ...commonHeaders, - 'Content-Type': 'application/json', 'Referer': 'https://cloud.siliconflow.cn/me/account/recharge', - 'X-Subject-ID': s.subjectID || '', - 'Cookie': s.cookie || '', + 'X-Subject-ID': s.subjectID, + 'Cookie': s.cookie, }, }) - if (res.ok) { - const data = await res.json() - if (data.code === 20000 && data.status && data.data) { - return { - orderId: data.data.order, - qrcUrl: data.data.codeUrl, - qrcTimeout: 120 * 1000, - } + if (ok && json.code === 20000 && json.status && json.data) { + return { + orderId: json.data.order, + qrcUrl: json.data.codeUrl, + qrcTimeout: 120 * 1000, } } throw new Error('QR code generation failed') @@ -246,25 +218,22 @@ export const useSiliconFlowProvider = defineProvider(() => { if (!s) { throw new Error('No session found') } - const res = await proxy(`https://cloud.siliconflow.cn/biz-server/api/v1/pay/status?order=${options.orderId}`, { + const { ok, json } = await proxy(`https://cloud.siliconflow.cn/biz-server/api/v1/pay/status?order=${options.orderId}`, { method: 'GET', headers: { ...commonHeaders, - 'X-Subject-ID': s.subjectID || '', - 'Cookie': s.cookie || '', + 'X-Subject-ID': s.subjectID, + 'Cookie': s.cookie, }, }) - if (res.ok) { - const data = await res.json() - if (data.code === 20000 && data.status && data.data) { - const payStatus = data.data.payStatus - if (payStatus === 1) { - return 'success' - } - else if (payStatus === 2) { - return 'wait' - } + if (ok && json.code === 20000 && json.status && json.data) { + const payStatus = json.data.payStatus + if (payStatus === 1) { + return 'success' + } + else if (payStatus === 2) { + return 'wait' } } throw new Error('Payment status check failed') @@ -277,23 +246,21 @@ export const useSiliconFlowProvider = defineProvider(() => { if (!s) { throw new Error('No session found') } - const res = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/apikey/create', { + const { ok, json } = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/apikey/create', { body: JSON.stringify({ description: 'Generated by UniToken', }), method: 'POST', headers: { ...commonHeaders, - 'Content-Type': 'application/json', 'Referer': 'https://cloud.siliconflow.cn/me/account/ak', - 'X-Subject-ID': s.subjectID || '', - 'Cookie': s.cookie || '', + 'X-Subject-ID': s.subjectID, + 'Cookie': s.cookie, }, }) - if (res.ok) { - const data = await res.json() - return data.data.secretKey + if (ok) { + return json.data.secretKey } else { throw new Error('API Key creation failed') @@ -316,67 +283,74 @@ export const useSiliconFlowProvider = defineProvider(() => { }) }, - async login(payload: string) { - const res = await proxy('https://account.siliconflow.cn/api/open/login/user', { + async loginViaSms(payload: any) { + const { ok, status, headers } = await proxy('https://account.siliconflow.cn/api/open/login/user', { method: 'POST', headers: commonHeaders, - body: payload, + body: JSON.stringify(payload), }) - if (res.ok) { - const setCookieHeader = res.headers.get('Set-Cookie') - if (setCookieHeader) { - const cookie = setCookieHeader.split(';').map(c => c.trim()).join('; ') - const meRes = await proxy('https://cloud.siliconflow.cn/me', { - headers: { - ...commonHeaders, - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'Referer': 'https://account.siliconflow.cn/', - 'Sec-Fetch-Dest': 'document', - 'Sec-Fetch-Mode': 'navigate', - 'Sec-Fetch-Site': 'same-site', - 'Sec-Fetch-User': '?1', - 'Upgrade-Insecure-Requests': '1', - 'Cookie': cookie, - }, - }) - const subjectID = meRes.headers.get('X-Subject-ID') || '' - await session.put({ cookie, subjectID }) - } + if (!ok) { + toast.error(status === 401 ? t('invalidCode') : t('loginFailed')) + throw new Error(`Login request failed with status ${status}`) } - return res + + const setCookieHeader = headers.get('Set-Cookie') + if (!setCookieHeader) { + throw new Error('No Set-Cookie header found') + } + const cookie = setCookieHeader.split(';').map(c => c.trim()).join('; ') + + const meRes = await proxy('https://cloud.siliconflow.cn/me', { + headers: { + ...commonHeaders, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Referer': 'https://account.siliconflow.cn/', + 'Sec-Fetch-Dest': 'document', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-Site': 'same-site', + 'Sec-Fetch-User': '?1', + 'Upgrade-Insecure-Requests': '1', + 'Cookie': cookie, + }, + }) + const subjectID = meRes.headers.get('X-Subject-ID')! + await session.put({ cookie, subjectID }) }, - async loginByEmail(payload: string) { - const res = await proxy('https://account.siliconflow.cn/api/open/login/email', { + async loginViaEmail(payload: any) { + const { ok, status, headers } = await proxy('https://account.siliconflow.cn/api/open/login/email', { method: 'POST', headers: commonHeaders, - body: payload, + body: JSON.stringify(payload), }) - if (res.ok) { - const setCookieHeader = res.headers.get('Set-Cookie') - if (setCookieHeader) { - const cookie = setCookieHeader.split(';').map(c => c.trim()).join('; ') - const meRes = await proxy('https://cloud.siliconflow.cn/me', { - headers: { - ...commonHeaders, - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'Referer': 'https://account.siliconflow.cn/', - 'Sec-Fetch-Dest': 'document', - 'Sec-Fetch-Mode': 'navigate', - 'Sec-Fetch-Site': 'same-site', - 'Sec-Fetch-User': '?1', - 'Upgrade-Insecure-Requests': '1', - 'Cookie': cookie, - }, - }) - const subjectID = meRes.headers.get('X-Subject-ID') || '' - await session.put({ cookie, subjectID }) - } + if (!ok) { + toast.error(status === 401 ? t('invalidCode') : t('loginFailed')) + throw new Error(`Login request failed with status ${status}`) } - return res - }, + const setCookieHeader = headers.get('Set-Cookie') + if (!setCookieHeader) { + throw new Error('No Set-Cookie header found') + } + const cookie = setCookieHeader.split(';').map(c => c.trim()).join('; ') + + const meRes = await proxy('https://cloud.siliconflow.cn/me', { + headers: { + ...commonHeaders, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Referer': 'https://account.siliconflow.cn/', + 'Sec-Fetch-Dest': 'document', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-Site': 'same-site', + 'Sec-Fetch-User': '?1', + 'Upgrade-Insecure-Requests': '1', + 'Cookie': cookie, + }, + }) + const subjectID = meRes.headers.get('X-Subject-ID')! + await session.put({ cookie, subjectID }) + }, } }) diff --git a/ui/src/stores/providers.ts b/ui/src/stores/providers.ts index d8b29d3..7157bb4 100644 --- a/ui/src/stores/providers.ts +++ b/ui/src/stores/providers.ts @@ -1,5 +1,6 @@ import type { Provider } from '@/lib/providers' import { defineStore } from 'pinia' +import { markRaw } from 'vue' import { useDeepSeekProvider } from '@/lib/providers/deepseek' import { useOpenRouterProvider } from '@/lib/providers/openrouter' import { useSiliconFlowProvider } from '@/lib/providers/siliconflow' @@ -10,7 +11,7 @@ export const useProvidersStore = defineStore('providers', () => { useDeepSeekProvider(), useOpenRouterProvider(), ] - const map = Object.fromEntries(list.map(p => [p.id, p])) as Record + const map = Object.fromEntries(list.map(p => [p.id, markRaw(p)])) as Record for (const provider of list) { provider.refreshUser() diff --git a/ui/src/stores/service.ts b/ui/src/stores/service.ts index a878f3b..08be45a 100644 --- a/ui/src/stores/service.ts +++ b/ui/src/stores/service.ts @@ -100,17 +100,37 @@ export const useServiceStore = defineStore('service', () => { headers?: { [key: string]: string } body?: string }) { - return await fetch(`${serviceUrl.value}proxy`, { - method: options?.method || 'GET', + const res = await fetch(`${serviceUrl.value}proxy`, { + method: 'POST', headers: { Authorization: `Bearer ${token.value}`, }, body: JSON.stringify({ + method: options?.method || 'GET', url, headers: options?.headers || {}, body: options?.body || null, }), }) + if (!res.ok) { + throw new Error(`Proxy request failed with status ${res.status}`) + } + const data = await res.json() as { + status: number + headers: { [key: string]: string } + body: string + } + return { + ok: data.status >= 200 && data.status < 300, + status: data.status, + headers: new Headers(data.headers), + get text() { + return data.body + }, + get json() { + return JSON.parse(data.body) + }, + } } return { From b654c23a9c4c648dd714225fff90ccc325217721 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Mon, 10 Nov 2025 16:06:52 +0800 Subject: [PATCH 3/8] fix --- ui/src/components/SiliconFlowLoginCard.vue | 12 +- ui/src/lib/providers/index.ts | 6 +- ui/src/lib/providers/siliconflow.ts | 155 +++++++++++---------- 3 files changed, 89 insertions(+), 84 deletions(-) diff --git a/ui/src/components/SiliconFlowLoginCard.vue b/ui/src/components/SiliconFlowLoginCard.vue index e6f1b22..0731d73 100644 --- a/ui/src/components/SiliconFlowLoginCard.vue +++ b/ui/src/components/SiliconFlowLoginCard.vue @@ -37,18 +37,18 @@ const captchaConfig = { async function sendSMS(result: any) { if (isEmailLogin.value) { // Send email verification code - await provider.sendEmail(JSON.stringify({ + await provider.apis.sendEmail({ email: email.value, ...result, - })) + }) } else { // Send SMS verification code - await provider.sendSms(JSON.stringify({ + await provider.apis.sendSms({ area: '+86', phone: phoneNumber.value, ...result, - })) + }) } } @@ -59,7 +59,7 @@ async function login() { try { isLoading.value = true if (isEmailLogin.value) { - await provider.loginViaEmail({ + await provider.apis.loginViaEmail({ email: email.value, code: smsCode.value, agree: agreed.value, @@ -68,7 +68,7 @@ async function login() { }) } else { - await provider.loginViaSms({ + await provider.apis.loginViaSms({ phone: phoneNumber.value, code: smsCode.value, shareCode: '', diff --git a/ui/src/lib/providers/index.ts b/ui/src/lib/providers/index.ts index 8065bb1..5a84654 100644 --- a/ui/src/lib/providers/index.ts +++ b/ui/src/lib/providers/index.ts @@ -17,7 +17,7 @@ export interface ProviderVerificationInfo { time?: number } -export interface Provider { +export interface Provider { readonly id: string readonly name: string readonly homepage: string @@ -64,6 +64,8 @@ export interface Provider { readonly baseURL: string readonly createKey: () => Promise + + readonly apis: A, } const useProviderSessionsDb = defineDbStore('provider_sessions') @@ -84,6 +86,6 @@ export function useProviderSession(providerId: string) { } } -export function defineProvider

(provider: () => P): () => P { +export function defineProvider(provider: () => Provider): () => Provider { return createSharedComposable(provider) } diff --git a/ui/src/lib/providers/siliconflow.ts b/ui/src/lib/providers/siliconflow.ts index a81e7c0..e340d60 100644 --- a/ui/src/lib/providers/siliconflow.ts +++ b/ui/src/lib/providers/siliconflow.ts @@ -53,6 +53,7 @@ export const useSiliconFlowProvider = defineProvider(() => { 'Sec-Fetch-Site': 'same-origin', 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0', 'Origin': 'https://cloud.siliconflow.cn', + 'Content-Type': 'application/json', } return { @@ -197,7 +198,7 @@ export const useSiliconFlowProvider = defineProvider(() => { }), headers: { ...commonHeaders, - 'Referer': 'https://cloud.siliconflow.cn/me/account/recharge', + 'Referer': 'https://cloud.siliconflow.cn/me/expensebill', 'X-Subject-ID': s.subjectID, 'Cookie': s.cookie, }, @@ -267,90 +268,92 @@ export const useSiliconFlowProvider = defineProvider(() => { } }, - async sendSms(payload: string) { - return proxy('https://account.siliconflow.cn/api/open/sms', { - method: 'POST', - headers: commonHeaders, - body: payload, - }) - }, + apis: { + async sendSms(payload: string) { + return proxy('https://account.siliconflow.cn/api/open/sms', { + method: 'POST', + headers: commonHeaders, + body: JSON.stringify(payload), + }) + }, - async sendEmail(payload: string) { - return proxy('https://account.siliconflow.cn/api/open/email', { - method: 'POST', - headers: commonHeaders, - body: payload, - }) - }, + async sendEmail(payload: string) { + return proxy('https://account.siliconflow.cn/api/open/email', { + method: 'POST', + headers: commonHeaders, + body: JSON.stringify(payload), + }) + }, - async loginViaSms(payload: any) { - const { ok, status, headers } = await proxy('https://account.siliconflow.cn/api/open/login/user', { - method: 'POST', - headers: commonHeaders, - body: JSON.stringify(payload), - }) + async loginViaSms(payload: any) { + const { ok, status, headers } = await proxy('https://account.siliconflow.cn/api/open/login/user', { + method: 'POST', + headers: commonHeaders, + body: JSON.stringify(payload), + }) - if (!ok) { - toast.error(status === 401 ? t('invalidCode') : t('loginFailed')) - throw new Error(`Login request failed with status ${status}`) - } + if (!ok) { + toast.error(status === 401 ? t('invalidCode') : t('loginFailed')) + throw new Error(`Login request failed with status ${status}`) + } - const setCookieHeader = headers.get('Set-Cookie') - if (!setCookieHeader) { - throw new Error('No Set-Cookie header found') - } - const cookie = setCookieHeader.split(';').map(c => c.trim()).join('; ') + const setCookieHeader = headers.get('Set-Cookie') + if (!setCookieHeader) { + throw new Error('No Set-Cookie header found') + } + const cookie = setCookieHeader.split(';').map(c => c.trim()).join('; ') - const meRes = await proxy('https://cloud.siliconflow.cn/me', { - headers: { - ...commonHeaders, - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'Referer': 'https://account.siliconflow.cn/', - 'Sec-Fetch-Dest': 'document', - 'Sec-Fetch-Mode': 'navigate', - 'Sec-Fetch-Site': 'same-site', - 'Sec-Fetch-User': '?1', - 'Upgrade-Insecure-Requests': '1', - 'Cookie': cookie, - }, - }) - const subjectID = meRes.headers.get('X-Subject-ID')! - await session.put({ cookie, subjectID }) - }, + const meRes = await proxy('https://cloud.siliconflow.cn/me', { + headers: { + ...commonHeaders, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Referer': 'https://account.siliconflow.cn/', + 'Sec-Fetch-Dest': 'document', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-Site': 'same-site', + 'Sec-Fetch-User': '?1', + 'Upgrade-Insecure-Requests': '1', + 'Cookie': cookie, + }, + }) + const subjectID = meRes.headers.get('X-Subject-ID')! + await session.put({ cookie, subjectID }) + }, - async loginViaEmail(payload: any) { - const { ok, status, headers } = await proxy('https://account.siliconflow.cn/api/open/login/email', { - method: 'POST', - headers: commonHeaders, - body: JSON.stringify(payload), - }) + async loginViaEmail(payload: any) { + const { ok, status, headers } = await proxy('https://account.siliconflow.cn/api/open/login/email', { + method: 'POST', + headers: commonHeaders, + body: JSON.stringify(payload), + }) - if (!ok) { - toast.error(status === 401 ? t('invalidCode') : t('loginFailed')) - throw new Error(`Login request failed with status ${status}`) - } + if (!ok) { + toast.error(status === 401 ? t('invalidCode') : t('loginFailed')) + throw new Error(`Login request failed with status ${status}`) + } - const setCookieHeader = headers.get('Set-Cookie') - if (!setCookieHeader) { - throw new Error('No Set-Cookie header found') - } - const cookie = setCookieHeader.split(';').map(c => c.trim()).join('; ') + const setCookieHeader = headers.get('Set-Cookie') + if (!setCookieHeader) { + throw new Error('No Set-Cookie header found') + } + const cookie = setCookieHeader.split(';').map(c => c.trim()).join('; ') - const meRes = await proxy('https://cloud.siliconflow.cn/me', { - headers: { - ...commonHeaders, - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'Referer': 'https://account.siliconflow.cn/', - 'Sec-Fetch-Dest': 'document', - 'Sec-Fetch-Mode': 'navigate', - 'Sec-Fetch-Site': 'same-site', - 'Sec-Fetch-User': '?1', - 'Upgrade-Insecure-Requests': '1', - 'Cookie': cookie, - }, - }) - const subjectID = meRes.headers.get('X-Subject-ID')! - await session.put({ cookie, subjectID }) + const meRes = await proxy('https://cloud.siliconflow.cn/me', { + headers: { + ...commonHeaders, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Referer': 'https://account.siliconflow.cn/', + 'Sec-Fetch-Dest': 'document', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-Site': 'same-site', + 'Sec-Fetch-User': '?1', + 'Upgrade-Insecure-Requests': '1', + 'Cookie': cookie, + }, + }) + const subjectID = meRes.headers.get('X-Subject-ID')! + await session.put({ cookie, subjectID }) + }, }, } }) From 82bf67cd48b36d00e960353aa00b61499bc776c1 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Mon, 10 Nov 2025 16:08:44 +0800 Subject: [PATCH 4/8] fix --- ui/src/components/SiliconFlowLoginCard.vue | 8 ++++---- ui/src/lib/providers/siliconflow.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/src/components/SiliconFlowLoginCard.vue b/ui/src/components/SiliconFlowLoginCard.vue index 0731d73..0c24be3 100644 --- a/ui/src/components/SiliconFlowLoginCard.vue +++ b/ui/src/components/SiliconFlowLoginCard.vue @@ -34,12 +34,12 @@ const captchaConfig = { protocol: 'https://', } -async function sendSMS(result: any) { +async function sendCode(captchaResult: any) { if (isEmailLogin.value) { // Send email verification code await provider.apis.sendEmail({ email: email.value, - ...result, + ...captchaResult, }) } else { @@ -47,7 +47,7 @@ async function sendSMS(result: any) { await provider.apis.sendSms({ area: '+86', phone: phoneNumber.value, - ...result, + ...captchaResult, }) } } @@ -162,7 +162,7 @@ function wxLogin() { :enabled="isEmailLogin ? email.length > 0 : phoneNumber.length > 0" :config="captchaConfig" class="h-10 px-4 bg-muted/50 rounded-r-md border-0 text-xs text-primary hover:bg-muted/70 transition-colors disabled:opacity-50" - @next="sendSMS" + @next="sendCode" /> diff --git a/ui/src/lib/providers/siliconflow.ts b/ui/src/lib/providers/siliconflow.ts index e340d60..e750030 100644 --- a/ui/src/lib/providers/siliconflow.ts +++ b/ui/src/lib/providers/siliconflow.ts @@ -269,7 +269,7 @@ export const useSiliconFlowProvider = defineProvider(() => { }, apis: { - async sendSms(payload: string) { + async sendSms(payload: unknown) { return proxy('https://account.siliconflow.cn/api/open/sms', { method: 'POST', headers: commonHeaders, @@ -277,7 +277,7 @@ export const useSiliconFlowProvider = defineProvider(() => { }) }, - async sendEmail(payload: string) { + async sendEmail(payload: unknown) { return proxy('https://account.siliconflow.cn/api/open/email', { method: 'POST', headers: commonHeaders, @@ -285,7 +285,7 @@ export const useSiliconFlowProvider = defineProvider(() => { }) }, - async loginViaSms(payload: any) { + async loginViaSms(payload: unknown) { const { ok, status, headers } = await proxy('https://account.siliconflow.cn/api/open/login/user', { method: 'POST', headers: commonHeaders, @@ -320,7 +320,7 @@ export const useSiliconFlowProvider = defineProvider(() => { await session.put({ cookie, subjectID }) }, - async loginViaEmail(payload: any) { + async loginViaEmail(payload: unknown) { const { ok, status, headers } = await proxy('https://account.siliconflow.cn/api/open/login/email', { method: 'POST', headers: commonHeaders, From cd8c20bedcf0f77288dfaf7a15670a117af36189 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Mon, 10 Nov 2025 16:15:41 +0800 Subject: [PATCH 5/8] refactor --- ui/src/lib/providers/deepseek.ts | 17 ++-- ui/src/lib/providers/openrouter.ts | 7 +- ui/src/lib/providers/siliconflow.ts | 119 ++++++++++------------------ 3 files changed, 54 insertions(+), 89 deletions(-) diff --git a/ui/src/lib/providers/deepseek.ts b/ui/src/lib/providers/deepseek.ts index c962630..234962b 100644 --- a/ui/src/lib/providers/deepseek.ts +++ b/ui/src/lib/providers/deepseek.ts @@ -1,11 +1,10 @@ -import type { Provider, ProviderUserInfo } from './index' -import { createSharedComposable } from '@vueuse/core' -import { markRaw, ref } from 'vue' -import DeepSeekLoginCard from '@/components/DeepSeekLoginCard.vue' +import type { ProviderUserInfo } from './index' +import { defineAsyncComponent, markRaw, ref } from 'vue' import { useServiceStore } from '@/stores' import { useI18n } from '../locals' +import { defineProvider } from './index' -export const useDeepSeekProvider = createSharedComposable((): Provider => { +export const useDeepSeekProvider = defineProvider(() => { const { t } = useI18n({ 'zh-CN': { providerName: 'DeepSeek', @@ -31,7 +30,7 @@ export const useDeepSeekProvider = createSharedComposable((): Provider => { return user.value }, async refreshUser() { - const res = await api('deepseek/status', { + const res = await api('https://platform.deepseek.com/auth-api/v0/users/current', { method: 'GET', }) @@ -51,7 +50,7 @@ export const useDeepSeekProvider = createSharedComposable((): Provider => { user.value = null }, - Login: markRaw(DeepSeekLoginCard), + Login: defineAsyncComponent(() => import('@/components/DeepSeekLoginCard.vue')), async logout() { const res = await api('deepseek/logout', { method: 'POST', @@ -181,5 +180,9 @@ export const useDeepSeekProvider = createSharedComposable((): Provider => { } throw new Error('API Key creation failed') }, + + apis: { + + }, } }) diff --git a/ui/src/lib/providers/openrouter.ts b/ui/src/lib/providers/openrouter.ts index 5a4554b..1558919 100644 --- a/ui/src/lib/providers/openrouter.ts +++ b/ui/src/lib/providers/openrouter.ts @@ -1,6 +1,5 @@ import type { ProviderUserInfo } from './index' -import { markRaw, ref } from 'vue' -import OpenRouterLoginCard from '@/components/OpenRouterLoginCard.vue' +import { defineAsyncComponent, ref } from 'vue' import { useServiceStore } from '@/stores' import { useI18n } from '../locals' import { defineProvider, useProviderSession } from './index' @@ -58,7 +57,7 @@ export const useOpenRouterProvider = defineProvider(() => { user.value = null }, - Login: markRaw(OpenRouterLoginCard), + Login: defineAsyncComponent(() => import('@/components/OpenRouterLoginCard.vue')), async logout() { await session.delete() user.value = null @@ -72,5 +71,7 @@ export const useOpenRouterProvider = defineProvider(() => { } return s.key }, + + apis: {}, } }) diff --git a/ui/src/lib/providers/siliconflow.ts b/ui/src/lib/providers/siliconflow.ts index e750030..afbcc79 100644 --- a/ui/src/lib/providers/siliconflow.ts +++ b/ui/src/lib/providers/siliconflow.ts @@ -41,19 +41,32 @@ export const useSiliconFlowProvider = defineProvider(() => { const user = ref() - const commonHeaders = { - 'Accept': '*/*', - 'Accept-Language': 'zh-CN,zh;q=0.9', - 'Priority': 'u=1, i', - 'Sec-CH-UA': `"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"`, - 'Sec-CH-UA-Mobile': '?0', - 'Sec-CH-UA-Platform': `"Linux"`, - 'Sec-Fetch-Dest': 'empty', - 'Sec-Fetch-Mode': 'cors', - 'Sec-Fetch-Site': 'same-origin', - 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0', - 'Origin': 'https://cloud.siliconflow.cn', - 'Content-Type': 'application/json', + async function makeHeaders(requireSession: boolean, extra?: Record) { + const s = requireSession ? null : await session.get() + if (requireSession && !s) { + throw new Error('No session found') + } + return { + 'Accept': '*/*', + 'Accept-Language': 'zh-CN,zh;q=0.9', + 'Priority': 'u=1, i', + 'Sec-CH-UA': `"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"`, + 'Sec-CH-UA-Mobile': '?0', + 'Sec-CH-UA-Platform': `"Linux"`, + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0', + 'Origin': 'https://cloud.siliconflow.cn', + 'Content-Type': 'application/json', + ...extra, + ...(s + ? { + 'X-Subject-ID': s.subjectID, + 'Cookie': s.cookie, + } + : {}), + } } return { @@ -74,12 +87,7 @@ export const useSiliconFlowProvider = defineProvider(() => { } const { ok, json } = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/user/info', { - headers: { - ...commonHeaders, - 'Referer': 'https://cloud.siliconflow.cn/me/account/info', - 'X-Subject-ID': s.subjectID, - 'Cookie': s.cookie, - }, + headers: await makeHeaders(true), }) if (!ok) { @@ -105,17 +113,8 @@ export const useSiliconFlowProvider = defineProvider(() => { verification: { async check() { - const s = await session.get() - if (!s) { - throw new Error('No session found') - } const { ok, json } = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/subject/auth/info', { - headers: { - ...commonHeaders, - 'Referer': 'https://cloud.siliconflow.cn/me/account/authentication/personal', - 'X-Subject-ID': s.subjectID, - 'Cookie': s.cookie, - }, + headers: await makeHeaders(true), }) if (ok && json.code === 20000 && json.status && json.data?.auth) { @@ -152,19 +151,9 @@ export const useSiliconFlowProvider = defineProvider(() => { }, async submit(data) { - const s = await session.get() - if (!s) { - throw new Error('No session found') - } - const { ok, json } = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/subject/auth/save', { method: 'POST', - headers: { - ...commonHeaders, - 'Referer': 'https://cloud.siliconflow.cn/me/account/authentication/personal', - 'X-Subject-ID': s.subjectID, - 'Cookie': s.cookie, - }, + headers: await makeHeaders(true), body: JSON.stringify({ username: data.name.trim(), cardType: data.cardType, @@ -186,22 +175,13 @@ export const useSiliconFlowProvider = defineProvider(() => { payment: { async createWeChatPay(options) { - const s = await session.get() - if (!s) { - throw new Error('No session found') - } const { ok, json } = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/pay/transactions', { method: 'POST', body: JSON.stringify({ platform: 'wx', amount: String(options.amount), }), - headers: { - ...commonHeaders, - 'Referer': 'https://cloud.siliconflow.cn/me/expensebill', - 'X-Subject-ID': s.subjectID, - 'Cookie': s.cookie, - }, + headers: await makeHeaders(true), }) if (ok && json.code === 20000 && json.status && json.data) { @@ -215,17 +195,9 @@ export const useSiliconFlowProvider = defineProvider(() => { }, async checkWeChatPay(options) { - const s = await session.get() - if (!s) { - throw new Error('No session found') - } const { ok, json } = await proxy(`https://cloud.siliconflow.cn/biz-server/api/v1/pay/status?order=${options.orderId}`, { method: 'GET', - headers: { - ...commonHeaders, - 'X-Subject-ID': s.subjectID, - 'Cookie': s.cookie, - }, + headers: await makeHeaders(true), }) if (ok && json.code === 20000 && json.status && json.data) { @@ -243,21 +215,12 @@ export const useSiliconFlowProvider = defineProvider(() => { baseURL: 'https://api.siliconflow.cn/v1', async createKey() { - const s = await session.get() - if (!s) { - throw new Error('No session found') - } const { ok, json } = await proxy('https://cloud.siliconflow.cn/biz-server/api/v1/apikey/create', { body: JSON.stringify({ description: 'Generated by UniToken', }), method: 'POST', - headers: { - ...commonHeaders, - 'Referer': 'https://cloud.siliconflow.cn/me/account/ak', - 'X-Subject-ID': s.subjectID, - 'Cookie': s.cookie, - }, + headers: await makeHeaders(true), }) if (ok) { @@ -272,7 +235,7 @@ export const useSiliconFlowProvider = defineProvider(() => { async sendSms(payload: unknown) { return proxy('https://account.siliconflow.cn/api/open/sms', { method: 'POST', - headers: commonHeaders, + headers: await makeHeaders(false), body: JSON.stringify(payload), }) }, @@ -280,7 +243,7 @@ export const useSiliconFlowProvider = defineProvider(() => { async sendEmail(payload: unknown) { return proxy('https://account.siliconflow.cn/api/open/email', { method: 'POST', - headers: commonHeaders, + headers: await makeHeaders(false), body: JSON.stringify(payload), }) }, @@ -288,7 +251,7 @@ export const useSiliconFlowProvider = defineProvider(() => { async loginViaSms(payload: unknown) { const { ok, status, headers } = await proxy('https://account.siliconflow.cn/api/open/login/user', { method: 'POST', - headers: commonHeaders, + headers: await makeHeaders(false), body: JSON.stringify(payload), }) @@ -304,8 +267,7 @@ export const useSiliconFlowProvider = defineProvider(() => { const cookie = setCookieHeader.split(';').map(c => c.trim()).join('; ') const meRes = await proxy('https://cloud.siliconflow.cn/me', { - headers: { - ...commonHeaders, + headers: await makeHeaders(false, { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Referer': 'https://account.siliconflow.cn/', 'Sec-Fetch-Dest': 'document', @@ -314,7 +276,7 @@ export const useSiliconFlowProvider = defineProvider(() => { 'Sec-Fetch-User': '?1', 'Upgrade-Insecure-Requests': '1', 'Cookie': cookie, - }, + }), }) const subjectID = meRes.headers.get('X-Subject-ID')! await session.put({ cookie, subjectID }) @@ -323,7 +285,7 @@ export const useSiliconFlowProvider = defineProvider(() => { async loginViaEmail(payload: unknown) { const { ok, status, headers } = await proxy('https://account.siliconflow.cn/api/open/login/email', { method: 'POST', - headers: commonHeaders, + headers: await makeHeaders(false), body: JSON.stringify(payload), }) @@ -339,8 +301,7 @@ export const useSiliconFlowProvider = defineProvider(() => { const cookie = setCookieHeader.split(';').map(c => c.trim()).join('; ') const meRes = await proxy('https://cloud.siliconflow.cn/me', { - headers: { - ...commonHeaders, + headers: await makeHeaders(false, { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Referer': 'https://account.siliconflow.cn/', 'Sec-Fetch-Dest': 'document', @@ -349,7 +310,7 @@ export const useSiliconFlowProvider = defineProvider(() => { 'Sec-Fetch-User': '?1', 'Upgrade-Insecure-Requests': '1', 'Cookie': cookie, - }, + }), }) const subjectID = meRes.headers.get('X-Subject-ID')! await session.put({ cookie, subjectID }) From d8a6534c19077114f76d79ef05af892f17b7038b Mon Sep 17 00:00:00 2001 From: _Kerman Date: Tue, 11 Nov 2025 16:46:25 +0800 Subject: [PATCH 6/8] feat: deepseek ! --- service/server/proxy.go | 47 +++- service/server/server.go | 14 +- ui/src/components/DeepSeekLoginCard.vue | 72 ++--- ui/src/components/ProviderRealNameDialog.vue | 2 +- ui/src/components/ShumeiCaptcha.vue | 39 +-- ui/src/lib/providers/deepseek.ts | 265 ++++++++++++++----- ui/src/lib/providers/siliconflow.ts | 2 +- ui/src/stores/service.ts | 5 + 8 files changed, 299 insertions(+), 147 deletions(-) diff --git a/service/server/proxy.go b/service/server/proxy.go index 6df4de3..29ac60b 100644 --- a/service/server/proxy.go +++ b/service/server/proxy.go @@ -2,16 +2,15 @@ package server import ( "bytes" + "encoding/base64" "net/http" "github.com/gin-gonic/gin" ) func SetupProxyAPI(router gin.IRouter) { - api := router.Group("/proxy").Use(RequireUserLogin()) - { - api.POST("", handleProxy) - } + router.POST("/proxy", handleProxy, RequireUserLogin()) + router.GET("/proxy/:base/*paths", handleSimpleProxy) } type ProxyRequest struct { @@ -37,7 +36,7 @@ func handleProxy(c *gin.Context) { client := &http.Client{} proxyReq, err := http.NewRequest(req.Method, req.Url, bytes.NewReader([]byte(req.Body))) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create proxy request"}) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create proxy request: " + err.Error()}) return } @@ -47,7 +46,7 @@ func handleProxy(c *gin.Context) { resp, err := client.Do(proxyReq) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to perform proxy request"}) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to perform proxy request: " + err.Error()}) return } defer resp.Body.Close() @@ -55,7 +54,7 @@ func handleProxy(c *gin.Context) { var respBody bytes.Buffer _, err = respBody.ReadFrom(resp.Body) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read proxy response body"}) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read proxy response body: " + err.Error()}) return } @@ -72,3 +71,37 @@ func handleProxy(c *gin.Context) { Body: respBody.String(), }) } + +func handleSimpleProxy(c *gin.Context) { + base, err := base64.StdEncoding.DecodeString(c.Param("base")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid base64 URL"}) + return + } + + paths := c.Param("paths") + targetUrl := string(base) + paths + + client := &http.Client{} + proxyReq, err := http.NewRequest(c.Request.Method, targetUrl, c.Request.Body) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create proxy request: " + err.Error()}) + return + } + + resp, err := client.Do(proxyReq) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to perform proxy request: " + err.Error()}) + return + } + defer resp.Body.Close() + + var respBody bytes.Buffer + _, err = respBody.ReadFrom(resp.Body) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read proxy response body: " + err.Error()}) + return + } + + c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), respBody.Bytes()) +} diff --git a/service/server/server.go b/service/server/server.go index 52425b9..cab8dfe 100644 --- a/service/server/server.go +++ b/service/server/server.go @@ -19,10 +19,16 @@ func SetupAPIServer() (int, error) { router := gin.Default() router.Use(cors.New(cors.Config{ - AllowOrigins: []string{"http://localhost:*", "https://uni-token.app"}, - AllowWildcard: true, - AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowHeaders: []string{"*"}, + AllowOrigins: []string{"http://localhost:*", "https://uni-token.app"}, + AllowWildcard: true, + AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowHeaders: []string{ + "Origin", + "Content-Length", + "Content-Type", + "Accept", + "Authorization", + }, ExposeHeaders: []string{"*"}, AllowCredentials: true, AllowPrivateNetwork: true, diff --git a/ui/src/components/DeepSeekLoginCard.vue b/ui/src/components/DeepSeekLoginCard.vue index 783e9c7..749b06e 100644 --- a/ui/src/components/DeepSeekLoginCard.vue +++ b/ui/src/components/DeepSeekLoginCard.vue @@ -6,13 +6,12 @@ import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { useDeepSeekProvider } from '@/lib/providers/deepseek' -import { useKeysStore, useServiceStore } from '@/stores' -import ShumeiCaptcha from './ShumeiCaptcha.vue' +import { useKeysStore } from '@/stores' +import ShumeiCaptcha, { deviceId } from './ShumeiCaptcha.vue' const { t, locale } = useI18n() const keysStore = useKeysStore() const provider = useDeepSeekProvider() -const { api: fetch } = useServiceStore() const captchaConfig = { organization: 'P9usCUBauxft8eAmUXaZ', @@ -50,7 +49,7 @@ function startCountdown() { }, 1000) } -async function sendSMS(rid: string, device_id: string) { +async function sendSMS(rid: string) { if (!canSendCode.value) return @@ -58,27 +57,19 @@ async function sendSMS(rid: string, device_id: string) { isSendingCode.value = true // Send SMS with captcha verification result - const res = await fetch('deepseek/sms', { - body: JSON.stringify({ - locale: locale.value === 'zh-CN' ? 'zh_CN' : 'en_US', - turnstile_token: '', - shumei_verification: { region: 'CN', rid }, - device_id, - scenario: 'login', - mobile_number: phoneNumber.value, - }), - method: 'POST', + const json = await provider.apis.sendSMS({ + locale: locale.value === 'zh-CN' ? 'zh_CN' : 'en_US', + turnstile_token: '', + shumei_verification: { region: 'CN', rid }, + device_id: await deviceId, + scenario: 'login', + mobile_number: phoneNumber.value, }) - if (res.ok) { - const { data } = await res.json() - if (data.code === 0) { - toast.success(t('smsSent')) - startCountdown() - } - else { - toast.error(t('smsFailure')) - } + const { data } = json + if (data.code === 0) { + toast.success(t('smsSent')) + startCountdown() } else { toast.error(t('smsFailure')) @@ -100,29 +91,24 @@ async function login() { try { isLoading.value = true - const res = await fetch('deepseek/login', { - body: JSON.stringify({ - phone: phoneNumber.value, - code: smsCode.value, - area_code: '+86', - }), - method: 'POST', + await provider.apis.loginWithSMS({ + region: 'CN', + locale: 'zh_CN', + mobile_number: phoneNumber.value, + area_code: '+86', + sms_verification_code: smsCode.value, + device_id: await deviceId, + os: 'web', }) - if (res.ok) { - await provider.refreshUser() - // Clear login form - phoneNumber.value = '' - smsCode.value = '' - toast.success(t('loginSuccess')) + await provider.refreshUser() + // Clear login form + phoneNumber.value = '' + smsCode.value = '' + toast.success(t('loginSuccess')) - // Automatically create API key after successful login - await keysStore.createAndAddKey(provider) - } - else { - const errorData = await res.json() - toast.error(errorData.message || t('loginFailure')) - } + // Automatically create API key after successful login + await keysStore.createAndAddKey(provider) } catch (error) { console.error('Login error:', error) diff --git a/ui/src/components/ProviderRealNameDialog.vue b/ui/src/components/ProviderRealNameDialog.vue index 939d9cb..ddb2691 100644 --- a/ui/src/components/ProviderRealNameDialog.vue +++ b/ui/src/components/ProviderRealNameDialog.vue @@ -304,7 +304,7 @@ zh-CN: description: 应硅基流动要求,充值前需要先完成实名认证 authStatusDescription: 您的实名认证状态 verified: 已认证 - realName: 真实姓名 + realName: 姓名 realNamePlaceholder: 请输入您的真实姓名 cardType: 证件类型 idCard: 身份证 diff --git a/ui/src/components/ShumeiCaptcha.vue b/ui/src/components/ShumeiCaptcha.vue index 5571984..eaf60f8 100644 --- a/ui/src/components/ShumeiCaptcha.vue +++ b/ui/src/components/ShumeiCaptcha.vue @@ -1,22 +1,11 @@ - + +