Skip to content

feat: add combo routing - #5443

Open
androw wants to merge 3 commits into
QuantumNous:mainfrom
androw:combo
Open

feat: add combo routing#5443
androw wants to merge 3 commits into
QuantumNous:mainfrom
androw:combo

Conversation

@androw

@androw androw commented Jun 11, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

This PR aims at providing a way to allow the user to create custom model id, that can be use to route request to a list of models based on various strategies.

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

(请在此粘贴截图、关键日志或测试报告,以证明变更生效)

Summary by CodeRabbit

  • New Features

    • Full "Combos" management: create/edit/delete (single & bulk), list/search with pagination, selection, status toggle, per-row actions, create/edit drawer, weights/strategy options, API surface, and a new sidebar page/route.
  • Runtime

    • Combo-based routing: requests using combo: are resolved to concrete models/channels at request time with selectable strategies.
  • Documentation

    • Added Combo feature design doc describing concepts, strategies and rollout.
  • Localization

    • Added combo translations for EN/FR/JA/RU/VI/ZH.
  • Chores

    • DB migrations updated to include combos.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1e5b5ed1-dfa1-4ee1-a09e-aa5de89742cf

📥 Commits

Reviewing files that changed from the base of the PR and between 785caa8 and 6f29c23.

📒 Files selected for processing (27)
  • controller/combo.go
  • docs/design/combo-feature.md
  • web/default/src/features/combos/api.ts
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/features/combos/components/combos-columns.tsx
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/components/combos-table.tsx
  • web/default/src/features/combos/constants.ts
  • web/default/src/features/combos/index.tsx
  • web/default/src/features/combos/types.ts
  • web/default/src/hooks/use-sidebar-config.ts
  • web/default/src/hooks/use-sidebar-data.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/routeTree.gen.ts
  • web/default/src/routes/_authenticated/combos/index.tsx
✅ Files skipped from review due to trivial changes (7)
  • web/default/src/hooks/use-sidebar-config.ts
  • docs/design/combo-feature.md
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/routeTree.gen.ts
🚧 Files skipped from review as they are similar to previous changes (19)
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/components/combos-columns.tsx
  • web/default/src/features/combos/index.tsx
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/i18n/locales/ru.json
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/features/combos/api.ts
  • web/default/src/features/combos/types.ts
  • web/default/src/routes/_authenticated/combos/index.tsx
  • web/default/src/features/combos/components/combos-table.tsx
  • controller/combo.go
  • web/default/src/hooks/use-sidebar-data.ts
  • web/default/src/features/combos/constants.ts

Walkthrough

Implements combo-based multi-model routing: DB model and migrations, Gin CRUD/search API, strategy resolver and request rewriting in distributor middleware, plus a React admin UI with routes, sidebar, and i18n.

Changes

Combo Multi-Model Routing

Layer / File(s) Summary
Combo data model and storage
constant/context_key.go, model/combo.go, model/main.go
Defines Combo GORM model (name, user_id, models CSV, strategy, weights JSON, status, created_time), CRUD methods and helpers, and registers the model in migrations; adds combo context keys.
REST API endpoints and routing
controller/combo.go, router/api-router.go
Gin handlers: Create, List, Get (owner/admin checks), Update (partial + status constraint), Delete (role-scoped), Search (keyword); routes registered under /api/combo with UserAuth.
Combo routing strategy service
service/combo_routing.go
Exports ResolveComboModel, GetComboFallbackChannel, and RewriteRequestBodyModel; implements fallback, random, weighted (JSON weights), and round-robin selection, weight parsing, and in-memory round-robin counters.
Middleware request routing and rewriting
middleware/distributor.go
Distributor detects model: "combo:<name>", validates/loads combo, records combo name/strategy in context, resolves target model/channel via service, rewrites request body model to resolved model, and avoids normal channel selection when appropriate.
Frontend types, validation, and API client
web/default/src/features/combos/types.ts, web/default/src/features/combos/constants.ts, web/default/src/features/combos/api.ts
Zod schemas and TS types for Combo and form validation, UI constants for status/strategy, messages, and API wrappers for list/search/get/create/update/delete/batch/status operations.
Frontend provider and page
web/default/src/features/combos/components/combos-provider.tsx, web/default/src/features/combos/index.tsx
CombosProvider and useCombos hook providing dialog state (open/currentRow) and a numeric refreshTrigger; page composition with provider, primary buttons, table, and dialogs.
UI components and table page
web/default/src/features/combos/components/*
Cell renderers, column defs with selection and timestamp formatting, row actions, bulk/single/batch delete dialogs, create/edit drawer with conditional weights field, primary buttons, CombosTable with URL-synced pagination/filtering, and bulk actions.
Routing, sidebar, i18n, design doc
web/default/src/routes/_authenticated/combos/index.tsx, web/default/src/routeTree.gen.ts, web/default/src/i18n/locales/{en,fr,ja,ru,vi,zh}.json, web/default/src/hooks/*sidebar*, docs/design/combo-feature.md
Adds authenticated /combos route and generated route-tree entries, enables combo sidebar module and nav item, adds translations across locales, and includes the design document.

Sequence Diagram

sequenceDiagram
  participant User
  participant Frontend
  participant Distributor
  participant Service
  participant DB
  participant ChannelProvider

  User->>Frontend: request with model="combo:mycombo"
  Frontend->>Distributor: send request
  Distributor->>DB: GetComboByNameUserId / GetComboByName
  Distributor->>Service: ResolveComboModel(combo, tokenGroup)
  Service->>DB: query channels / models as needed
  Service-->>Distributor: ComboRoutingResult { model, channel?, group? }
  Distributor->>Distributor: RewriteRequestBodyModel -> model = resolved model
  Distributor->>ChannelProvider: forward to selected channel (if any) / continue selection
  ChannelProvider-->>User: response from resolved downstream model
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • seefs001

Poem

🐰 I hop through combos, models in line,

fallback, random, weighted, round-robin fine,
Middleware nudges the model in the byte,
Admin UI sings, translations alight,
I nibble the changes — code feels just right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add combo routing' clearly and concisely summarizes the main change—adding a combo routing feature. It directly reflects the primary objective of the PR across backend, middleware, and frontend layers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

🧹 Nitpick comments (8)
controller/combo.go (2)

48-48: 💤 Low value

Redundant CreatedTime assignment.

Line 48 manually sets combo.CreatedTime = time.Now().Unix(), but combo.Insert() on line 50 already sets CreatedTime internally (see model/combo.go line 28). This assignment is unnecessary and will be overwritten.

🧹 Proposed cleanup
 	userId := c.GetInt("id")
 	combo.UserId = userId
 	combo.Status = 1
-	combo.CreatedTime = time.Now().Unix()
 
 	if err := combo.Insert(); err != nil {
🤖 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 `@controller/combo.go` at line 48, Redundant assignment: remove the manual
setting of combo.CreatedTime (the line assigning combo.CreatedTime =
time.Now().Unix()) since combo.Insert() already initializes CreatedTime
internally; update the controller method that calls combo.Insert() (referencing
combo.CreatedTime and the combo.Insert() call) to delete that assignment and
rely on the model implementation in model/combo.go to set CreatedTime.

34-34: ⚡ Quick win

Consider extracting strategy validation to a constant or helper.

The strategy validation on line 34 uses hardcoded string literals repeated inline. For better maintainability and to avoid typos, consider defining allowed strategies as a package-level constant slice and using a helper function for validation.

♻️ Example refactor
var allowedStrategies = []string{"fallback", "random", "weighted", "round_robin"}

func isValidStrategy(s string) bool {
    for _, valid := range allowedStrategies {
        if s == valid {
            return true
        }
    }
    return false
}

Then replace line 34:

-	if combo.Strategy != "fallback" && combo.Strategy != "random" && combo.Strategy != "weighted" && combo.Strategy != "round_robin" {
+	if !isValidStrategy(combo.Strategy) {
🤖 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 `@controller/combo.go` at line 34, Extract the inline strategy check into a
package-level allowedStrategies slice and a helper isValidStrategy(s string)
bool, then replace the long condition that reads combo.Strategy != "fallback" &&
... with a call to isValidStrategy(combo.Strategy); ensure allowedStrategies
contains "fallback", "random", "weighted", "round_robin" and update the
validation logic in the function/method that reads combo.Strategy to use
isValidStrategy.
web/default/src/features/combos/components/combos-multi-delete-dialog.tsx (2)

36-43: ⚡ Quick win

Avoid destructuring component props.

The component destructures props open, onOpenChange, and selectedIds. As per coding guidelines, use props.open, props.onOpenChange, and props.selectedIds directly instead for clarity and consistency.

♻️ Proposed refactor
 export function CombosMultiDeleteDialog({
-  open,
-  onOpenChange,
-  selectedIds,
-}: {
+  props
+}: props: {
   open: boolean
   onOpenChange: (open: boolean) => void
   selectedIds: number[]
 }) {
   const { t } = useTranslation()
   const { triggerRefresh } = useCombos()
   const [isDeleting, setIsDeleting] = useState(false)
 
   const handleDelete = async () => {
     setIsDeleting(true)
     try {
-      await Promise.all(selectedIds.map((id) => deleteCombo(id)))
+      await Promise.all(props.selectedIds.map((id) => deleteCombo(id)))
       toast.success(t(SUCCESS_MESSAGES.COMBO_BATCH_DELETED))
       triggerRefresh()
-      onOpenChange(false)
+      props.onOpenChange(false)
     } catch {
       toast.error(t(ERROR_MESSAGES.UNEXPECTED))
     } finally {
       setIsDeleting(false)
     }
   }
 
   return (
-    <AlertDialog open={open} onOpenChange={onOpenChange}>
+    <AlertDialog open={props.open} onOpenChange={props.onOpenChange}>
       <AlertDialogContent>
         <AlertDialogHeader>
           <AlertDialogTitle>{t('Are you sure?')}</AlertDialogTitle>
           <AlertDialogDescription>
             {t('This will permanently delete')} {selectedIds.length}{' '}
-            {selectedIds.length === 1 ? t('combo') : t('combos')}
+            {props.selectedIds.length === 1 ? t('combo') : t('combos')}
             .{t(' This action cannot be undone.')}
           </AlertDialogDescription>
🤖 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/combos/components/combos-multi-delete-dialog.tsx`
around lines 36 - 43, The component CombosMultiDeleteDialog currently
destructures props (open, onOpenChange, selectedIds); change it to use the props
object instead to follow guidelines—replace destructuring in the function
signature and all internal references so they read props.open,
props.onOpenChange, and props.selectedIds, ensuring the prop type annotation
remains intact for the function parameter.

Source: Coding guidelines


49-61: ⚖️ Poor tradeoff

Consider handling partial deletion failures gracefully.

Promise.all fails atomically—if any single delete fails, the entire operation is rejected and the user sees a generic error. Users cannot tell which combos failed or succeeded. Consider using Promise.allSettled to track individual results and show a more informative toast indicating partial success/failure counts.

♻️ Proposed enhancement
   const handleDelete = async () => {
     setIsDeleting(true)
     try {
-      await Promise.all(selectedIds.map((id) => deleteCombo(id)))
-      toast.success(t(SUCCESS_MESSAGES.COMBO_BATCH_DELETED))
+      const results = await Promise.allSettled(
+        selectedIds.map((id) => deleteCombo(id))
+      )
+      const succeeded = results.filter((r) => r.status === 'fulfilled').length
+      const failed = results.filter((r) => r.status === 'rejected').length
+      if (failed === 0) {
+        toast.success(t(SUCCESS_MESSAGES.COMBO_BATCH_DELETED))
+      } else if (succeeded > 0) {
+        toast.warning(`${succeeded} deleted, ${failed} failed`)
+      } else {
+        toast.error(t(ERROR_MESSAGES.UNEXPECTED))
+      }
       triggerRefresh()
       onOpenChange(false)
     } catch {
       toast.error(t(ERROR_MESSAGES.UNEXPECTED))
     } finally {
       setIsDeleting(false)
     }
   }
🤖 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/combos/components/combos-multi-delete-dialog.tsx`
around lines 49 - 61, The handleDelete function currently uses Promise.all so
any single deleteCombo(id) rejection shows a generic error; change it to use
Promise.allSettled(selectedIds.map(id => deleteCombo(id))) in handleDelete, then
count fulfilled vs rejected results, show a toast reflecting full success,
partial success (e.g., "X deleted, Y failed"), or full failure, and optionally
include/log the failed ids; still call triggerRefresh() after successful
deletions and setIsDeleting(false) in finally, and keep invoking
onOpenChange(false) only when at least one delete succeeded (or per your UX
decision).
web/default/src/features/combos/components/combos-provider.tsx (1)

35-35: ⚡ Quick win

Avoid destructuring component props.

The component destructures children from props. As per coding guidelines, use props.children directly instead for clarity and consistency.

♻️ Proposed refactor
-export function CombosProvider({ children }: { children: React.ReactNode }) {
+export function CombosProvider(props: { children: React.ReactNode }) {
   const [open, setOpen] = React.useState<ComboDialogType | null>(null)
   const [currentRow, setCurrentRow] = React.useState<Combo | null>(null)
   const [refreshTrigger, setRefreshTrigger] = React.useState(0)
   const triggerRefresh = useCallback(() => {
     setRefreshTrigger((v) => v + 1)
   }, [])
 
   return (
     <CombosContext.Provider
       value={{
         open,
         setOpen,
         currentRow,
         setCurrentRow,
         refreshTrigger,
         triggerRefresh,
       }}
     >
-      {children}
+      {props.children}
     </CombosContext.Provider>
   )
 }
🤖 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/combos/components/combos-provider.tsx` at line 35,
The CombosProvider component currently destructures children in its parameter
list; change the component to accept a single props parameter (e.g., props: {
children: React.ReactNode }) and update the implementation to reference
props.children instead of the destructured children variable so the component
follows the project's non-destructuring props convention (locate the
CombosProvider function declaration and any usage of children inside it and
replace accordingly).

Source: Coding guidelines

web/default/src/features/combos/components/combos-columns.tsx (2)

54-96: ⚡ Quick win

Consider using hierarchical i18n keys for table headers.

The column headers use flat keys like t('Name'), t('Models'), which may conflict with other features and are less maintainable. As per coding guidelines, translation keys should be hierarchical and semantically clear (e.g., dashboard.overview.title). Consider using keys like t('combos.table.name'), t('combos.table.models'), etc.

♻️ Example refactor to hierarchical keys
       header: ({ column }) => (
-        <DataTableColumnHeader column={column} title={t('Name')} />
+        <DataTableColumnHeader column={column} title={t('combos.table.name')} />
       ),

Apply similar changes to 'Models', 'Strategy', 'Status', and 'Created At' headers.

🤖 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/combos/components/combos-columns.tsx` around lines
54 - 96, Update the hardcoded flat i18n keys used in the DataTableColumnHeader
calls to hierarchical, semantically-scoped keys; replace t('Name'), t('Models'),
t('Strategy'), t('Status'), and t('Created At') with keys like
t('combos.table.name'), t('combos.table.models'), t('combos.table.strategy'),
t('combos.table.status'), and t('combos.table.createdAt') respectively in the
combos-columns component where DataTableColumnHeader is rendered (refer to the
column definitions using accessorKey 'name', 'models', 'strategy', 'status',
'created_time' and the StrategyCell/StatusCell usages) so translations are
organized and unambiguous.

Source: Coding guidelines


98-102: ⚡ Quick win

Consider locale-aware date formatting.

The .toLocaleString() method without a locale parameter uses the browser's default locale, which may not match the user's selected language in the application. Consider passing the current i18n locale to ensure consistent date formatting.

♻️ Example using i18n locale
+import { useTranslation } from 'react-i18next'
+
 export function useCombosColumns(): ColumnDef<Combo>[] {
-  const { t } = useTranslation()
+  const { t, i18n } = useTranslation()
   return [
     // ...
     {
       accessorKey: 'created_time',
       header: ({ column }) => (
         <DataTableColumnHeader column={column} title={t('Created At')} />
       ),
       cell: ({ row }) => {
         const time = row.getValue('created_time') as number | null
         if (!time) return '-'
-        return new Date(time * 1000).toLocaleString()
+        return new Date(time * 1000).toLocaleString(i18n.language)
       },
       size: 160,
     },
🤖 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/combos/components/combos-columns.tsx` around lines
98 - 102, The date cell renderer in combos-columns.tsx (cell: ({ row }) => { ...
}) uses toLocaleString() without specifying a locale; update the cell
implementation to retrieve the app's current i18n locale (e.g., from
i18n.language or the app's locale context) and pass it as the first argument to
Date.prototype.toLocaleString so the created_time display respects the user's
selected language/locale; ensure you still handle null/undefined created_time
and convert the epoch seconds (time * 1000) before formatting.
web/default/src/features/combos/components/combos-table.tsx (1)

90-96: ⚡ Quick win

Consider using hierarchical i18n keys.

Similar to the columns file, these UI strings use flat keys like t('No Combos Found'), t('Search combos...'). As per coding guidelines, translation keys should be hierarchical (e.g., t('combos.empty.title'), t('combos.search.placeholder')).

♻️ Example refactor to hierarchical keys
       emptyTitle={t('combos.empty.title')}
       emptyDescription={t('combos.empty.description')}
       // ...
       <Input
-        placeholder={t('Search combos...')}
+        placeholder={t('combos.search.placeholder')}
         value={globalFilter ?? ''}
         onChange={(e) => onGlobalFilterChange(e.target.value)}
         className='max-w-sm'
       />
🤖 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/combos/components/combos-table.tsx` around lines 90
- 96, The UI strings in combos-table.tsx are using flat i18n keys (e.g., t('No
Combos Found'), t('Search combos...')) — update the calls to use hierarchical
keys such as t('combos.empty.title'), t('combos.empty.description'), and
t('combos.search.placeholder') in the emptyTitle, emptyDescription and Input
placeholder props (within the renderToolbar block), and ensure corresponding
entries are added/updated in the translation resource files for all supported
locales so the new keys resolve at runtime.

Source: Coding guidelines

🤖 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 `@model/combo.go`:
- Around line 111-118: DeleteComboById currently calls DB.Delete immediately
then reassigns query, causing two deletes; change it to build the query first
(e.g., use DB.Model(&Combo{}) or DB.Unscoped()/DB.Session if needed) so no
delete is executed on initialization, then conditionally add the user scoping
with Where("user_id = ?", userId[0]) on the same query, and finally call
query.Delete(&Combo{}, "id = ?", id) once; update the query variable in the
DeleteComboById function and ensure you return query.Error.

In `@model/main.go`:
- Line 284: migrateDBFast() is missing the Combo model in its migrations slice
causing the combos table to be skipped; update the migrations slice inside
migrateDBFast() to include &Combo{} just like migrateDB() does (or if
migrateDBFast() is intentionally deprecated, remove/mark it accordingly). Locate
the migrations []interface{} declaration in migrateDBFast() and add &Combo{} to
that list so the Combo model is created/migrated when migrateDBFast() runs.

In `@service/combo_routing.go`:
- Around line 184-191: The global comboRoundRobinCounters map is accessed
concurrently in resolveRoundRobin (comboRoundRobinCounters and combo.Id),
causing race conditions; protect it by introducing synchronization: add a
package-level sync.Mutex (e.g., comboRRMutex) and wrap the read-modify-write and
subsequent read (the increment and idx calculation inside resolveRoundRobin)
with comboRRMutex.Lock()/Unlock(), ensuring you increment the counter and
compute idx while holding the lock and return &ComboRoutingResult{ResolvedModel:
models[idx]}; alternatively replace comboRoundRobinCounters with a sync.Map and
perform atomic load/store semantics, but ensure the resolveRoundRobin logic
updates and reads the counter atomically.
- Line 159: Replace the direct use of encoding/json's json.Unmarshal in
combo_routing.go (the call json.Unmarshal([]byte(weightsStr), &parsed) that
deserializes weightsStr into parsed) with the project's wrapper common.Unmarshal
to enforce consistent JSON handling (i.e., call common.Unmarshal with the same
input/target used previously). After changing the call, remove the
"encoding/json" import if it becomes unused. Ensure you update the call site
referencing weightsStr and parsed so compilation and behavior remain equivalent.

In `@web/default/src/features/combos/components/combos-cells.tsx`:
- Around line 36-49: Replace the hardcoded English labels in StatusCell with
calls to the i18n t() function: import or obtain t (e.g., via
useTranslation/useTranslations as used in the codebase), keep the existing
enabled computation (const enabled = combo.status === 1) and change the
displayed text to t('combos.status.enabled') when enabled and
t('combos.status.disabled') when not; ensure to add the import/hook usage for t
at the top of the component and update any tests or story text keys accordingly.
- Around line 21-34: Replace hardcoded English labels in StrategyCell by calling
the i18n t() function: change strategyMap in the StrategyCell component to map
keys to translation keys (e.g. 'fallback' -> t('combos.strategy.fallback'),
'random' -> t('combos.strategy.random'), etc.) and compute label as
strategyMap[combo.strategy] || t(`combos.strategy.${combo.strategy}`) so unknown
strategies are translated too; also add the required import or hook for t() at
the top of the file and keep the rest of the component (span and classNames)
unchanged.

In `@web/default/src/features/combos/components/combos-mutate-drawer.tsx`:
- Around line 176-177: The placeholder strings in the combos-mutate-drawer
component are hardcoded (e.g., the Textarea placeholder 'gpt-4, claude-3,
gemini-pro' and the other user-facing string around lines 229-230); replace
these literals with translation calls using the t() function from
useTranslation() (import/use useTranslation in this component if not already
present) and reference appropriate i18n keys (e.g.,
t('combos.placeholder_models') and t('combos.placeholder_otherField')), then add
the corresponding keys to the translation resource files so the placeholders are
localized.
- Around line 99-115: The effect calls getCombo asynchronously and applies
form.reset with its result without verifying the response still matches the
active row; capture the currentRow id (e.g., const expectedId = currentRow?.id)
before calling getCombo and after the promise resolves verify that open &&
isUpdate && currentRow?.id === expectedId (or use an AbortController/signal if
getCombo supports cancellation) before calling form.reset, so stale responses do
not overwrite the form for a different currentRow.

In `@web/default/src/features/combos/components/combos-row-actions.tsx`:
- Around line 57-59: The icon-only action button in the CombosRowActions
component lacks an accessible name; update the Button (in
combos-row-actions.tsx) to include a clear aria-label (e.g., aria-label="More
actions" or similar) so screen readers can announce it, and mark the decorative
icon MoreHorizontal with aria-hidden="true" to avoid duplicate/verbose
announcements; ensure the label text matches the button's purpose and is
localized if needed.
- Around line 38-47: The handler handleToggleStatus currently calls updateCombo
with the entire combo payload (using combo.name/models/weights/strategy),
risking overwriting newer fields; change it to call the dedicated status-only
API (e.g., updateComboStatus or the PATCH status endpoint) with combo.id and the
computed newStatus (based on combo.status) so only the status is updated and
other fields aren’t overwritten, and remove the full payload usage in
handleToggleStatus.

In `@web/default/src/features/combos/components/combos-table.tsx`:
- Line 62: Remove the debug console.log in CombosTable's getCombos (the line
logging "[CombosTable] getCombos raw result:") — delete that console.log or
replace it with a conditional/dev-only logger if runtime diagnostics are
required (e.g., check process.env.NODE_ENV === 'development' or use the app
logger utility) so no debug console output exists in production.

In `@web/default/src/i18n/locales/fr.json`:
- Line 4558: The French locale contains an untranslated entry for the key "This
will permanently delete"; update web/default/src/i18n/locales/fr.json by
replacing the English value for the key "This will permanently delete" with a
proper French translation (for example "Cela supprimera définitivement" or
another approved French phrase), ensuring the JSON value remains a string and
respects existing formatting/escaping conventions so deletion confirmation
dialogs show French text.

In `@web/default/src/i18n/locales/ja.json`:
- Line 4558: Replace the untranslated English value for the key "This will
permanently delete" in the Japanese locale by providing a proper Japanese
translation (e.g., "これにより完全に削除されます" or "これを実行すると完全に削除されます") so the UI is fully
localized; update the value string in ja.json for the identical key while
preserving the JSON key format and punctuation.
- Line 4542: Remove the duplicate JSON key that includes a leading space (" This
action cannot be undone.") so lookups only use the canonical key ("This action
cannot be undone."); locate the entry with the leading-space key in ja.json and
delete that key/value pair, ensuring only the correctly-spaced key remains to
avoid duplicate mapping and inconsistent translations.

In `@web/default/src/i18n/locales/ru.json`:
- Line 4558: The Russian locale entry for the key "This will permanently delete"
in ru.json is still English; replace the value string currently set to "This
will permanently delete" with an appropriate Russian translation (e.g., "Это
действие удалит навсегда" or another accurate Russian phrasing) so the
confirmation UI is fully localized; update only the value for the existing JSON
key without changing the key itself.

In `@web/default/src/i18n/locales/vi.json`:
- Around line 4558-4565: The vi.json contains untranslated values for keys like
"This will permanently delete" and "combo" (also "combos" at the end); update
their string values to proper Vietnamese translations (e.g., "This will
permanently delete" -> "Điều này sẽ xóa vĩnh viễn", "combo" -> localized term
such as "combo" or "gói-khuyến-mãi" depending on project terminology, and
"combos" -> plural form) so all new combo-related keys ("This will permanently
delete combo", "combo-name", etc.) have Vietnamese values consistent with
existing entries like "combo" -> "combo" or the chosen translation and
"combo-name" -> "tên-combo".

In `@web/default/src/i18n/locales/zh.json`:
- Line 4558: The zh.json entry with key "This will permanently delete" currently
keeps the English text as its value; update the value to a proper Chinese
translation (for example: "此操作将永久删除") so the delete confirmation is fully
localized; locate the JSON key "This will permanently delete" in the zh locale
and replace the right-hand string with the Chinese translation, keeping the
surrounding JSON syntax intact.

---

Nitpick comments:
In `@controller/combo.go`:
- Line 48: Redundant assignment: remove the manual setting of combo.CreatedTime
(the line assigning combo.CreatedTime = time.Now().Unix()) since combo.Insert()
already initializes CreatedTime internally; update the controller method that
calls combo.Insert() (referencing combo.CreatedTime and the combo.Insert() call)
to delete that assignment and rely on the model implementation in model/combo.go
to set CreatedTime.
- Line 34: Extract the inline strategy check into a package-level
allowedStrategies slice and a helper isValidStrategy(s string) bool, then
replace the long condition that reads combo.Strategy != "fallback" && ... with a
call to isValidStrategy(combo.Strategy); ensure allowedStrategies contains
"fallback", "random", "weighted", "round_robin" and update the validation logic
in the function/method that reads combo.Strategy to use isValidStrategy.

In `@web/default/src/features/combos/components/combos-columns.tsx`:
- Around line 54-96: Update the hardcoded flat i18n keys used in the
DataTableColumnHeader calls to hierarchical, semantically-scoped keys; replace
t('Name'), t('Models'), t('Strategy'), t('Status'), and t('Created At') with
keys like t('combos.table.name'), t('combos.table.models'),
t('combos.table.strategy'), t('combos.table.status'), and
t('combos.table.createdAt') respectively in the combos-columns component where
DataTableColumnHeader is rendered (refer to the column definitions using
accessorKey 'name', 'models', 'strategy', 'status', 'created_time' and the
StrategyCell/StatusCell usages) so translations are organized and unambiguous.
- Around line 98-102: The date cell renderer in combos-columns.tsx (cell: ({ row
}) => { ... }) uses toLocaleString() without specifying a locale; update the
cell implementation to retrieve the app's current i18n locale (e.g., from
i18n.language or the app's locale context) and pass it as the first argument to
Date.prototype.toLocaleString so the created_time display respects the user's
selected language/locale; ensure you still handle null/undefined created_time
and convert the epoch seconds (time * 1000) before formatting.

In `@web/default/src/features/combos/components/combos-multi-delete-dialog.tsx`:
- Around line 36-43: The component CombosMultiDeleteDialog currently
destructures props (open, onOpenChange, selectedIds); change it to use the props
object instead to follow guidelines—replace destructuring in the function
signature and all internal references so they read props.open,
props.onOpenChange, and props.selectedIds, ensuring the prop type annotation
remains intact for the function parameter.
- Around line 49-61: The handleDelete function currently uses Promise.all so any
single deleteCombo(id) rejection shows a generic error; change it to use
Promise.allSettled(selectedIds.map(id => deleteCombo(id))) in handleDelete, then
count fulfilled vs rejected results, show a toast reflecting full success,
partial success (e.g., "X deleted, Y failed"), or full failure, and optionally
include/log the failed ids; still call triggerRefresh() after successful
deletions and setIsDeleting(false) in finally, and keep invoking
onOpenChange(false) only when at least one delete succeeded (or per your UX
decision).

In `@web/default/src/features/combos/components/combos-provider.tsx`:
- Line 35: The CombosProvider component currently destructures children in its
parameter list; change the component to accept a single props parameter (e.g.,
props: { children: React.ReactNode }) and update the implementation to reference
props.children instead of the destructured children variable so the component
follows the project's non-destructuring props convention (locate the
CombosProvider function declaration and any usage of children inside it and
replace accordingly).

In `@web/default/src/features/combos/components/combos-table.tsx`:
- Around line 90-96: The UI strings in combos-table.tsx are using flat i18n keys
(e.g., t('No Combos Found'), t('Search combos...')) — update the calls to use
hierarchical keys such as t('combos.empty.title'),
t('combos.empty.description'), and t('combos.search.placeholder') in the
emptyTitle, emptyDescription and Input placeholder props (within the
renderToolbar block), and ensure corresponding entries are added/updated in the
translation resource files for all supported locales so the new keys resolve at
runtime.
🪄 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: 70c3b5cb-9271-461c-a6a7-e842b1d5edb2

📥 Commits

Reviewing files that changed from the base of the PR and between 6f41542 and 7d09293.

📒 Files selected for processing (31)
  • constant/context_key.go
  • controller/combo.go
  • docs/design/combo-feature.md
  • middleware/distributor.go
  • model/combo.go
  • model/main.go
  • router/api-router.go
  • service/combo_routing.go
  • web/default/src/features/combos/api.ts
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/features/combos/components/combos-columns.tsx
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/components/combos-table.tsx
  • web/default/src/features/combos/constants.ts
  • web/default/src/features/combos/index.tsx
  • web/default/src/features/combos/types.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/routeTree.gen.ts
  • web/default/src/routes/_authenticated/combos/index.tsx

Comment thread model/combo.go
Comment thread model/main.go
Comment thread service/combo_routing.go Outdated
Comment thread service/combo_routing.go Outdated
Comment thread web/default/src/features/combos/components/combos-cells.tsx
Comment thread web/default/src/i18n/locales/ja.json Outdated
Comment thread web/default/src/i18n/locales/ja.json Outdated
Comment thread web/default/src/i18n/locales/ru.json Outdated
Comment thread web/default/src/i18n/locales/vi.json Outdated
Comment thread web/default/src/i18n/locales/zh.json Outdated
@androw
androw force-pushed the combo branch 2 times, most recently from 705a47e to 1d24954 Compare June 11, 2026 19:51

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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/hooks/use-sidebar-data.ts`:
- Around line 91-95: The Combos nav item (title: "Combos", url: "/combos", icon:
GitMerge) is currently placed in the general nav group in use-sidebar-data.ts
but is configured as an admin module in use-sidebar-config.ts (mapped to
section: 'admin', module: 'combo'); remove the Combos entry from the general
group and add the same nav item object into the admin group alongside other
admin items (Channels, Models, Users, Redemption Codes) so the sidebar placement
matches the config.
🪄 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: 66d450bb-8507-4ec6-a366-070a1b8c0694

📥 Commits

Reviewing files that changed from the base of the PR and between 7d09293 and 705a47e.

📒 Files selected for processing (27)
  • controller/combo.go
  • docs/design/combo-feature.md
  • web/default/src/features/combos/api.ts
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/features/combos/components/combos-columns.tsx
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/components/combos-table.tsx
  • web/default/src/features/combos/constants.ts
  • web/default/src/features/combos/index.tsx
  • web/default/src/features/combos/types.ts
  • web/default/src/hooks/use-sidebar-config.ts
  • web/default/src/hooks/use-sidebar-data.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/routeTree.gen.ts
  • web/default/src/routes/_authenticated/combos/index.tsx
✅ Files skipped from review due to trivial changes (3)
  • docs/design/combo-feature.md
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/vi.json
🚧 Files skipped from review as they are similar to previous changes (21)
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-table.tsx
  • web/default/src/features/combos/constants.ts
  • web/default/src/routes/_authenticated/combos/index.tsx
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • web/default/src/i18n/locales/ru.json
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/i18n/locales/fr.json
  • web/default/src/features/combos/api.ts
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-columns.tsx
  • controller/combo.go
  • web/default/src/routeTree.gen.ts
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/types.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/i18n/locales/zh.json

Comment thread web/default/src/hooks/use-sidebar-data.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
web/default/src/features/combos/constants.ts (2)

35-42: ⚡ Quick win

Prefer hierarchical i18n keys for status labels.

The labelKey values use flat English words ('Enabled', 'Disabled') instead of hierarchical keys. As per coding guidelines, i18n key names should be hierarchical and semantically clear (e.g., combos.status.enabled, common.enabled).

♻️ Suggested refactor for hierarchical i18n keys
   [COMBO_STATUS.ENABLED]: {
     variant: 'success',
-    labelKey: 'Enabled',
+    labelKey: 'combos.status.enabled',
   },
   [COMBO_STATUS.DISABLED]: {
     variant: 'neutral',
-    labelKey: 'Disabled',
+    labelKey: 'combos.status.disabled',
   },

Then add corresponding entries to locale JSON files (en.json, etc.):

{
  "combos": {
    "status": {
      "enabled": "Enabled",
      "disabled": "Disabled"
    }
  }
}
🤖 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/combos/constants.ts` around lines 35 - 42, The
status labelKey values in constants.ts use flat English strings; update the
COMBO_STATUS mapping (the [COMBO_STATUS.ENABLED] and [COMBO_STATUS.DISABLED]
entries) to use hierarchical i18n keys such as "combos.status.enabled" and
"combos.status.disabled" instead of "Enabled"/"Disabled", and then add matching
entries under the combos.status tree in your locale JSON files (e.g., en.json)
so the translation lookup for labelKey works correctly.

Source: Coding guidelines


66-69: ⚡ Quick win

Prefer hierarchical i18n keys for strategy labels.

Similar to the status labels, the strategy labelKey values use flat English strings ('Fallback', 'Random', etc.) instead of hierarchical keys. Consider using a pattern like combos.strategy.fallback for consistency with i18n guidelines.

♻️ Suggested refactor for hierarchical strategy keys
 export const COMBO_STRATEGY_OPTIONS = [
-  { value: COMBO_STRATEGIES.FALLBACK, labelKey: 'Fallback' },
-  { value: COMBO_STRATEGIES.RANDOM, labelKey: 'Random' },
-  { value: COMBO_STRATEGIES.WEIGHTED, labelKey: 'Weighted' },
-  { value: COMBO_STRATEGIES.ROUND_ROBIN, labelKey: 'Round Robin' },
+  { value: COMBO_STRATEGIES.FALLBACK, labelKey: 'combos.strategy.fallback' },
+  { value: COMBO_STRATEGIES.RANDOM, labelKey: 'combos.strategy.random' },
+  { value: COMBO_STRATEGIES.WEIGHTED, labelKey: 'combos.strategy.weighted' },
+  { value: COMBO_STRATEGIES.ROUND_ROBIN, labelKey: 'combos.strategy.roundRobin' },
 ]

Then add to locale files:

{
  "combos": {
    "strategy": {
      "fallback": "Fallback",
      "random": "Random",
      "weighted": "Weighted",
      "roundRobin": "Round Robin"
    }
  }
}
🤖 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/combos/constants.ts` around lines 66 - 69, Change
the flat English labelKey strings in the combos strategy options to hierarchical
i18n keys; specifically update the objects that reference COMBO_STRATEGIES (the
array with entries for COMBO_STRATEGIES.FALLBACK, .RANDOM, .WEIGHTED,
.ROUND_ROBIN) to use keys like "combos.strategy.fallback",
"combos.strategy.random", "combos.strategy.weighted", and
"combos.strategy.roundRobin" and add corresponding entries to the locale files
under combos.strategy so the translator lookup works consistently with the
status labels.

Source: Coding guidelines

middleware/distributor.go (1)

46-68: 💤 Low value

Error messages should use i18n for consistency.

The new error messages use hardcoded English strings while existing code in this file (e.g., line 38) uses i18n.T(). For consistency and localization support, consider adding i18n message keys for:

  • Line 47: "Combo name is empty"
  • Line 52: "Combo not found: "+comboName
  • Line 56: "Combo is disabled: "+comboName
  • Line 67: "Combo routing failed: "+comboErr.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 `@middleware/distributor.go` around lines 46 - 68, Replace hardcoded English
error strings in the distributor middleware with i18n calls: use i18n.T(...) for
messages where abortWithOpenAiMessage is invoked when comboName == "", when
model.GetComboByName returns nil/error, when combo.Status != 1, and when
service.ResolveComboModel returns comboErr; keep the dynamic parts (comboName
and comboErr.Error()) by passing them as format args to i18n.T (or using the
project’s i18n interpolation pattern) so calls around abortWithOpenAiMessage(c,
...) use i18n.T("key", comboName) or i18n.T("key", comboErr.Error()); update or
add the corresponding i18n keys for "Combo name is empty", "Combo not found:
%s", "Combo is disabled: %s", and "Combo routing failed: %s" to the translations
resource so messages remain localized.
🤖 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 `@middleware/distributor.go`:
- Around line 72-74: The middleware currently logs but continues when
service.RewriteRequestBodyModel(c, result.ResolvedModel) fails, leaving
modelRequest.Model updated while the request body still contains the original
"combo:..." string; change this to abort the request on rewrite failure: after
service.RewriteRequestBodyModel returns an error, call the request
abort/response helper (e.g., use c.AbortWithStatusJSON or the project's standard
abort function) and return immediately instead of just logging via
common.SysError, ensuring modelRequest.Model and the actual request body remain
consistent for downstream handlers and retries; reference the call site where
RewriteRequestBodyModel is invoked and where modelRequest.Model is set
(result.ResolvedModel) to implement the early return/abort flow.

In `@web/default/src/features/combos/constants.ts`:
- Around line 45-52: Replace the O(n²) derivation in COMBO_STATUS_OPTIONS by
iterating COMBO_STATUSES with Object.entries instead of Object.values + nested
Object.keys.find; specifically, iterate Object.entries(COMBO_STATUSES) and map
each [key, config] to { value: key as `${number}`, label: config.labelKey } so
COMBO_STATUS_OPTIONS is produced in one pass and removes the nested find and
type assertion complexity.

In `@web/default/src/i18n/locales/en.json`:
- Line 4542: Remove the duplicate i18n key that starts with a leading space ("
This action cannot be undone.") and consolidate translations to the correct key
without the leading space ("This action cannot be undone."); specifically delete
the entry with the leading-space key and, if it contains any different
translation value, move that value to the canonical key "This action cannot be
undone." to avoid lookup failures and duplicate keys in en.json.

---

Nitpick comments:
In `@middleware/distributor.go`:
- Around line 46-68: Replace hardcoded English error strings in the distributor
middleware with i18n calls: use i18n.T(...) for messages where
abortWithOpenAiMessage is invoked when comboName == "", when
model.GetComboByName returns nil/error, when combo.Status != 1, and when
service.ResolveComboModel returns comboErr; keep the dynamic parts (comboName
and comboErr.Error()) by passing them as format args to i18n.T (or using the
project’s i18n interpolation pattern) so calls around abortWithOpenAiMessage(c,
...) use i18n.T("key", comboName) or i18n.T("key", comboErr.Error()); update or
add the corresponding i18n keys for "Combo name is empty", "Combo not found:
%s", "Combo is disabled: %s", and "Combo routing failed: %s" to the translations
resource so messages remain localized.

In `@web/default/src/features/combos/constants.ts`:
- Around line 35-42: The status labelKey values in constants.ts use flat English
strings; update the COMBO_STATUS mapping (the [COMBO_STATUS.ENABLED] and
[COMBO_STATUS.DISABLED] entries) to use hierarchical i18n keys such as
"combos.status.enabled" and "combos.status.disabled" instead of
"Enabled"/"Disabled", and then add matching entries under the combos.status tree
in your locale JSON files (e.g., en.json) so the translation lookup for labelKey
works correctly.
- Around line 66-69: Change the flat English labelKey strings in the combos
strategy options to hierarchical i18n keys; specifically update the objects that
reference COMBO_STRATEGIES (the array with entries for
COMBO_STRATEGIES.FALLBACK, .RANDOM, .WEIGHTED, .ROUND_ROBIN) to use keys like
"combos.strategy.fallback", "combos.strategy.random",
"combos.strategy.weighted", and "combos.strategy.roundRobin" and add
corresponding entries to the locale files under combos.strategy so the
translator lookup works consistently with the status labels.
🪄 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: f73971f7-d295-4128-8fae-9fd97d18c93c

📥 Commits

Reviewing files that changed from the base of the PR and between 705a47e and 1d24954.

📒 Files selected for processing (33)
  • constant/context_key.go
  • controller/combo.go
  • docs/design/combo-feature.md
  • middleware/distributor.go
  • model/combo.go
  • model/main.go
  • router/api-router.go
  • service/combo_routing.go
  • web/default/src/features/combos/api.ts
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/features/combos/components/combos-columns.tsx
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/components/combos-table.tsx
  • web/default/src/features/combos/constants.ts
  • web/default/src/features/combos/index.tsx
  • web/default/src/features/combos/types.ts
  • web/default/src/hooks/use-sidebar-config.ts
  • web/default/src/hooks/use-sidebar-data.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/routeTree.gen.ts
  • web/default/src/routes/_authenticated/combos/index.tsx
✅ Files skipped from review due to trivial changes (3)
  • web/default/src/i18n/locales/ja.json
  • web/default/src/hooks/use-sidebar-config.ts
  • web/default/src/routeTree.gen.ts
🚧 Files skipped from review as they are similar to previous changes (25)
  • model/main.go
  • web/default/src/hooks/use-sidebar-data.ts
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/index.tsx
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • constant/context_key.go
  • web/default/src/i18n/locales/vi.json
  • web/default/src/routes/_authenticated/combos/index.tsx
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-columns.tsx
  • web/default/src/i18n/locales/ru.json
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/components/combos-cells.tsx
  • router/api-router.go
  • web/default/src/features/combos/components/combos-table.tsx
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/types.ts
  • controller/combo.go
  • web/default/src/i18n/locales/fr.json
  • model/combo.go
  • web/default/src/features/combos/api.ts
  • service/combo_routing.go

Comment thread middleware/distributor.go
Comment thread web/default/src/features/combos/constants.ts Outdated
Comment thread web/default/src/i18n/locales/en.json Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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/hooks/use-sidebar-config.ts`:
- Line 48: The visibility mapping for '/combos' is wrong:
URL_TO_CONFIG_MAP['/combos'] checks admin.combo while DEFAULT_SIDEBAR_MODULES
enables console.combo, causing isModuleEnabled() (which checks
adminSection[module] === true) to filter it out; fix by making the module key
consistent—either change URL_TO_CONFIG_MAP['/combos'] to check console.combo or
update DEFAULT_SIDEBAR_MODULES to enable admin.combo (and do the same fix at the
other occurrence noted around line 104), ensuring the module name used in
URL_TO_CONFIG_MAP, DEFAULT_SIDEBAR_MODULES, and isModuleEnabled() calls match
exactly.

In `@web/default/src/routes/_authenticated/combos/index.tsx`:
- Around line 24-25: Tighten the pagination schema used in validateSearch by
restricting page and pageSize to positive integers: change the page schema from
z.number().optional().catch(1) to z.number().int().min(1).optional().catch(1)
and change pageSize from z.number().optional().catch(undefined) to
z.number().int().min(1).optional().catch(undefined) so floats, zero and negative
values are rejected while preserving the existing fallbacks.
🪄 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: 47cd8bf7-3559-43b8-b947-16fc452b9ce2

📥 Commits

Reviewing files that changed from the base of the PR and between 1d24954 and 36f5788.

📒 Files selected for processing (33)
  • constant/context_key.go
  • controller/combo.go
  • docs/design/combo-feature.md
  • middleware/distributor.go
  • model/combo.go
  • model/main.go
  • router/api-router.go
  • service/combo_routing.go
  • web/default/src/features/combos/api.ts
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/features/combos/components/combos-columns.tsx
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/components/combos-table.tsx
  • web/default/src/features/combos/constants.ts
  • web/default/src/features/combos/index.tsx
  • web/default/src/features/combos/types.ts
  • web/default/src/hooks/use-sidebar-config.ts
  • web/default/src/hooks/use-sidebar-data.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/routeTree.gen.ts
  • web/default/src/routes/_authenticated/combos/index.tsx
✅ Files skipped from review due to trivial changes (2)
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/zh.json
🚧 Files skipped from review as they are similar to previous changes (27)
  • web/default/src/hooks/use-sidebar-data.ts
  • docs/design/combo-feature.md
  • web/default/src/features/combos/index.tsx
  • model/main.go
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • constant/context_key.go
  • web/default/src/features/combos/constants.ts
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/components/combos-table.tsx
  • web/default/src/i18n/locales/en.json
  • web/default/src/features/combos/types.ts
  • web/default/src/features/combos/components/combos-columns.tsx
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/i18n/locales/ru.json
  • web/default/src/routeTree.gen.ts
  • web/default/src/features/combos/api.ts
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/i18n/locales/vi.json
  • middleware/distributor.go
  • controller/combo.go
  • web/default/src/i18n/locales/ja.json
  • model/combo.go
  • service/combo_routing.go

Comment thread web/default/src/hooks/use-sidebar-config.ts
Comment thread web/default/src/routes/_authenticated/combos/index.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
web/default/src/i18n/locales/fr.json (1)

4617-4617: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

French locale still has an untranslated delete-confirmation fragment.

"This will permanently delete" is still mapped to English, so French UI remains mixed-language in destructive dialogs. Please translate this value in fr.json.

🤖 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/i18n/locales/fr.json` at line 4617, The fr locale key "This
will permanently delete" is still English; update its value in the fr.json
locales to a proper French translation (e.g. "Cela supprimera définitivement")
so destructive dialogs are fully localized, keeping the key unchanged and only
replacing the right-hand string.
🧹 Nitpick comments (3)
web/default/src/features/combos/components/combos-columns.tsx (2)

26-111: ⚡ Quick win

Memoize the columns array to prevent unnecessary table re-renders.

The useCombosColumns hook returns a new columns array on every render. Since the columns depend on the t function from useTranslation, wrap the return value in useMemo with t as a dependency to avoid recreating columns unnecessarily and causing table re-renders.

⚡ Proposed optimization
+import { useMemo } from 'react'
 import { useTranslation } from 'react-i18next'
 import { type ColumnDef } from '`@tanstack/react-table`'
 import { DataTableColumnHeader } from '`@/components/data-table`'
 import { type Combo } from '../types'
 import { CombosRowActions } from './combos-row-actions'
 import { StrategyCell, StatusCell } from './combos-cells'

 export function useCombosColumns(): ColumnDef<Combo>[] {
   const { t } = useTranslation()
-  return [
+  return useMemo(() => [
     {
       id: 'select',
       // ... rest of columns
     },
-  ]
+  ], [t])
 }
🤖 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/combos/components/combos-columns.tsx` around lines
26 - 111, The columns array returned by useCombosColumns is recreated on every
render causing extra table re-renders; wrap the returned array in React's
useMemo and depend on t (from useTranslation) so the columns are memoized and
only recomputed when translations change. Update the useCombosColumns function
to import/use useMemo, move the array into a useMemo callback, and list [t] as
the dependency; keep all column definitions (including identifiers like
'select', accessorKey 'name'/'models'/'strategy'/'status'/'created_time', and
cells using DataTableColumnHeader, StrategyCell, StatusCell, CombosRowActions)
unchanged inside the memoized value.

Source: Coding guidelines


98-102: 💤 Low value

Consider using i18n for date formatting.

toLocaleString() without arguments uses the browser's default locale, which may not align with the application's selected language. Consider using i18n date formatting utilities for consistency.

🌐 Example using i18n date formatting
cell: ({ row }) => {
  const time = row.getValue('created_time') as number | null
  if (!time) return '-'
  return new Intl.DateTimeFormat(i18n.language).format(new Date(time * 1000))
}
🤖 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/combos/components/combos-columns.tsx` around lines
98 - 102, The cell renderer for the created_time column uses
Date.prototype.toLocaleString() which relies on the browser default locale;
update the cell function (the arrow function assigned to cell in
combos-columns.tsx that reads row.getValue('created_time')) to format dates via
the app i18n settings (e.g., use Intl.DateTimeFormat with the current
i18n.language or the project's i18n date helper) so the displayed timestamp
respects the selected locale and formatting conventions; keep the existing null
check (if (!time) return '-') and only replace the toLocaleString() call with
the i18n-aware formatter.

Source: Coding guidelines

web/default/src/features/combos/components/combos-provider.tsx (1)

44-52: ⚡ Quick win

Memoize the provider value to prevent unnecessary re-renders.

The provider value object is recreated on every render, which can cause all consumers to re-render even when the state hasn't changed. As per coding guidelines, useMemo should be used judiciously to reduce unnecessary re-renders.

⚡ Proposed optimization
+import React, { useCallback, useMemo } from 'react'
+
 export function CombosProvider(props: { children: React.ReactNode }) {
   const [open, setOpen] = React.useState<ComboDialogType | null>(null)
   const [currentRow, setCurrentRow] = React.useState<Combo | null>(null)
   const [refreshTrigger, setRefreshTrigger] = React.useState(0)
   const triggerRefresh = useCallback(() => {
     setRefreshTrigger((v) => v + 1)
   }, [])

+  const value = useMemo(
+    () => ({
+      open,
+      setOpen,
+      currentRow,
+      setCurrentRow,
+      refreshTrigger,
+      triggerRefresh,
+    }),
+    [open, currentRow, refreshTrigger, triggerRefresh]
+  )
+
   return (
-    <CombosContext.Provider
-      value={{
-        open,
-        setOpen,
-        currentRow,
-        setCurrentRow,
-        refreshTrigger,
-        triggerRefresh,
-      }}
-    >
+    <CombosContext.Provider value={value}>
       {props.children}
     </CombosContext.Provider>
   )
 }
🤖 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/combos/components/combos-provider.tsx` around lines
44 - 52, The provider value object for CombosContext.Provider is recreated every
render; wrap the value in a useMemo to memoize it and prevent unnecessary
consumer re-renders: import React's useMemo, create a memoizedValue using
useMemo(() => ({ open, setOpen, currentRow, setCurrentRow, refreshTrigger,
triggerRefresh }), [open, setOpen, currentRow, setCurrentRow, refreshTrigger,
triggerRefresh]) and pass memoizedValue to CombosContext.Provider's value prop
(adjust dependencies if any of those setters/handlers are stable and can be
omitted).

Source: Coding guidelines

🤖 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 `@service/combo_routing.go`:
- Around line 190-196: In resolveRoundRobin, the counter is incremented before
computing idx so new combos start at models[1]; change the order so you compute
idx from the current counter and then increment it (i.e., use
comboRoundRobinCounters[combo.Id] % len(models) to pick the model, then
increment comboRoundRobinCounters[combo.Id]) while preserving the mutex
(comboRoundRobinMutex) and returning the selected model in ComboRoutingResult;
this ensures the first request maps to models[0].
- Around line 249-260: replaceModelFieldInJSON currently assumes JSON and
silently returns unchanged bytes on failure; update the flow so
RewriteRequestBodyModel only attempts JSON rewriting when Content-Type is
application/json and treat non-JSON bodies as unsupported for combo rewrite:
modify replaceModelFieldInJSON to return (result []byte, err error) and return a
clear error when the input is not a top-level JSON object or sjson.Set fails,
change RewriteRequestBodyModel to check Content-Type and either call the new
replaceModelFieldInJSON and propagate errors (causing Distribute to reject the
request) or, if you prefer to support forms, implement equivalent rewriting for
form/multipart there; finally update Distribute to handle and surface the error
from RewriteRequestBodyModel instead of proceeding when body rewrite failed
(also ensure getModelFromRequest and common.UnmarshalBodyReusable remain the
source of the model but do not mask rewrite failures).

In `@web/default/src/features/combos/components/combos-columns.tsx`:
- Around line 32-37: Add accessible labels to the selection checkboxes by adding
aria-label attributes: for the header checkbox (the input using
checked={table.getIsAllPageRowsSelected()} and
onChange={table.toggleAllPageRowsSelected(...)}) add aria-label="Select all rows
on page" (or similar), and for each row checkbox (the input in the row
rendering, where row.toggleSelected and row.getIsSelected are used) add
aria-label={`Select row ${row.index + 1}`} or a more specific label using the
row's identifying data; ensure labels are concise and unique for screen readers.

---

Duplicate comments:
In `@web/default/src/i18n/locales/fr.json`:
- Line 4617: The fr locale key "This will permanently delete" is still English;
update its value in the fr.json locales to a proper French translation (e.g.
"Cela supprimera définitivement") so destructive dialogs are fully localized,
keeping the key unchanged and only replacing the right-hand string.

---

Nitpick comments:
In `@web/default/src/features/combos/components/combos-columns.tsx`:
- Around line 26-111: The columns array returned by useCombosColumns is
recreated on every render causing extra table re-renders; wrap the returned
array in React's useMemo and depend on t (from useTranslation) so the columns
are memoized and only recomputed when translations change. Update the
useCombosColumns function to import/use useMemo, move the array into a useMemo
callback, and list [t] as the dependency; keep all column definitions (including
identifiers like 'select', accessorKey
'name'/'models'/'strategy'/'status'/'created_time', and cells using
DataTableColumnHeader, StrategyCell, StatusCell, CombosRowActions) unchanged
inside the memoized value.
- Around line 98-102: The cell renderer for the created_time column uses
Date.prototype.toLocaleString() which relies on the browser default locale;
update the cell function (the arrow function assigned to cell in
combos-columns.tsx that reads row.getValue('created_time')) to format dates via
the app i18n settings (e.g., use Intl.DateTimeFormat with the current
i18n.language or the project's i18n date helper) so the displayed timestamp
respects the selected locale and formatting conventions; keep the existing null
check (if (!time) return '-') and only replace the toLocaleString() call with
the i18n-aware formatter.

In `@web/default/src/features/combos/components/combos-provider.tsx`:
- Around line 44-52: The provider value object for CombosContext.Provider is
recreated every render; wrap the value in a useMemo to memoize it and prevent
unnecessary consumer re-renders: import React's useMemo, create a memoizedValue
using useMemo(() => ({ open, setOpen, currentRow, setCurrentRow, refreshTrigger,
triggerRefresh }), [open, setOpen, currentRow, setCurrentRow, refreshTrigger,
triggerRefresh]) and pass memoizedValue to CombosContext.Provider's value prop
(adjust dependencies if any of those setters/handlers are stable and can be
omitted).
🪄 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: 9a75ace8-9e4c-4ee6-a609-2c78da8ee7d8

📥 Commits

Reviewing files that changed from the base of the PR and between 36f5788 and d5f3861.

📒 Files selected for processing (33)
  • constant/context_key.go
  • controller/combo.go
  • docs/design/combo-feature.md
  • middleware/distributor.go
  • model/combo.go
  • model/main.go
  • router/api-router.go
  • service/combo_routing.go
  • web/default/src/features/combos/api.ts
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/features/combos/components/combos-columns.tsx
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/components/combos-table.tsx
  • web/default/src/features/combos/constants.ts
  • web/default/src/features/combos/index.tsx
  • web/default/src/features/combos/types.ts
  • web/default/src/hooks/use-sidebar-config.ts
  • web/default/src/hooks/use-sidebar-data.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/routeTree.gen.ts
  • web/default/src/routes/_authenticated/combos/index.tsx
✅ Files skipped from review due to trivial changes (6)
  • docs/design/combo-feature.md
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/routeTree.gen.ts
  • web/default/src/i18n/locales/zh.json
🚧 Files skipped from review as they are similar to previous changes (22)
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/index.tsx
  • web/default/src/hooks/use-sidebar-config.ts
  • web/default/src/hooks/use-sidebar-data.ts
  • constant/context_key.go
  • web/default/src/routes/_authenticated/combos/index.tsx
  • model/main.go
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • router/api-router.go
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/i18n/locales/ru.json
  • web/default/src/features/combos/types.ts
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/features/combos/api.ts
  • web/default/src/features/combos/constants.ts
  • controller/combo.go
  • model/combo.go
  • middleware/distributor.go

Comment thread service/combo_routing.go
Comment thread service/combo_routing.go
Comment thread web/default/src/features/combos/components/combos-columns.tsx Outdated
@androw
androw force-pushed the combo branch 4 times, most recently from a69b1f3 to 965b182 Compare June 13, 2026 07:10
androw added 2 commits June 13, 2026 12:11
- New model.Combo struct with Insert/Update/Delete/GetByID/GetByName/GetByUserID/GetAll/Search methods
- New controller with 6 handlers: CreateCombo, GetComboList, GetCombo, UpdateCombo, DeleteCombo, SearchCombos
- Auto-migration via DB.AutoMigrate(&Combo{})
- Routes under /api/combo/ with UserAuth middleware
- Design doc at docs/design/combo-feature.md

Combo bundles multiple models with a routing strategy (fallback/random/weighted/round_robin).
Users reference a combo via model: "combo:<name>" in requests (Phase 2).
- Detect model: "combo:<name>" in Distribute() and resolve
  via service.ResolveComboModel() per strategy
- Rewrite request body model field from combo:xxx to real model
- Fallback strategy pre-selects channel, bypasses normal selection
- Non-fallback strategies pass resolved model to normal channel selection
- Store combo metadata in context for downstream logging/billing
- Add Group field to ComboRoutingResult for auto-group communication
@androw
androw marked this pull request as ready for review June 13, 2026 10:24
@androw

androw commented Jun 13, 2026

Copy link
Copy Markdown
Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
middleware/distributor.go (1)

47-75: ⚡ Quick win

User-facing error messages should use i18n.

The combo routing error messages are hardcoded in English, but per coding guidelines, all user-facing text should use i18n. Other error messages in this file use i18n.T(c, ...).

 			if comboName == "" {
-				abortWithOpenAiMessage(c, http.StatusBadRequest, "Combo name is empty")
+				abortWithOpenAiMessage(c, http.StatusBadRequest, i18n.T(c, i18n.MsgComboNameEmpty))
 				return
 			}
 			userId := c.GetInt("id")
 			combo, comboErr := model.GetComboByNameUserId(comboName, userId)
 			if comboErr != nil || combo == nil {
-				abortWithOpenAiMessage(c, http.StatusNotFound, "Combo not found: "+comboName)
+				abortWithOpenAiMessage(c, http.StatusNotFound, i18n.T(c, i18n.MsgComboNotFound, map[string]any{"Name": comboName}))
 				return
 			}
 			if combo.Status != 1 {
-				abortWithOpenAiMessage(c, http.StatusForbidden, "Combo is disabled: "+comboName)
+				abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgComboDisabled, map[string]any{"Name": comboName}))
 				return
 			}
 			...
-				abortWithOpenAiMessage(c, http.StatusServiceUnavailable, "Combo routing failed: "+comboErr.Error(), types.ErrorCodeModelNotFound)
+				abortWithOpenAiMessage(c, http.StatusServiceUnavailable, i18n.T(c, i18n.MsgComboRoutingFailed, map[string]any{"Error": comboErr.Error()}), types.ErrorCodeModelNotFound)
 			...
-				abortWithOpenAiMessage(c, http.StatusInternalServerError, "Failed to rewrite request body")
+				abortWithOpenAiMessage(c, http.StatusInternalServerError, i18n.T(c, i18n.MsgComboRewriteFailed))

This requires adding the corresponding i18n message constants and translations.

🤖 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 `@middleware/distributor.go` around lines 47 - 75, Replace hardcoded English
messages passed to abortWithOpenAiMessage and logged in the combo routing block
by using i18n.T(c, "<key>") calls: update the abortWithOpenAiMessage calls
around ResolveComboModel and RewriteRequestBodyModel (and the "Combo not found"
/ "Combo is disabled" messages earlier in the same block if present) to use new
i18n keys (e.g., "combo.not_found", "combo.disabled", "combo.routing_failed",
"combo.rewrite_failed") instead of literal strings, and add those keys to the
i18n message constants and translation files so the messages are available in
supported locales; keep the same HTTP status and types.ErrorCodeModelNotFound
usage when calling abortWithOpenAiMessage.

Source: Coding guidelines

service/combo_routing.go (1)

156-163: 💤 Low value

Minor: Fix indentation inside the outer if block.

The if err := common.Unmarshal... block (lines 159-162) appears to have inconsistent indentation relative to its enclosing if statement at line 158. This is a cosmetic issue but affects readability.

 func parseWeights(weightsStr string, models []string) map[string]int {
 	parsed := make(map[string]int)
 	if weightsStr != "" && weightsStr != "{}" {
-	if err := common.Unmarshal([]byte(weightsStr), &parsed); err != nil {
-		// Invalid JSON — ignore, all models get default weight
-		parsed = make(map[string]int)
-	}
+		if err := common.Unmarshal([]byte(weightsStr), &parsed); err != nil {
+			// Invalid JSON — ignore, all models get default weight
+			parsed = make(map[string]int)
+		}
 	}
🤖 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 `@service/combo_routing.go` around lines 156 - 163, In parseWeights, fix the
indentation of the inner if block that calls common.Unmarshal so it is
consistently nested under the outer if (weightsStr != "" && weightsStr != "{}");
adjust spacing so the lines starting with "if err := common.Unmarshal..." and
its body (the comment and parsed = make(map[string]int)) align one level deeper
than the outer if, preserving the existing logic and variable names.
🤖 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/combos/components/combos-columns.tsx`:
- Around line 105-107: The null-check for the created_time value incorrectly
uses a falsy check which treats epoch 0 as empty; in the component rendering
logic around row.getValue('created_time') replace the `if (!time)` check with an
explicit null/undefined check (e.g. `if (time == null)` or `if (time === null ||
time === undefined)`) so that valid epoch 0 timestamps are rendered, and keep
returning '-' only when the value is actually missing.
- Around line 37-44: Replace the hard-coded English aria-labels on the Checkbox
JSX with localized strings: import/use useTranslation() in the React component
that renders the Checkbox, add const { t } = useTranslation(), and change
aria-label='Select all' and aria-label='Select row' to
aria-label={t('combos.select_all')} and aria-label={t('combos.select_row')} (or
your project's chosen i18n keys); ensure the keys are added to the locale
resource files. Target the Checkbox elements shown in the combos-columns
component (the header Checkbox and the row cell Checkbox) so both labels go
through t().

In `@web/default/src/i18n/locales/fr.json`:
- Line 4612: The French translation for the key "Round Robin" is incorrect
("Tourniquet"); update the value for the "Round Robin" key in the
locales/fr.json mapping to a routing-accurate term such as "Rotation circulaire"
(or leave it as "Round Robin" if you prefer to keep the technical term
untranslated) so the UI correctly conveys the load-balancing/routing meaning.
- Line 4618: The French translation for the label "Update combo configuration"
removed the entity context and should explicitly include "combo"; update the
value for the "Update combo configuration" key in fr.json to include the word
"combo" (for example "Mettre à jour la configuration du combo" or another
grammatically appropriate variant) so the action remains unambiguous in the
admin UI.

In `@web/default/src/i18n/locales/zh.json`:
- Around line 4621-4623: The zh locale has untranslated values for the keys
"combo" and "combos" — replace the English strings with the proper Chinese
translations consistent with surrounding keys (e.g., change "combo" -> "组合" and
"combos" -> "组合列表" or another context-appropriate Chinese phrase) so all
combo-related labels in the file are localized; update the values for the keys
"combo" and "combos" in the same JSON object where "combo-name" is defined.

---

Nitpick comments:
In `@middleware/distributor.go`:
- Around line 47-75: Replace hardcoded English messages passed to
abortWithOpenAiMessage and logged in the combo routing block by using i18n.T(c,
"<key>") calls: update the abortWithOpenAiMessage calls around ResolveComboModel
and RewriteRequestBodyModel (and the "Combo not found" / "Combo is disabled"
messages earlier in the same block if present) to use new i18n keys (e.g.,
"combo.not_found", "combo.disabled", "combo.routing_failed",
"combo.rewrite_failed") instead of literal strings, and add those keys to the
i18n message constants and translation files so the messages are available in
supported locales; keep the same HTTP status and types.ErrorCodeModelNotFound
usage when calling abortWithOpenAiMessage.

In `@service/combo_routing.go`:
- Around line 156-163: In parseWeights, fix the indentation of the inner if
block that calls common.Unmarshal so it is consistently nested under the outer
if (weightsStr != "" && weightsStr != "{}"); adjust spacing so the lines
starting with "if err := common.Unmarshal..." and its body (the comment and
parsed = make(map[string]int)) align one level deeper than the outer if,
preserving the existing logic and variable names.
🪄 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: 58fa66bc-5f30-4869-8970-c7c802c775ee

📥 Commits

Reviewing files that changed from the base of the PR and between 36f5788 and 4dbf415.

📒 Files selected for processing (33)
  • constant/context_key.go
  • controller/combo.go
  • docs/design/combo-feature.md
  • middleware/distributor.go
  • model/combo.go
  • model/main.go
  • router/api-router.go
  • service/combo_routing.go
  • web/default/src/features/combos/api.ts
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/features/combos/components/combos-columns.tsx
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/components/combos-table.tsx
  • web/default/src/features/combos/constants.ts
  • web/default/src/features/combos/index.tsx
  • web/default/src/features/combos/types.ts
  • web/default/src/hooks/use-sidebar-config.ts
  • web/default/src/hooks/use-sidebar-data.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/routeTree.gen.ts
  • web/default/src/routes/_authenticated/combos/index.tsx
✅ Files skipped from review due to trivial changes (5)
  • docs/design/combo-feature.md
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/routeTree.gen.ts
🚧 Files skipped from review as they are similar to previous changes (20)
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/index.tsx
  • constant/context_key.go
  • model/main.go
  • web/default/src/hooks/use-sidebar-config.ts
  • router/api-router.go
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/constants.ts
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • model/combo.go
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/features/combos/components/combos-table.tsx
  • web/default/src/routes/_authenticated/combos/index.tsx
  • controller/combo.go
  • web/default/src/features/combos/types.ts

Comment thread web/default/src/features/combos/components/combos-columns.tsx Outdated
Comment thread web/default/src/features/combos/components/combos-columns.tsx
Comment thread web/default/src/i18n/locales/fr.json Outdated
Comment thread web/default/src/i18n/locales/fr.json Outdated
Comment thread web/default/src/i18n/locales/zh.json Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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/i18n/locales/ru.json`:
- Around line 4621-4623: The locale entries "combo", "combo-name", and "combos"
are still in English; update their values to Russian equivalents (e.g., "combo"
-> "комбинация", "combo-name" -> "имя комбинации", "combos" -> "комбинации") so
the Russian locale uses consistent translated strings for these keys.
🪄 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: 6f3e785a-30ee-4faf-80b5-957f8a594fe8

📥 Commits

Reviewing files that changed from the base of the PR and between 4dbf415 and 785caa8.

📒 Files selected for processing (27)
  • controller/combo.go
  • docs/design/combo-feature.md
  • web/default/src/features/combos/api.ts
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/features/combos/components/combos-columns.tsx
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/components/combos-table.tsx
  • web/default/src/features/combos/constants.ts
  • web/default/src/features/combos/index.tsx
  • web/default/src/features/combos/types.ts
  • web/default/src/hooks/use-sidebar-config.ts
  • web/default/src/hooks/use-sidebar-data.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/routeTree.gen.ts
  • web/default/src/routes/_authenticated/combos/index.tsx
✅ Files skipped from review due to trivial changes (5)
  • docs/design/combo-feature.md
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/zh.json
🚧 Files skipped from review as they are similar to previous changes (20)
  • web/default/src/features/combos/components/combos-columns.tsx
  • web/default/src/features/combos/components/combos-primary-buttons.tsx
  • web/default/src/features/combos/components/combos-multi-delete-dialog.tsx
  • web/default/src/features/combos/index.tsx
  • web/default/src/features/combos/components/combos-dialogs.tsx
  • web/default/src/hooks/use-sidebar-data.ts
  • web/default/src/hooks/use-sidebar-config.ts
  • web/default/src/features/combos/components/combos-bulk-actions.tsx
  • web/default/src/features/combos/components/combos-cells.tsx
  • web/default/src/features/combos/components/combos-mutate-drawer.tsx
  • web/default/src/features/combos/components/combos-provider.tsx
  • web/default/src/i18n/locales/fr.json
  • web/default/src/features/combos/constants.ts
  • web/default/src/features/combos/components/combos-table.tsx
  • web/default/src/features/combos/components/combos-row-actions.tsx
  • web/default/src/features/combos/types.ts
  • web/default/src/features/combos/api.ts
  • web/default/src/features/combos/components/combos-delete-dialog.tsx
  • controller/combo.go
  • web/default/src/routeTree.gen.ts

Comment thread web/default/src/i18n/locales/ru.json Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant