feat: add DeepChat deeplink support#4668
Conversation
WalkthroughThis PR integrates DeepChat support across backend and frontend systems. It adds DeepChat as a chat provider in the configuration, introduces a reusable API key fetching hook, extends URL resolution logic to handle DeepChat configuration placeholders, updates UI components to use per-item loading states, and wires the new hook into existing chat routing flows. ChangesDeepChat Provider Integration
Sequence DiagramsequenceDiagram
actor User
participant UI as Chat UI<br/>(Preset Item)
participant Hook as useActiveChatKey<br/>Hook
participant API as Backend<br/>API Keys Service
participant Token as Token Service
participant Chat as DeepChat<br/>Provider
User->>UI: Click "Open DeepChat"
activate UI
UI->>UI: Detect preset requires API key<br/>(via chatLinkRequiresApiKey)
UI->>Hook: useActiveChatKey(true)
activate Hook
Hook->>API: getApiKeys() for enabled keys
activate API
API-->>Hook: [enabled key, ...]
deactivate API
Hook->>Token: fetchTokenKey(keyId)
activate Token
Token-->>Hook: {key: "abc123"}
deactivate Token
Hook->>Hook: Return "sk-abc123"
deactivate Hook
UI->>UI: Resolve chat URL with config<br/>(resolveChatUrl)
UI->>UI: Encode {deepchatConfig}<br/>to base64 JSON payload
UI->>Chat: window.open(resolved_url)
activate Chat
Chat-->>User: DeepChat interface loads
deactivate Chat
deactivate UI
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/default/src/features/chat/hooks/use-active-chat-key.ts (1)
6-39:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd explicit return types on exported functions.
fetchActiveChatKey(line 6) anduseActiveChatKey(line 29) lack explicit return type annotations and should be added for clarity and consistency with TypeScript best practices.Suggested type annotations
-import { useQuery } from '@tanstack/react-query' +import { useQuery, type UseQueryResult } from '@tanstack/react-query' @@ -export async function fetchActiveChatKey() { +export async function fetchActiveChatKey(): Promise<string> { @@ -export function useActiveChatKey(enabled: boolean) { +export function useActiveChatKey(enabled: boolean): UseQueryResult<string, Error> {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/default/src/features/chat/hooks/use-active-chat-key.ts` around lines 6 - 39, Both exported functions lack explicit return types; add a Promise<string> return annotation to fetchActiveChatKey and annotate useActiveChatKey to return the proper query result type (e.g. UseQueryResult<string, Error> or the project's equivalent) so callers get correct typing. Update the function signatures (fetchActiveChatKey and useActiveChatKey) to include these return types and import the appropriate type (e.g. UseQueryResult) from your query library, ensuring the existing enabled parameter and useQuery call remain unchanged.
🧹 Nitpick comments (2)
web/classic/src/hooks/tokens/useTokensData.jsx (1)
234-268: ⚡ Quick winConsider extracting the
{xxxConfig}placeholder builders into a single helper.The DeepChat branch repeats the same encode-base64-then-URI pattern already used for
{cherryConfig}and{aionuiConfig}, with only the payload shape differing. As more providers are added, divergence between branches (typos inbaseUrl, missingid/platform, etc.) becomes more likely. A small lookup/iteration would keep the providers consistent and shrink the function.♻️ Sketch of the refactor
const CONFIG_PLACEHOLDERS = [ { token: '{cherryConfig}', build: (addr, key) => ({ id: 'new-api', baseUrl: addr, apiKey: key }), }, { token: '{aionuiConfig}', build: (addr, key) => ({ platform: 'new-api', baseUrl: addr, apiKey: key }), }, { token: '{deepchatConfig}', build: (addr, key) => ({ id: 'new-api', baseUrl: addr, apiKey: key }), }, ]; const placeholder = CONFIG_PLACEHOLDERS.find((p) => url.includes(p.token)); if (placeholder) { const encoded = encodeURIComponent( encodeToBase64(JSON.stringify(placeholder.build(serverAddress, `sk-${fullKey}`))), ); url = url.replaceAll(placeholder.token, encoded); } else { url = url .replaceAll('{address}', encodeURIComponent(serverAddress)) .replaceAll('{key}', `sk-${fullKey}`); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/classic/src/hooks/tokens/useTokensData.jsx` around lines 234 - 268, The repeated placeholder handling in useTokensData.jsx should be replaced with a single helper: create a CONFIG_PLACEHOLDERS array (or map) that lists each token string (e.g., '{cherryConfig}', '{aionuiConfig}', '{deepchatConfig}') and a build function returning the correct payload shape (use serverAddress and `sk-${fullKey}`), then in the token-replacement logic find the matching placeholder, call its build function, JSON.stringify -> encodeToBase64 -> encodeURIComponent, and replaceAll the token with the encoded value; fall back to the existing '{address}'/'{key}' branch when no placeholder is found. Ensure you reuse encodeToBase64, reference serverAddress and fullKey, and remove the duplicated branches.web/default/src/features/chat/lib/chat-links.ts (1)
138-166: ⚡ Quick winConsolidate the
{xxxConfig}placeholder branches into a config-driven loop.
{cherryConfig},{aionuiConfig}, and the new{deepchatConfig}all share the identicalencodeURIComponent(toBase64(JSON.stringify(payload)))pipeline and only differ in the payload shape. Centralizing them keeps the file a single source of truth and prevents the per-provider branches from drifting (e.g. one branch forgetting to URI-encode, or using a different key set than the correspondinguseTokensData.jsxbuilder).♻️ Suggested refactor
type ConfigPlaceholder = { token: string build: (serverAddress: string, apiKey: string) => Record<string, string> } const CONFIG_PLACEHOLDERS: ConfigPlaceholder[] = [ { token: '{cherryConfig}', build: (baseUrl, apiKey) => ({ id: 'new-api', baseUrl, apiKey }), }, { token: '{aionuiConfig}', build: (baseUrl, apiKey) => ({ platform: 'new-api', baseUrl, apiKey }), }, { token: '{deepchatConfig}', build: (baseUrl, apiKey) => ({ id: 'new-api', baseUrl, apiKey }), }, ] // inside resolveChatUrl, before the {address}/{key} fallbacks: const placeholder = CONFIG_PLACEHOLDERS.find((p) => url.includes(p.token)) if (placeholder) { const encoded = encodeURIComponent( toBase64(JSON.stringify(placeholder.build(safeServerAddress, safeApiKey))), ) return replaceToken(url, placeholder.token, encoded) }
chatLinkRequiresApiKeycould also derive its placeholder list from the same array to keep both helpers in sync.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/default/src/features/chat/lib/chat-links.ts` around lines 138 - 166, The three separate branches handling '{cherryConfig}', '{aionuiConfig}', and '{deepchatConfig}' should be consolidated into a config-driven loop to avoid duplicated encodeURIComponent(toBase64(JSON.stringify(...))) logic; add a CONFIG_PLACEHOLDERS array of {token, build(serverAddress, apiKey)} entries and in resolveChatUrl find the matching placeholder, call placeholder.build(safeServerAddress, safeApiKey), base64+URI-encode it via toBase64 and encodeURIComponent, then return replaceToken(url, placeholder.token, encoded); also update chatLinkRequiresApiKey to derive its checks from the same CONFIG_PLACEHOLDERS to keep helpers in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@web/default/src/features/chat/hooks/use-active-chat-key.ts`:
- Around line 7-15: The current code calls getApiKeys({ p: 1, size: 50 }) and
only searches that first page for an API_KEY_STATUS.ENABLED key, which can
falsely throw if an enabled key exists on a later page; change the logic in
useActiveChatKey to page-scan: call getApiKeys in a loop (increment p) until you
find an item with status === API_KEY_STATUS.ENABLED or until you've exhausted
pages (use result.data.total/size or stop when returned items length is 0),
return the first enabled key when found, and only throw the "No enabled API keys
found" error after all pages have been checked; keep references to getApiKeys,
API_KEY_STATUS.ENABLED, and the variables result/items/active when updating the
implementation.
- Around line 9-20: Replace the three user-facing hardcoded error strings in the
use-active-chat-key flow with i18n calls: use t() from i18next for the 'Failed
to load API keys', 'No enabled API keys found. Create or enable one first.' and
'Failed to load API key' messages; update the code around the items/result
checks and the fetchTokenKey handling (references: result, items, active,
API_KEY_STATUS, fetchTokenKey) to throw new Error(t('...')) with appropriate
translation keys, and add an import for t from 'i18next' at the top if it's not
already imported.
---
Outside diff comments:
In `@web/default/src/features/chat/hooks/use-active-chat-key.ts`:
- Around line 6-39: Both exported functions lack explicit return types; add a
Promise<string> return annotation to fetchActiveChatKey and annotate
useActiveChatKey to return the proper query result type (e.g.
UseQueryResult<string, Error> or the project's equivalent) so callers get
correct typing. Update the function signatures (fetchActiveChatKey and
useActiveChatKey) to include these return types and import the appropriate type
(e.g. UseQueryResult) from your query library, ensuring the existing enabled
parameter and useQuery call remain unchanged.
---
Nitpick comments:
In `@web/classic/src/hooks/tokens/useTokensData.jsx`:
- Around line 234-268: The repeated placeholder handling in useTokensData.jsx
should be replaced with a single helper: create a CONFIG_PLACEHOLDERS array (or
map) that lists each token string (e.g., '{cherryConfig}', '{aionuiConfig}',
'{deepchatConfig}') and a build function returning the correct payload shape
(use serverAddress and `sk-${fullKey}`), then in the token-replacement logic
find the matching placeholder, call its build function, JSON.stringify ->
encodeToBase64 -> encodeURIComponent, and replaceAll the token with the encoded
value; fall back to the existing '{address}'/'{key}' branch when no placeholder
is found. Ensure you reuse encodeToBase64, reference serverAddress and fullKey,
and remove the duplicated branches.
In `@web/default/src/features/chat/lib/chat-links.ts`:
- Around line 138-166: The three separate branches handling '{cherryConfig}',
'{aionuiConfig}', and '{deepchatConfig}' should be consolidated into a
config-driven loop to avoid duplicated
encodeURIComponent(toBase64(JSON.stringify(...))) logic; add a
CONFIG_PLACEHOLDERS array of {token, build(serverAddress, apiKey)} entries and
in resolveChatUrl find the matching placeholder, call
placeholder.build(safeServerAddress, safeApiKey), base64+URI-encode it via
toBase64 and encodeURIComponent, then return replaceToken(url,
placeholder.token, encoded); also update chatLinkRequiresApiKey to derive its
checks from the same CONFIG_PLACEHOLDERS to keep helpers in sync.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c4cd7864-2f98-48ad-937c-bdbbe4a59764
📒 Files selected for processing (9)
setting/chat.goweb/classic/src/components/layout/SiderBar.jsxweb/classic/src/hooks/tokens/useTokensData.jsxweb/classic/src/pages/Setting/Chat/SettingsChats.jsxweb/default/src/components/layout/components/chat-presets-item.tsxweb/default/src/features/chat/hooks/use-active-chat-key.tsweb/default/src/features/chat/lib/chat-links.tsweb/default/src/routes/_authenticated/chat/$chatId.tsxweb/default/src/routes/_authenticated/chat2link.tsx
| const result = await getApiKeys({ p: 1, size: 50 }) | ||
| if (!result.success) { | ||
| throw new Error(result.message || 'Failed to load API keys') | ||
| } | ||
|
|
||
| const items = result.data?.items ?? [] | ||
| const active = items.find((item) => item.status === API_KEY_STATUS.ENABLED) | ||
| if (!active) { | ||
| throw new Error('No enabled API keys found. Create or enable one first.') |
There was a problem hiding this comment.
Enabled-key detection is limited to page 1.
Line 7 fetches only the first 50 keys, and Line 13 only searches that slice. If the first enabled key is on a later page, Line 15 throws a false “No enabled API keys found” error.
💡 Suggested fix (scan pages until an enabled key is found)
export async function fetchActiveChatKey() {
- const result = await getApiKeys({ p: 1, size: 50 })
- if (!result.success) {
- throw new Error(result.message || 'Failed to load API keys')
- }
-
- const items = result.data?.items ?? []
- const active = items.find((item) => item.status === API_KEY_STATUS.ENABLED)
+ const pageSize = 50
+ let page = 1
+ let active: { id: string | number } | undefined
+
+ while (!active) {
+ const result = await getApiKeys({ p: page, size: pageSize })
+ if (!result.success) {
+ throw new Error(result.message || 'Failed to load API keys')
+ }
+
+ const items = result.data?.items ?? []
+ active = items.find((item) => item.status === API_KEY_STATUS.ENABLED)
+ if (active || items.length < pageSize) break
+ page += 1
+ }
+
if (!active) {
throw new Error('No enabled API keys found. Create or enable one first.')
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const result = await getApiKeys({ p: 1, size: 50 }) | |
| if (!result.success) { | |
| throw new Error(result.message || 'Failed to load API keys') | |
| } | |
| const items = result.data?.items ?? [] | |
| const active = items.find((item) => item.status === API_KEY_STATUS.ENABLED) | |
| if (!active) { | |
| throw new Error('No enabled API keys found. Create or enable one first.') | |
| const pageSize = 50 | |
| let page = 1 | |
| let active: { id: string | number } | undefined | |
| while (!active) { | |
| const result = await getApiKeys({ p: page, size: pageSize }) | |
| if (!result.success) { | |
| throw new Error(result.message || 'Failed to load API keys') | |
| } | |
| const items = result.data?.items ?? [] | |
| active = items.find((item) => item.status === API_KEY_STATUS.ENABLED) | |
| if (active || items.length < pageSize) break | |
| page += 1 | |
| } | |
| if (!active) { | |
| throw new Error('No enabled API keys found. Create or enable one first.') | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/features/chat/hooks/use-active-chat-key.ts` around lines 7 -
15, The current code calls getApiKeys({ p: 1, size: 50 }) and only searches that
first page for an API_KEY_STATUS.ENABLED key, which can falsely throw if an
enabled key exists on a later page; change the logic in useActiveChatKey to
page-scan: call getApiKeys in a loop (increment p) until you find an item with
status === API_KEY_STATUS.ENABLED or until you've exhausted pages (use
result.data.total/size or stop when returned items length is 0), return the
first enabled key when found, and only throw the "No enabled API keys found"
error after all pages have been checked; keep references to getApiKeys,
API_KEY_STATUS.ENABLED, and the variables result/items/active when updating the
implementation.
| throw new Error(result.message || 'Failed to load API keys') | ||
| } | ||
|
|
||
| const items = result.data?.items ?? [] | ||
| const active = items.find((item) => item.status === API_KEY_STATUS.ENABLED) | ||
| if (!active) { | ||
| throw new Error('No enabled API keys found. Create or enable one first.') | ||
| } | ||
|
|
||
| const keyResult = await fetchTokenKey(active.id) | ||
| if (!keyResult.success || !keyResult.data?.key) { | ||
| throw new Error(keyResult.message || 'Failed to load API key') |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify there are no remaining hardcoded string literals in throw-new-Error calls in this hook.
rg -n "throw new Error\\(['\"][^'\"]+['\"]\\)" web/default/src/features/chat/hooks/use-active-chat-key.tsRepository: QuantumNous/new-api
Length of output: 144
🏁 Script executed:
cat -n web/default/src/features/chat/hooks/use-active-chat-key.tsRepository: QuantumNous/new-api
Length of output: 1564
🏁 Script executed:
# Verify the file follows the expected pattern for non-React utility functions
head -5 web/default/src/features/chat/hooks/use-active-chat-key.ts | grep -E "(React|useTranslation|i18next)"Repository: QuantumNous/new-api
Length of output: 45
Use t() from i18next for user-visible error messages in this utility function.
Lines 9, 15, and 20 contain hardcoded error strings that violate the i18n requirement for .ts files. Replace with t() calls:
Suggested refactor
+import { t } from 'i18next'
import { useQuery } from '@tanstack/react-query'
import { fetchTokenKey, getApiKeys } from '@/features/keys/api'
import { API_KEY_STATUS } from '@/features/keys/constants'
import { useAuthStore } from '@/stores/auth-store'
export async function fetchActiveChatKey() {
const result = await getApiKeys({ p: 1, size: 50 })
if (!result.success) {
- throw new Error(result.message || 'Failed to load API keys')
+ throw new Error(result.message || t('chat.errors.failedToLoadApiKeys'))
}
const items = result.data?.items ?? []
const active = items.find((item) => item.status === API_KEY_STATUS.ENABLED)
if (!active) {
- throw new Error('No enabled API keys found. Create or enable one first.')
+ throw new Error(t('chat.errors.noEnabledApiKeys'))
}
const keyResult = await fetchTokenKey(active.id)
if (!keyResult.success || !keyResult.data?.key) {
- throw new Error(keyResult.message || 'Failed to load API key')
+ throw new Error(keyResult.message || t('chat.errors.failedToLoadApiKey'))
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| throw new Error(result.message || 'Failed to load API keys') | |
| } | |
| const items = result.data?.items ?? [] | |
| const active = items.find((item) => item.status === API_KEY_STATUS.ENABLED) | |
| if (!active) { | |
| throw new Error('No enabled API keys found. Create or enable one first.') | |
| } | |
| const keyResult = await fetchTokenKey(active.id) | |
| if (!keyResult.success || !keyResult.data?.key) { | |
| throw new Error(keyResult.message || 'Failed to load API key') | |
| import { t } from 'i18next' | |
| import { useQuery } from '@tanstack/react-query' | |
| import { fetchTokenKey, getApiKeys } from '@/features/keys/api' | |
| import { API_KEY_STATUS } from '@/features/keys/constants' | |
| import { useAuthStore } from '@/stores/auth-store' | |
| export async function fetchActiveChatKey() { | |
| const result = await getApiKeys({ p: 1, size: 50 }) | |
| if (!result.success) { | |
| throw new Error(result.message || t('chat.errors.failedToLoadApiKeys')) | |
| } | |
| const items = result.data?.items ?? [] | |
| const active = items.find((item) => item.status === API_KEY_STATUS.ENABLED) | |
| if (!active) { | |
| throw new Error(t('chat.errors.noEnabledApiKeys')) | |
| } | |
| const keyResult = await fetchTokenKey(active.id) | |
| if (!keyResult.success || !keyResult.data?.key) { | |
| throw new Error(keyResult.message || t('chat.errors.failedToLoadApiKey')) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/features/chat/hooks/use-active-chat-key.ts` around lines 9 -
20, Replace the three user-facing hardcoded error strings in the
use-active-chat-key flow with i18n calls: use t() from i18next for the 'Failed
to load API keys', 'No enabled API keys found. Create or enable one first.' and
'Failed to load API key' messages; update the code around the items/result
checks and the fetchTokenKey handling (references: result, items, active,
API_KEY_STATUS, fetchTokenKey) to throw new Error(t('...')) with appropriate
translation keys, and add an import for t from 'i18next' at the top if it's not
already imported.
Important
📝 变更描述 / Description
(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)
defaultUI 聊天入口生成 deeplink 时误使用脱敏 API Key 的问题:现在会先定位启用中的 token,再通过真实 key 接口获取完整sk-...,确保 Cherry Studio、AionUI、DeepChat 等客户端导入时拿到可用密钥。defaultUI 侧边栏的真实 key 获取逻辑,改为点击外部客户端入口时按需获取,并按当前用户隔离缓存,避免跨会话复用旧用户的真实 key。🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
(请在此粘贴截图、关键日志或测试报告,以证明变更生效)

Summary by CodeRabbit
New Features
Improvements