@@ -20,6 +20,7 @@ import { useChatStore } from '../../stores/chatStore'
2020import { useHahaOAuthStore } from '../../stores/hahaOAuthStore'
2121import { useHahaOpenAIOAuthStore } from '../../stores/hahaOpenAIOAuthStore'
2222import { useHahaGrokOAuthStore } from '../../stores/hahaGrokOAuthStore'
23+ import { useHahaDeepSeekOAuthStore } from '../../stores/hahaDeepSeekOAuthStore'
2324import { useProviderStore } from '../../stores/providerStore'
2425import { useSessionRuntimeStore } from '../../stores/sessionRuntimeStore'
2526import { useSettingsStore } from '../../stores/settingsStore'
@@ -52,6 +53,7 @@ afterEach(() => {
5253 useHahaOAuthStore . setState ( useHahaOAuthStore . getInitialState ( ) , true )
5354 useHahaOpenAIOAuthStore . setState ( useHahaOpenAIOAuthStore . getInitialState ( ) , true )
5455 useHahaGrokOAuthStore . setState ( useHahaGrokOAuthStore . getInitialState ( ) , true )
56+ useHahaDeepSeekOAuthStore . setState ( useHahaDeepSeekOAuthStore . getInitialState ( ) , true )
5557 useTabStore . setState ( useTabStore . getInitialState ( ) , true )
5658 useUIStore . setState ( useUIStore . getInitialState ( ) , true )
5759} )
@@ -60,6 +62,7 @@ beforeEach(() => {
6062 useHahaOAuthStore . setState ( { fetchStatus : async ( ) => { } } )
6163 useHahaOpenAIOAuthStore . setState ( { fetchStatus : async ( ) => { } } )
6264 useHahaGrokOAuthStore . setState ( { fetchStatus : async ( ) => { } } )
65+ useHahaDeepSeekOAuthStore . setState ( { fetchStatus : async ( ) => { } } )
6366} )
6467
6568describe ( 'ModelSelector' , ( ) => {
@@ -356,9 +359,13 @@ describe('ModelSelector', () => {
356359 const fetchGrokStatus = vi . fn ( async ( ) => {
357360 useHahaGrokOAuthStore . setState ( { status : { loggedIn : false } } )
358361 } )
362+ const fetchDeepSeekStatus = vi . fn ( async ( ) => {
363+ useHahaDeepSeekOAuthStore . setState ( { status : { loggedIn : false } } )
364+ } )
359365 useHahaOAuthStore . setState ( { fetchStatus : fetchClaudeStatus } )
360366 useHahaOpenAIOAuthStore . setState ( { fetchStatus : fetchOpenAIStatus } )
361367 useHahaGrokOAuthStore . setState ( { fetchStatus : fetchGrokStatus } )
368+ useHahaDeepSeekOAuthStore . setState ( { fetchStatus : fetchDeepSeekStatus } )
362369 useSettingsStore . setState ( {
363370 locale : 'en' ,
364371 availableModels : MODELS ,
@@ -391,6 +398,7 @@ describe('ModelSelector', () => {
391398 expect ( fetchClaudeStatus ) . toHaveBeenCalledTimes ( 1 )
392399 expect ( fetchOpenAIStatus ) . toHaveBeenCalledTimes ( 1 )
393400 expect ( fetchGrokStatus ) . toHaveBeenCalledTimes ( 1 )
401+ expect ( fetchDeepSeekStatus ) . toHaveBeenCalledTimes ( 1 )
394402 expect ( useTabStore . getState ( ) . activeTabId ) . toBe ( SETTINGS_TAB_ID )
395403 expect ( screen . queryByTestId ( 'model-selector-dropdown' ) ) . not . toBeInTheDocument ( )
396404 } )
@@ -399,9 +407,11 @@ describe('ModelSelector', () => {
399407 const fetchClaudeStatus = vi . fn ( async ( ) => { } )
400408 const fetchOpenAIStatus = vi . fn ( async ( ) => { } )
401409 const fetchGrokStatus = vi . fn ( async ( ) => { } )
410+ const fetchDeepSeekStatus = vi . fn ( async ( ) => { } )
402411 useHahaOAuthStore . setState ( { status : { loggedIn : false } , fetchStatus : fetchClaudeStatus } )
403412 useHahaOpenAIOAuthStore . setState ( { status : { loggedIn : false } , fetchStatus : fetchOpenAIStatus } )
404413 useHahaGrokOAuthStore . setState ( { status : { loggedIn : false } , fetchStatus : fetchGrokStatus } )
414+ useHahaDeepSeekOAuthStore . setState ( { status : { loggedIn : false } , fetchStatus : fetchDeepSeekStatus } )
405415 useSettingsStore . setState ( {
406416 locale : 'en' ,
407417 currentModel : {
@@ -424,6 +434,7 @@ describe('ModelSelector', () => {
424434 expect ( fetchClaudeStatus ) . not . toHaveBeenCalled ( )
425435 expect ( fetchOpenAIStatus ) . not . toHaveBeenCalled ( )
426436 expect ( fetchGrokStatus ) . not . toHaveBeenCalled ( )
437+ expect ( fetchDeepSeekStatus ) . not . toHaveBeenCalled ( )
427438 expect ( useUIStore . getState ( ) . pendingSettingsTab ) . toBe ( 'providers' )
428439 expect ( useTabStore . getState ( ) . activeTabId ) . toBe ( SETTINGS_TAB_ID )
429440 } )
@@ -1373,6 +1384,147 @@ describe('ModelSelector', () => {
13731384 expect ( screen . queryByRole ( 'button' , { name : / E f f o r t : / i } ) ) . not . toBeInTheDocument ( )
13741385 } )
13751386
1387+ describe ( 'DeepSeek Account' , ( ) => {
1388+ const savedProvider = {
1389+ id : 'provider-a' ,
1390+ presetId : 'custom' ,
1391+ name : 'Provider A' ,
1392+ apiKey : '***' ,
1393+ baseUrl : 'https://api.example.com' ,
1394+ apiFormat : 'anthropic' as const ,
1395+ models : { main : 'provider-main' , haiku : '' , sonnet : '' , opus : '' } ,
1396+ }
1397+
1398+ function useSavedProviderActive ( ) {
1399+ useSettingsStore . setState ( {
1400+ locale : 'en' ,
1401+ availableModels : MODELS ,
1402+ currentModel : MODELS [ 0 ] ,
1403+ activeProviderName : 'Provider A' ,
1404+ } )
1405+ useProviderStore . setState ( {
1406+ providers : [ savedProvider ] ,
1407+ activeId : 'provider-a' ,
1408+ hasLoadedProviders : true ,
1409+ isLoading : false ,
1410+ } )
1411+ useHahaOAuthStore . setState ( { status : { loggedIn : false } , fetchStatus : async ( ) => { } } )
1412+ useHahaOpenAIOAuthStore . setState ( { status : { loggedIn : false } , fetchStatus : async ( ) => { } } )
1413+ useHahaGrokOAuthStore . setState ( { status : { loggedIn : false } , fetchStatus : async ( ) => { } } )
1414+ }
1415+
1416+ it ( 'does not offer DeepSeek Account models while signed out' , async ( ) => {
1417+ useSavedProviderActive ( )
1418+ useHahaDeepSeekOAuthStore . setState ( { status : { loggedIn : false } , fetchStatus : async ( ) => { } } )
1419+
1420+ render ( < ModelSelector runtimeKey = "session-ds-out" /> )
1421+ await clickByRole ( / p r o v i d e r - m a i n / i)
1422+
1423+ const dropdown = screen . getByTestId ( 'model-selector-dropdown' )
1424+ expect ( dropdown . textContent ) . toContain ( 'Provider A' )
1425+ expect ( dropdown . textContent ) . not . toContain ( 'DeepSeek Account' )
1426+ expect ( dropdown . textContent ) . not . toContain ( 'deepseek-v4' )
1427+ } )
1428+
1429+ it ( 'lists the bundled DeepSeek models once signed in and selects one for the session' , async ( ) => {
1430+ useSavedProviderActive ( )
1431+ useHahaDeepSeekOAuthStore . setState ( {
1432+ status : { loggedIn : true , name : 'Ada' , contact : 'a***@example.com' } ,
1433+ fetchStatus : async ( ) => { } ,
1434+ } )
1435+
1436+ render ( < ModelSelector runtimeKey = "session-ds-in" /> )
1437+ await clickByRole ( / p r o v i d e r - m a i n / i)
1438+
1439+ const dropdown = screen . getByTestId ( 'model-selector-dropdown' )
1440+ expect ( dropdown . textContent ) . toContain ( 'DeepSeek Account' )
1441+ expect ( within ( dropdown ) . getByRole ( 'button' , { name : / d e e p s e e k - v 4 - p r o \[ 1 m \] / } ) ) . toBeInTheDocument ( )
1442+ await act ( async ( ) => {
1443+ fireEvent . click ( within ( dropdown ) . getByRole ( 'button' , { name : / d e e p s e e k - v 4 - f l a s h / } ) )
1444+ await Promise . resolve ( )
1445+ } )
1446+
1447+ expect ( useSessionRuntimeStore . getState ( ) . selections [ 'session-ds-in' ] ) . toMatchObject ( {
1448+ providerId : 'deepseek-account' ,
1449+ modelId : 'deepseek-v4-flash' ,
1450+ } )
1451+ } )
1452+
1453+ it ( 'keeps a restored DeepSeek Account selection instead of falling back to the saved provider' , ( ) => {
1454+ useSavedProviderActive ( )
1455+ useHahaDeepSeekOAuthStore . setState ( { status : null , fetchStatus : async ( ) => { } } )
1456+ useSessionRuntimeStore . getState ( ) . setSelection ( 'session-ds-restored' , {
1457+ providerId : 'deepseek-account' ,
1458+ modelId : 'deepseek-v4-pro[1m]' ,
1459+ } )
1460+
1461+ render ( < ModelSelector runtimeKey = "session-ds-restored" /> )
1462+
1463+ expect ( screen . getByRole ( 'button' , { name : / d e e p s e e k - v 4 - p r o \[ 1 m \] , D e e p S e e k A c c o u n t / } ) ) . toBeInTheDocument ( )
1464+ } )
1465+
1466+ it ( 'fetches the DeepSeek sign-in status when the selector opens' , async ( ) => {
1467+ useSavedProviderActive ( )
1468+ const fetchStatus = vi . fn ( async ( ) => { } )
1469+ useHahaDeepSeekOAuthStore . setState ( { status : null , fetchStatus } )
1470+
1471+ render ( < ModelSelector runtimeKey = "session-ds-lazy" /> )
1472+ expect ( fetchStatus ) . not . toHaveBeenCalled ( )
1473+ await clickByRole ( / p r o v i d e r - m a i n / i)
1474+
1475+ expect ( fetchStatus ) . toHaveBeenCalledTimes ( 1 )
1476+ } )
1477+
1478+ it ( 'opens the selector instead of routing to settings when DeepSeek is the only login' , async ( ) => {
1479+ useSettingsStore . setState ( { locale : 'en' , currentModel : undefined , activeProviderName : null } )
1480+ useProviderStore . setState ( { providers : [ ] , activeId : null , hasLoadedProviders : true , isLoading : false } )
1481+ useHahaOAuthStore . setState ( { status : { loggedIn : false } , fetchStatus : async ( ) => { } } )
1482+ useHahaOpenAIOAuthStore . setState ( { status : { loggedIn : false } , fetchStatus : async ( ) => { } } )
1483+ useHahaGrokOAuthStore . setState ( { status : { loggedIn : false } , fetchStatus : async ( ) => { } } )
1484+ useHahaDeepSeekOAuthStore . setState ( {
1485+ status : { loggedIn : true , name : null , contact : null } ,
1486+ fetchStatus : async ( ) => { } ,
1487+ } )
1488+
1489+ render ( < ModelSelector runtimeKey = "session-ds-only" /> )
1490+ await act ( async ( ) => {
1491+ fireEvent . click ( screen . getByRole ( 'button' ) )
1492+ await Promise . resolve ( )
1493+ } )
1494+
1495+ expect ( useUIStore . getState ( ) . pendingSettingsTab ) . not . toBe ( 'providers' )
1496+ expect ( screen . getByTestId ( 'model-selector-dropdown' ) . textContent ) . toContain ( 'DeepSeek Account' )
1497+ } )
1498+
1499+ it ( 'opens the selector after the status fetch finds a DeepSeek login nobody had checked yet' , async ( ) => {
1500+ useSettingsStore . setState ( { locale : 'en' , currentModel : undefined , activeProviderName : null } )
1501+ useProviderStore . setState ( { providers : [ ] , activeId : null , hasLoadedProviders : true , isLoading : false } )
1502+ useHahaOAuthStore . setState ( { status : null , fetchStatus : async ( ) => {
1503+ useHahaOAuthStore . setState ( { status : { loggedIn : false } } )
1504+ } } )
1505+ useHahaOpenAIOAuthStore . setState ( { status : null , fetchStatus : async ( ) => {
1506+ useHahaOpenAIOAuthStore . setState ( { status : { loggedIn : false } } )
1507+ } } )
1508+ useHahaGrokOAuthStore . setState ( { status : null , fetchStatus : async ( ) => {
1509+ useHahaGrokOAuthStore . setState ( { status : { loggedIn : false } } )
1510+ } } )
1511+ useHahaDeepSeekOAuthStore . setState ( { status : null , fetchStatus : async ( ) => {
1512+ useHahaDeepSeekOAuthStore . setState ( { status : { loggedIn : true , name : null , contact : null } } )
1513+ } } )
1514+
1515+ render ( < ModelSelector runtimeKey = "session-ds-late" /> )
1516+ await act ( async ( ) => {
1517+ fireEvent . click ( screen . getByRole ( 'button' ) )
1518+ await Promise . resolve ( )
1519+ } )
1520+
1521+ await waitFor ( ( ) => {
1522+ expect ( screen . getByTestId ( 'model-selector-dropdown' ) . textContent ) . toContain ( 'DeepSeek Account' )
1523+ } )
1524+ expect ( useUIStore . getState ( ) . pendingSettingsTab ) . not . toBe ( 'providers' )
1525+ } )
1526+ } )
1527+
13761528 it ( 'replaces a stale Grok runtime model with the current official default' , async ( ) => {
13771529 const grokModels : ModelInfo [ ] = [ {
13781530 id : 'grok-4.7' ,
0 commit comments