Skip to content

Add optional moeflow-companion service#38

Closed
jokester wants to merge 27 commits into
moeflow-com:mainfrom
jokester:connect-gradio-companion
Closed

Add optional moeflow-companion service#38
jokester wants to merge 27 commits into
moeflow-com:mainfrom
jokester:connect-gradio-companion

Conversation

@jokester

@jokester jokester commented Aug 30, 2025

Copy link
Copy Markdown
Member

service : https://github.com/moeflow-com/manga-image-translator/tree/moeflow-companion-main/moeflow_companion

Summary by CodeRabbit

  • New Features

    • Added AI-powered “Auto Translate” for project files via Moeflow Companion.
    • Improved target selection: auto-selects single/default target and streamlines import status flow.
    • Added loading indicator when project context isn’t ready.
  • Documentation

    • Added a sample runtime configuration file for Moeflow Companion.
  • Localization

    • Updated and reorganized translation keys; refined copy (e.g., “Fit Width/Height”, selection actions).
  • Refactor

    • Reorganized components and runtime configuration initialization.
  • Chores

    • Updated dependency range for Gradio client.
    • Adjusted ignore settings and added locale generation Makefile tasks.

@coderabbitai

coderabbitai Bot commented Aug 30, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Introduces runtime-configurable “Moeflow Companion” integration via @gradio/client, adds hooks and UI to AI-translate files, restructures component exports/paths, updates Redux to carry runtimeConfig, adjusts API initialization/headers, revises locales/keys, adds Make targets for locale JSON, and performs broad import/path refactors.

Changes

Cohort / File(s) Summary
Runtime config & bootstrap
public/moeflow-runtime-config.sample.json, src/configs.tsx, src/index.tsx, .gitignore
Adds sample runtime config with moeflowCompanion options; exports RuntimeConfig; dispatches runtimeConfig at app mount; ignores generated runtime config in git.
HTTP API init & tokens
src/apis/index.ts, src/store/user/sagas.ts
Reworks lazy axios instance to read token from store and set Authorization in dev; comments out 401 auto-clear; minor debug comment.
Redux: site state
src/store/site/slice.ts
Makes osName/platform required; adds runtimeConfig to SiteState; adds setRuntimeConfig action.
Moeflow Companion feature
src/services/moeflow_companion/use_moeflow_companion.ts, src/components/project/FileListAiTranslate.tsx, src/components/project-file/ImageViewer.tsx, src/services/moeflow_companion/TranslateCompanion.tsx, src/services/moeflow_companion/mit_preprocess.ts, src/services/moeflow_companion/BatchProcessModal.ts
Adds companion connection hook using @gradio/client, multimodal translate pipeline, AI translate modal/flow for file lists, optional Client prop on ImageViewer, and related import path adjustments.
Pages: files/translator flow
src/pages/ProjectFiles.tsx, src/pages/ImageTranslator.tsx
Refactors ProjectFiles render and target selection; adds wrapper/modals; integrates AI translate entry; extracts hotkeys/window-size to hooks in ImageTranslator.
Components: project file/list
src/components/project/FileList.tsx, src/components/project/FileItem.tsx, src/components/project/FileUploadProgress.tsx, src/components/project-file/ImageSelect.tsx, src/components/project-file/MovableLabel.tsx
Rebuilds FileList paging/upload handlers, routes, AI translate gating; centralizes styles in FileItem; updates imports; adds companion service usage in ImageSelect and shared imports in MovableLabel.
Components export surface reorg
src/components/index.ts, src/components/shared/*, src/components/shared-form/*, src/components/shared-member/*, src/components/project/*, src/components/project-set/*, src/components/team/*, src/components/dashboard/DashboardMenu.tsx, src/components/setting/*, src/components/admin/*, src/components/unused/*
Moves many exports to shared/project/team subdirs; adjusts import paths to @ aliases; removes several exports (e.g., FileCover, FileItem root, Output, ProjectItem). No logic changes.
Localization
src/locales/messages.yaml, src/locales/en.json, src/locales/zh-cn.json, Makefile
Renames keys from site.* to project*/project.*; adds AI translate strings; tweaks UI texts; Make targets to generate locale JSON from YAML.
Interfaces & shared types
src/components/shared/List.tsx, src/interfaces/file.ts
Adds ListPageSpec type; File interface gets uploading/uploadOverwrite and notes on uploadState.
Sagas typing tweaks
src/store/project/sagas.ts, src/store/projectSet/sagas.ts
Annotates catch clauses as any; adds explicit select type in projectSet saga.
Routing/usages
src/pages/ProjectSet.tsx, src/pages/Team.tsx, src/pages/Project.tsx, src/pages/Dashboard.tsx, src/pages/Login.tsx, src/pages/ProjectSetSetting.tsx, src/components/project/ProjectCreateForm.tsx, src/components/project/ProjectImportForm.tsx, src/components/project/ProjectSettingTarget.tsx, src/components/project/Output*.tsx, src/components/project-list/*, src/pages/Team*.tsx
Updates imports, i18n keys, and api usage namespaces; no major logic changes beyond text/paths.
Tooling & ignore
.dockerignore, package.json
Stops ignoring local .env.* files for Docker context; loosens @gradio/client to ^1.14.0.
Style typing
src/style.ts
Annotates default export as const (readonly type).
Misc slice addition
src/store/source/slice.ts
Adds empty exported interface Singl.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App as App (React)
  participant Store as Redux Store
  participant Config as runtimeConfig (loader)
  participant API as Axios Instance

  User->>App: Load application
  App->>Config: await runtimeConfig
  Config-->>App: RuntimeConfig { baseURL, moeflowCompanion, ... }
  App->>Store: dispatch setRuntimeConfig(RuntimeConfig)
  App->>API: Lazy init axios with baseURL, Authorization (dev if token present)
  Note over API: 401 handler no longer clears token by default
Loading
sequenceDiagram
  autonumber
  actor User
  participant FileList as FileList (UI)
  participant Hook as useMoeflowCompanion
  participant Companion as @gradio/client Client
  participant Backend as Moeflow Companion
  participant API as App Backend

  User->>FileList: Click "Auto Translate"
  FileList->>Hook: useMoeflowCompanion()
  Hook->>Companion: connect(gradioUrl)
  alt connect ok
    Companion-->>Hook: connected
    Hook-->>FileList: [connected, service]
    FileList->>FileList: Open ModalContent
    loop For each selected file (limited concurrency)
      FileList->>Backend: multimodal_llm_process_files(files, model, target_language)
      Backend-->>FileList: [{ files: TranslatedFile[] }, ...]
      FileList->>API: Create sources/translations per block (throttled)
      API-->>FileList: OK
    end
    FileList->>Companion: close() on cleanup
  else connect fail
    Companion-->>Hook: error
    Hook-->>FileList: [disconnected, null]
    FileList->>FileList: Disable AI translate UI
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Poem

Hop hop! I wired a friend today—
a gradio breeze to guide our way.
Configs whisper at runtime’s door,
Lists now hum and translate more.
Keys reshuffled, paths aligned—
carrot-shaped commits, neatly signed. 🥕✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/configs.tsx (1)

19-29: Fix runtime crash when /moeflow-runtime-config.json is missing or non-JSON

overriden can be null (fetch failure/404/invalid JSON), and spreading null throws at runtime. Also res.ok is not checked, and browser caching can hide updates.

Apply:

-export const runtimeConfig = lazyThenable<RuntimeConfig>(async () => {
-  const overriden: RuntimeConfig = await fetch('/moeflow-runtime-config.json')
-    .then((res) => res.json())
-    .catch(() => null);
-  const merged: RuntimeConfig = {
-    ...{
-      // defaults
-      baseURL: process.env.REACT_APP_BASE_URL || '/api/',
-    },
-    ...overriden,
-  };
+export const runtimeConfig = lazyThenable<RuntimeConfig>(async () => {
+  const override: Partial<RuntimeConfig> | null = await fetch(
+    '/moeflow-runtime-config.json',
+    { cache: 'no-store' },
+  )
+    .then((res) => (res.ok ? res.json() : null))
+    .catch(() => null);
+  const merged: RuntimeConfig = {
+    // defaults
+    baseURL: process.env.REACT_APP_BASE_URL || '/api/',
+    ...(override ?? {}),
+  };
+  // normalize
+  if (!merged.baseURL.endsWith('/')) merged.baseURL += '/';
🧹 Nitpick comments (6)
.gitignore (1)

31-31: Ignore runtime runtime-config: good addition; add quick dev note in README

This avoids accidental commits. Consider adding a short README line: “cp public/moeflow-runtime-config.sample.json public/moeflow-runtime-config.json” for local runs and noting how it's provided in Docker/deploys.

src/store/projectSet/sagas.ts (1)

19-21: Prefer a typed selector over variable annotation

Define a selector to centralize typing and avoid drifting types.

Example outside this hunk:

// src/store/projectSet/selectors.ts
export const selectProjectSets = (state: AppState) => state.projectSet.projectSets;

// usage
const projectSets: ReturnType<typeof selectProjectSets> = yield select(selectProjectSets);
src/store/user/sagas.ts (1)

13-13: Remove commented debug or use the new debug logger consistently

Either drop this line or switch to the createDebugLogger used in index.tsx for consistency.

-  // console.debug('instance', instance);
+
src/configs.tsx (1)

3-10: Clarify moeflowCompanion.gradioUrl expectations

Consider documenting whether gradioUrl must be absolute and whether trailing slashes are significant; future consumers can normalize accordingly.

src/index.tsx (1)

43-43: Avoid top-level await unless your build target guarantees TLA

Top-level await can break older build chains. If unsure, prefer a non-TLA pattern:

-store.dispatch(setRuntimeConfig(await runtimeConfig));
+runtimeConfig.then((cfg) => store.dispatch(setRuntimeConfig(cfg)));
src/store/site/slice.ts (1)

6-11: Prefer explicit “not-yet-loaded” types over non-null assertions

Using null! hides real states. Model them in the type and init accordingly.

-export interface SiteState {
-  osName: OSName;
-  platform: Platform;
-  newInvitationsCount: number;
-  relatedApplicationsCount: number;
-  runtimeConfig: RuntimeConfig;
-}
+export interface SiteState {
+  osName: OSName | null;
+  platform: Platform | undefined;
+  newInvitationsCount: number;
+  relatedApplicationsCount: number;
+  runtimeConfig: RuntimeConfig | null;
+}
 
 const initialState: SiteState = {
-  osName: null!,
-  platform: null!,
+  osName: null,
+  platform: undefined,
   relatedApplicationsCount: 0,
   newInvitationsCount: 0,
-  runtimeConfig: null!,
+  runtimeConfig: null,
 };

Also applies to: 14-19

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8ce4e53 and 4fcf8a4.

📒 Files selected for processing (10)
  • .dockerignore (0 hunks)
  • .gitignore (1 hunks)
  • public/moeflow-runtime-config.sample.json (1 hunks)
  • src/apis/index.ts (1 hunks)
  • src/configs.tsx (2 hunks)
  • src/index.tsx (3 hunks)
  • src/store/project/sagas.ts (1 hunks)
  • src/store/projectSet/sagas.ts (2 hunks)
  • src/store/site/slice.ts (3 hunks)
  • src/store/user/sagas.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • .dockerignore
🧰 Additional context used
🧬 Code graph analysis (2)
src/store/site/slice.ts (2)
src/interfaces/common.ts (1)
  • Platform (14-14)
src/configs.tsx (1)
  • RuntimeConfig (3-10)
src/index.tsx (3)
src/utils/debug-logger.ts (1)
  • createDebugLogger (3-5)
src/store/site/slice.ts (1)
  • setRuntimeConfig (36-38)
src/configs.tsx (1)
  • runtimeConfig (19-33)
🔇 Additional comments (8)
public/moeflow-runtime-config.sample.json (1)

1-6: Sample looks good; add a hint about cross-origin

If Gradio runs on a different origin, ensure the app fetch logic handles CORS and timeouts. Consider noting “copy to public/moeflow-runtime-config.json” in project docs.

src/apis/index.ts (2)

12-12: LGTM: runtimeConfig import swap is correct

The removal of the unused configs import and keeping runtimeConfig aligns with the new runtime-config flow.


42-46: Axios instance baseURL depends on runtime config — OK once config loading is robust

This usage is fine, but it relies on runtimeConfig never being null/invalid. Please apply the configs.tsx fix I suggested to guarantee a safe fallback.

src/index.tsx (3)

14-14: LGTM: wiring runtime config into site slice

Imports look correct and match the new store action and config export.

Also applies to: 18-18


26-27: LGTM: lightweight startup logger

Non-intrusive and useful for initial state troubleshooting.


64-64: LGTM: initial state logging after i18n init

This executes after early dispatches; order looks fine.

src/store/site/slice.ts (2)

36-38: LGTM: reducer for runtimeConfig

Simple assignment is correct; immutability is handled by RTK.


47-47: Exporting setRuntimeConfig is correct

Matches usage in index.tsx.

Comment thread src/store/project/sagas.ts
Comment on lines +38 to 39
} catch (error: any) {
error.default();

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.

🛠️ Refactor suggestion

Don’t widen to any; guard error.default() to prevent crash paths

Calling default on unknown errors can throw. Use unknown and guard.

-    } catch (error: any) {
-      error.default();
+    } catch (error: unknown) {
+      if (typeof (error as any)?.default === 'function') {
+        (error as any).default();
+      } else {
+        // TODO: route to centralized error handler
+        console.error(error);
+      }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error: any) {
error.default();
} catch (error: unknown) {
if (typeof (error as any)?.default === 'function') {
(error as any).default();
} else {
// TODO: route to centralized error handler
console.error(error);
}
}
🤖 Prompt for AI Agents
In src/store/projectSet/sagas.ts around lines 38 to 39, the catch currently
types the error as any and calls error.default() unguarded which can throw;
change the catch to use error: unknown (or keep unknown-compatible typing) and
before invoking default verify it exists and is callable (e.g., check that error
is non-null and that (error as any).default is a function) and only then call
it; otherwise handle the fallback (log or ignore) to avoid crash paths.

@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

🧹 Nitpick comments (2)
src/index.tsx (2)

61-63: Optional: load i18n and runtime config in parallel to reduce TTI.

These two are independent; batching with Promise.all saves a network RTT on cold starts.

Apply this diff:

-  store.dispatch(setRuntimeConfig(await runtimeConfig));
-  const { intlMessages, locale, antdLocale, antdValidateMessages } =
-    await initI18n;
+  const [cfg, { intlMessages, locale, antdLocale, antdValidateMessages }] =
+    await Promise.all([runtimeConfig, initI18n]);
+  store.dispatch(setRuntimeConfig(cfg));

64-64: Use %O to pretty-print the store state.

debug formats objects best with %O; passing as a second arg may render [object Object].

Apply this diff:

-  debugLogger('initial state', store.getState());
+  debugLogger('initial state %O', store.getState());
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4fcf8a4 and e399d85.

📒 Files selected for processing (1)
  • src/index.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/index.tsx (4)
src/utils/debug-logger.ts (1)
  • createDebugLogger (3-5)
src/store/site/slice.ts (1)
  • setRuntimeConfig (36-38)
src/configs.tsx (1)
  • runtimeConfig (19-33)
src/locales/index.ts (1)
  • initI18n (225-235)
🔇 Additional comments (3)
src/index.tsx (3)

14-14: LGTM: wiring runtime config into the site slice.

Import looks correct and matches the new action in site/slice.


18-18: Importing the Thenable runtimeConfig is fine.

Using await runtimeConfig later matches the lazy-thenable pattern.


26-27: Scoped debug logger is a good addition.

Nice namespacing with moeflow:app.

Comment thread src/index.tsx

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/services/moeflow_companion/mit_preprocess.ts (2)

49-63: Unbounded polling + unsafe non-null assertion can hang or crash.

  • Infinite loop without timeout/abort.
  • r.data.result! may be undefined at runtime.

Apply:

-async function waitImgTask<Result>(taskId: string) {
-  while (true) {
+async function waitImgTask<Result>(
+  taskId: string,
+  opts: { maxAttempts?: number; pollMs?: number; signal?: AbortSignal } = {},
+) {
+  const { maxAttempts = 120, pollMs = 2000, signal } = opts;
+  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
+    if (signal?.aborted) throw new Error('task aborted');
     const r = await request<TaskState<Result>>({
       method: 'GET',
       url: `${mitApiPrefix}/image-tasks/${taskId}`,
     });
     if (r.data.status === 'success') {
-      return r.data.result!;
+      if (r.data.result == null) {
+        throw new Error('task succeeded but result missing');
+      }
+      return r.data.result;
     } else if (r.data.status === 'pending') {
-      await wait(2e3);
+      await wait(pollMs);
     } else {
       throw new Error(`task failed: ${r.data.message ?? 'unknown'}`);
     }
-  }
+  }
+  throw new Error('task timeout');
 }

75-89: Mirror timeout/abort/result-guard in translate polling.

Apply:

-async function waitTranslateTask(taskId: string) {
-  while (true) {
+async function waitTranslateTask(
+  taskId: string,
+  opts: { maxAttempts?: number; pollMs?: number; signal?: AbortSignal } = {},
+) {
+  const { maxAttempts = 120, pollMs = 1000, signal } = opts;
+  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
+    if (signal?.aborted) throw new Error('task aborted');
     const r = await request<TaskState<string[]>>({
       method: 'GET',
       url: `${mitApiPrefix}/translate-tasks/${taskId}`,
     });
     if (r.data.status === 'success') {
-      return r.data.result!;
+      if (r.data.result == null) {
+        throw new Error('task succeeded but result missing');
+      }
+      return r.data.result;
     } else if (r.data.status === 'pending') {
-      await wait(1e3);
+      await wait(pollMs);
     } else {
       throw new Error(`task failed: ${r.data.message ?? 'unknown'}`);
     }
-  }
+  }
+  throw new Error('task timeout');
 }
src/services/moeflow_companion/TranslateCompanion.tsx (1)

1-1: Correct Prettier CLI invocation
The suggested pnpm prettier/yarn prettier commands aren’t picking up the locally installed CLI. Use the installed binary instead:

pnpm install
pnpm exec prettier --write "src/**/*.ts" "src/**/*.tsx"

Or add a dedicated script in package.json:

"scripts": {
  "format:write": "prettier --write \"src/**/*.{ts,tsx}\""
}

then run npm run format:write.

🧹 Nitpick comments (18)
src/services/moeflow_companion/use_moeflow_companion.ts (6)

33-36: Expose the “connecting” state to the UI

Set state before initiating the connection so consumers can render a spinner/notice.

   }
   try {
+    setClientState(moeflowCompanionServiceState.connecting);
-    const client = await Client.connect(serviceConf.gradioUrl);
+    const client = await Client.connect(gradioUrl);
     clientRef.current = client;
     setClientState(moeflowCompanionServiceState.connected);

37-37: Avoid stale-ref leakage on cleanup

Clear the ref only if it still points to the same client; also guard close() errors.

-        released.then(() => client.close());
+        released.then(() => {
+          try {
+            if (clientRef.current === client) clientRef.current = null;
+            client.close();
+          } catch {}
+        });

26-45: Guard against setState after effect release (race during reconnect/unmount)

If connect resolves after release, setClientState can run on an unmounted/stale effect. Add a simple cancellation flag.

   useAsyncEffect(
     async (_, released) => {
+      let canceled = false;
+      released.then(() => {
+        canceled = true;
+      });
@@
-      try {
+      try {
         setClientState(moeflowCompanionServiceState.connecting);
-        const client = await Client.connect(gradioUrl);
+        const client = await Client.connect(gradioUrl);
+        if (canceled) {
+          try { client.close(); } catch {}
+          return;
+        }
         clientRef.current = client;
         setClientState(moeflowCompanionServiceState.connected);
         released.then(() => {
           try {
             if (clientRef.current === client) clientRef.current = null;
             client.close();
           } catch {}
         });
       } catch (e) {
+        if (canceled) return;
         logger('error connecting', e, gradioUrl);
         clientRef.current = null;
         setClientState(moeflowCompanionServiceState.disconnected);
       }

39-41: Safer error logging with unknown catch bindings

Handle both Error and non-Error rejections cleanly.

-        logger('error connecting', e, gradioUrl);
+        const err = e instanceof Error ? e.message : String(e);
+        logger('error connecting', err, gradioUrl);

49-50: Remove placeholder export

x() is empty and exported; this is confusing API surface and may trip linters/unused exports checks.

-export async function x(client: Client) {}

26-45: Optional: reconnection strategy and timeouts

Consider retry with exponential backoff on disconnected, and a connect timeout/abort to avoid hanging UIs if the endpoint is down.

If helpful, I can sketch a small backoff helper and wire it here—want me to? Also confirm whether @gradio/client exposes an AbortSignal for Client.connect so we can cancel in-flight attempts.

src/components/FileList.tsx (3)

51-52: Normalize baseURL to avoid double slashes; handle runtimeConfig availability.

If runtimeConfig.baseURL ends with '/', ${baseURL}/v1... yields '//v1...'. Also, guard against runtimeConfig being momentarily undefined at startup.

Apply:

-const runtimeConfig = useSelector((state: AppState) => state.site.runtimeConfig);
-const uploadAPI = `${runtimeConfig.baseURL}/v1/projects/${project.id}/files`;
+const runtimeConfig = useSelector((state: AppState) => state.site.runtimeConfig);
+const baseURL = (runtimeConfig?.baseURL ?? '/api').replace(/\/+$/, '');
+const uploadAPI = `${baseURL}/v1/projects/${project.id}/files`;

77-79: Guard navigation when target is missing.

You dropped optional chaining on target.id. If target is unset, this will throw on click. Add a lightweight guard with a user hint.

-const toTranslator = (file: File) => {
-  history.push(routes.imageTranslator.build(file.id, target.id));
-};
+const toTranslator = (file: File) => {
+  if (!target?.id) {
+    message.warning(formatMessage({ id: 'project.selectTargetFirst' }));
+    return;
+  }
+  history.push(routes.imageTranslator.build(file.id, target.id));
+};

108-108: Prettier nit: fix empty handler spacing (and re-run formatter).

onCancel: () => { } violates formatter rules; change to () => {}. Also run Prettier to resolve the pipeline warning.

-      onCancel: () => { },
+      onCancel: () => {},
src/services/moeflow_companion/mit_preprocess.ts (3)

26-30: Narrow payload type.

object is too loose and undermines type safety.

Apply:

-async function createImgTask(
+async function createImgTask(
   filename: string,
   taskName: 'mit_ocr' | 'mit_detect_text',
-  payload: object,
+  payload: Record<string, unknown>,
 )

65-73: Also narrow payload for translate task.

Apply:

-async function createTranslateTask(payload: object) {
+async function createTranslateTask(payload: Record<string, unknown>) {

5-5: Consider reading API prefix from runtime config.

Hard-coding /v1/mit couples frontend to a fixed path. Prefer sourcing from runtime config to support multiple environments.

src/services/moeflow_companion/TranslateCompanion.tsx (6)

10-10: Import path consistency.

You switched to a relative import for mit_preprocess, while this file still uses alias imports elsewhere. Consider standardizing (all alias or all relative) to avoid path-churn and fragile moves.


69-72: Remove unused running parameter or wire cancellation.

running is never read inside startTranslateFile; dead param adds noise.

Apply:

-async function* startTranslateFile(
-  image: File,
-  running: RefObject<boolean>,
-): AsyncGenerator<{
+async function* startTranslateFile(
+  image: File,
+): AsyncGenerator<{

And update the call site (see Lines 165-169).


165-169: Align call site with signature change.

Apply:

-    for await (const fileProgress of startTranslateFile(image, {
-      current: true,
-    })) {
+    for await (const fileProgress of startTranslateFile(image)) {

283-283: Avoid hard non-null on ref.

Use optional chaining to prevent a rare NPE if the ref isn’t ready.

Apply:

-            filePondRef.current!.removeFiles();
+            filePondRef.current?.removeFiles();

60-66: Hard-coded translator.

translator: 'gpt4' may not be available across environments. Consider sourcing from runtime config or a prop, with a safe fallback, and handle server-side “unsupported translator” errors explicitly.

Can you confirm the companion service supports translator=gpt4 in this deployment?


209-209: Hard-coded external URL in project intro.

Move the POC link to config or omit it in production builds to avoid leaking environment-specific URLs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e399d85 and 0a36d0c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • package.json (1 hunks)
  • src/components/FileList.tsx (5 hunks)
  • src/services/moeflow_companion/TranslateCompanion.tsx (2 hunks)
  • src/services/moeflow_companion/mit_preprocess.ts (1 hunks)
  • src/services/moeflow_companion/use_moeflow_companion.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • package.json
🧰 Additional context used
🧬 Code graph analysis (2)
src/components/FileList.tsx (3)
src/configs.tsx (1)
  • runtimeConfig (19-33)
src/store/index.ts (1)
  • AppState (44-44)
src/pages/routes.ts (1)
  • routes (1-25)
src/services/moeflow_companion/use_moeflow_companion.ts (2)
src/utils/debug-logger.ts (1)
  • createDebugLogger (3-5)
src/store/index.ts (1)
  • AppState (44-44)
🪛 GitHub Actions: Check PR
src/services/moeflow_companion/TranslateCompanion.tsx

[warning] 1-1: Prettier formatting issue detected in this file.

src/components/FileList.tsx

[warning] 1-1: Prettier formatting issue detected in this file.

🔇 Additional comments (4)
src/components/FileList.tsx (2)

27-27: Route builder import LGTM.

Switching to a centralized routes helper improves consistency and avoids hand-crafted paths.


6-6: Automatic JSX runtime enabled
tsconfig.json is set to "jsx": "react-jsx" and no Babel configuration is present, so the automatic JSX runtime is in effect.

src/services/moeflow_companion/mit_preprocess.ts (1)

1-2: Exports present; confirm axios instance consistency
request is exported from src/apis/index.ts:164 and uploadRequest from src/apis/_request.ts:4. Ensure uploadRequest uses the same configured Axios instance (baseURL, interceptors, auth, timeouts) as request.

src/services/moeflow_companion/TranslateCompanion.tsx (1)

251-254: Progress updater tweak LGTM.

Using Math.max avoids regressions on out-of-order completions; updating numPages from total keeps state consistent.

Comment on lines +8 to +14
export const moeflowCompanionServiceState = {
disabled: 'disabled',
connecting: 'connecting',
connected: 'connected',
disconnected: 'disconnected',
} as const;

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.

🛠️ Refactor suggestion

Strengthen typing for state and public API

Use a literal union for service state and annotate hook return for consumers.

 export const moeflowCompanionServiceState = {
   disabled: 'disabled',
   connecting: 'connecting',
   connected: 'connected',
   disconnected: 'disconnected',
 } as const;
 
+export type MoeflowCompanionServiceState =
+  (typeof moeflowCompanionServiceState)[keyof typeof moeflowCompanionServiceState];
+
 const logger = createDebugLogger('service:moeflow_companion');
 
-export function useMoeflowCompanion() {
+export function useMoeflowCompanion(): readonly [
+  MoeflowCompanionServiceState,
+  Client | null
+] {
   const clientRef = useRef<Client | null>(null);
-  const [clientState, setClientState] = useState<string>(
+  const [clientState, setClientState] = useState<MoeflowCompanionServiceState>(
     moeflowCompanionServiceState.disabled,
   );
@@
-  return [clientState, clientRef.current] as const;
+  return [clientState, clientRef.current] as const;

Also applies to: 17-21, 46-46

🤖 Prompt for AI Agents
In src/services/moeflow_companion/use_moeflow_companion.ts around lines 8-14
(and also apply changes at 17-21 and 46), replace the loose string-typed state
object with a properly typed const plus a derived union type and use that union
in the hook's public return type: keep the exported object as a readonly const,
add an exported type alias such as ServiceState = typeof
moeflowCompanionServiceState[keyof typeof moeflowCompanionServiceState] (or
explicit union 'disabled'|'connecting'|'connected'|'disconnected'), then
annotate the hook's return type to use this ServiceState for any state fields
and update any related function params/returns on lines 17-21 and 46 to use that
type so consumers get literal union types instead of plain strings.

Comment on lines +22 to +24
const serviceConf = useSelector(
(s: AppState) => s.site.runtimeConfig.moeflowCompanion,
);

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.

🛠️ Refactor suggestion

Prevent possible runtime crash when runtimeConfig is undefined; depend only on gradioUrl

Selector currently assumes runtimeConfig is always present. Narrowing to gradioUrl avoids undefined access and reduces unnecessary reconnects when unrelated fields change.

Apply:

-  const serviceConf = useSelector(
-    (s: AppState) => s.site.runtimeConfig.moeflowCompanion,
-  );
+  const gradioUrl = useSelector(
+    (s: AppState) => s.site.runtimeConfig?.moeflowCompanion?.gradioUrl,
+  );
@@
-      if (!serviceConf?.gradioUrl) {
+      if (!gradioUrl) {
         clientRef.current = null;
         setClientState(moeflowCompanionServiceState.disabled);
         return;
       }
       try {
-        const client = await Client.connect(serviceConf.gradioUrl);
+        const client = await Client.connect(gradioUrl);
         clientRef.current = client;
         setClientState(moeflowCompanionServiceState.connected);
         released.then(() => client.close());
       } catch (e) {
-        logger('error connecting', e, serviceConf.gradioUrl);
+        logger('error connecting', e, gradioUrl);
         clientRef.current = null;
         setClientState(moeflowCompanionServiceState.disconnected);
       }
@@
-    [serviceConf],
+    [gradioUrl],

Also applies to: 28-32, 34-41, 44-45

@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: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/pages/Team.tsx (1)

60-66: Add missing locale entries

  • projectSet.createProjectSet is missing in src/locales/en.json; add this key (and verify site.projectSet) in both src/locales/en.json and src/locales/zh-cn.json.
src/components/project-file/ImageSelect.tsx (1)

60-83: Avoid leaving loading=true on early return.

When currentProject is falsy, getImages exits with loading stuck true.

   setLoading(true);
-  if (!currentProject) return;
+  if (!currentProject) {
+    setLoading(false);
+    return;
+  }
   api.file
src/components/FileList.tsx (2)

259-266: Avoid mutating items in setState mappers

Mutating item objects breaks referential integrity and can cause stale renders.

-                setItems((items) => {
-                  return items.map((item) => {
-                    if (item.id === file.id && img) {
-                      item.coverUrl = (img as HTMLCanvasElement).toDataURL();
-                    }
-                    return item;
-                  });
-                });
+                setItems((items) =>
+                  items.map((item) =>
+                    item.id === file.id && img
+                      ? { ...item, coverUrl: (img as HTMLCanvasElement).toDataURL() }
+                      : item,
+                  ),
+                );
-          setItems((items) =>
-            items.map((item) => {
-              if (item.id === file.id) {
-                item.uploadPercent = Math.floor(progress * 100);
-              }
-              return item;
-            }),
-          );
+          setItems((items) =>
+            items.map((item) =>
+              item.id === file.id
+                ? { ...item, uploadPercent: Math.floor(progress * 100) }
+                : item,
+            ),
+          );
-            setItems((items) =>
-              items.map((item) => {
-                if (item.id === file.id) {
-                  item.uploadState = 'failure';
-                }
-                return item;
-              }),
-            );
+            setItems((items) =>
+              items.map((item) =>
+                item.id === file.id ? { ...item, uploadState: 'failure' } : item,
+              ),
+            );

Also applies to: 304-311, 337-344


314-333: Harden serverId parsing to prevent runtime crashes

file.serverId may be invalid JSON or unexpected shape; wrap in try/catch and bail gracefully.

-          const result = toLowerCamelCase(JSON.parse(file.serverId) as MFile);
+          let result: MFile | null = null;
+          try {
+            result = toLowerCamelCase(JSON.parse(file.serverId) as MFile);
+          } catch (e) {
+            message.error(formatMessage({ id: 'file.uploadParseError' }));
+            return;
+          }
+          if (!result?.id) {
+            message.error(formatMessage({ id: 'file.uploadInvalidResponse' }));
+            return;
+          }
♻️ Duplicate comments (2)
src/services/moeflow_companion/use_moeflow_companion.ts (2)

8-13: Strengthen typing for state and hook return (same as prior suggestion)

Use a literal union type and annotate the hook’s return.

 export const moeflowCompanionServiceState = {
   disabled: 'disabled',
   connecting: 'connecting',
   connected: 'connected',
   disconnected: 'disconnected',
 } as const;
 
+export type MoeflowCompanionServiceState =
+  (typeof moeflowCompanionServiceState)[keyof typeof moeflowCompanionServiceState];
@@
-export function useMoeflowCompanion() {
+export function useMoeflowCompanion(): readonly [
+  MoeflowCompanionServiceState,
+  Client | null,
+] {
   const clientRef = useRef<Client | null>(null);
-  const [clientState, setClientState] = useState<string>(
-    moeflowCompanionServiceState.connecting,
-  );
+  const [clientState, setClientState] =
+    useState<MoeflowCompanionServiceState>(moeflowCompanionServiceState.disabled);
@@
-  return [clientState, clientRef.current] as const;
+  return [clientState, clientRef.current] as const;

Also applies to: 17-21, 46-46


22-24: Prevent crash on undefined runtimeConfig; limit selector churn; set connecting state during connect

Narrow to gradioUrl and update dependencies accordingly.

-  const serviceConf = useSelector(
-    (s: AppState) => s.site.runtimeConfig.moeflowCompanion,
-  );
+  const gradioUrl = useSelector(
+    (s: AppState) => s.site.runtimeConfig?.moeflowCompanion?.gradioUrl,
+  );
@@
   useAsyncEffect(
     async (_, released) => {
-      if (!serviceConf?.gradioUrl) {
+      if (!gradioUrl) {
         clientRef.current = null;
         setClientState(moeflowCompanionServiceState.disabled);
         return;
       }
       try {
-        const client = await Client.connect(serviceConf.gradioUrl);
+        setClientState(moeflowCompanionServiceState.connecting);
+        const client = await Client.connect(gradioUrl);
         clientRef.current = client;
         setClientState(moeflowCompanionServiceState.connected);
         released.then(() => client.close());
       } catch (e) {
-        debugLogger('error connecting', e, serviceConf.gradioUrl);
+        debugLogger('error connecting', e, gradioUrl);
         clientRef.current = null;
         setClientState(moeflowCompanionServiceState.disconnected);
       }
     },
-    [serviceConf],
+    [gradioUrl],
   );

Also applies to: 26-45

🧹 Nitpick comments (22)
src/components/project-file/ImageViewer.tsx (1)

37-41: Use type-only import for Client and remove unused logger and prop

-import { createDebugLogger } from '@/utils/debug-logger';
-import { Client } from '@gradio/client';
-
-const debugLogger = createDebugLogger('components:project-file:ImageViewer');
+import type { Client } from '@gradio/client';
  • Remove the unused createDebugLogger import and debugLogger instantiation.
  • Either drop the unused companionClient?: Client prop or wire it up where it’s consumed.
Makefile (1)

1-14: Add .PHONY and switch to npx tsx
Add a .PHONY declaration to avoid collisions and replace hard-coded node_modules/.bin/tsx with npx tsx so it works out of the box:

+ .PHONY: locale-json locale-json-watch format

 locale-json: src/locales/en.json src/locales/zh-cn.json

 locale-json-watch:
 	watch make locale-json

 format:
 	npm run format:fix

 src/locales/en.json: src/locales/messages.yaml
-	node_modules/.bin/tsx scripts/generate-locale-json.ts
+	npx tsx scripts/generate-locale-json.ts

 src/locales/zh-cn.json: src/locales/messages.yaml
-	node_modules/.bin/tsx scripts/generate-locale-json.ts
+	npx tsx scripts/generate-locale-json.ts
src/locales/en.json (1)

152-152: Consider title case for consistency: “Create Project Set”.

Current string is “Create project set”. If the rest of the UI prefers Title Case for actions, adjust accordingly.

-  "projectSet.createProjectSet": "Create project set",
+  "projectSet.createProjectSet": "Create Project Set",
src/locales/messages.yaml (1)

458-460: Optional: title case “Create Project Set”.

-  en: Create project set
+  en: Create Project Set
src/components/project-file/ImageSelect.tsx (1)

15-17: Remove unused companion imports (or wire them).

They’re currently unused and may trip lint/CI.

-import {
-  useMoeflowCompanion,
-  moeflowCompanionServiceState,
-} from '@/services/moeflow_companion/use_moeflow_companion';
+// import when integrating companion features here:
+// import { useMoeflowCompanion, moeflowCompanionServiceState } from '@/services/moeflow_companion/use_moeflow_companion';
src/components/project/ProjectTargetList.tsx (3)

56-67: Honor List’s computed pageSize when calling the API.

Hard-coding limit: 100000 defeats pagination and can be wasteful. Pass pageSize through.

Apply:

-        params: {
-          page,
-          limit: 100000,
-          word,
-        },
+        params: {
+          page,
+          limit: pageSize,
+          word,
+        },

70-70: Parse x-pagination-count to a number.

Headers are strings; total is typed as number. Convert safely to avoid type drift.

-        setTotal(result.headers['x-pagination-count']);
+        const totalHeader = result.headers['x-pagination-count'];
+        setTotal(totalHeader ? Number(totalHeader) : result.data.length);

44-55: Reuse ListPageSpec for the onChange payload type.

Keeps types in sync with List.tsx and avoids drift.

-  const handleChange = ({
-    page,
-    pageSize,
-    word,
-    cancelToken,
-  }: {
-    page: number;
-    pageSize: number;
-    word: string;
-    cancelToken: CancelToken;
-  }) => {
+  // Prefer importing the shared type:
+  // import type { ListPageSpec } from '../List';
+  const handleChange = ({
+    page,
+    pageSize,
+    word,
+    cancelToken,
+  }: ListPageSpec) => {
src/components/List.tsx (1)

374-386: Pagination props are consistent with controlled page/pageSize.

Optional: defaultPageSize is redundant when pageSize is controlled; you can remove it.

-          <Pagination
+          <Pagination
             className="List__Pagination"
             size="small"
             defaultCurrent={1}
             current={page}
             onChange={handlePageChange}
-            defaultPageSize={pageSize}
             pageSize={pageSize}
             total={total}
             hideOnSinglePage
             simple
             {...paginationProps}
           />
src/components/project-set/ProjectCreateForm.tsx (1)

274-291: Harden Upload beforeUpload with error handling and resolve path.

Currently only onload path rejects to cancel upload; add onerror and ensure consistent cancellation.

               <Upload
                 accept=".txt"
                 beforeUpload={(file) => {
                   return new Promise((resolve, reject) => {
                     const reader = new FileReader();
-                    reader.onload = function (event) {
+                    reader.onload = function (event) {
                       if (event.target) {
                         const content = event.target.result;
                         if (typeof content === 'string') {
                           setLabelplusTXT(content);
                         }
                       }
-                      reject();
+                      // Prevent actual upload; we've consumed the file
+                      reject();
                     };
+                    reader.onerror = () => {
+                      message.error(formatMessage({ id: 'site.uploadFailed' }));
+                      reject();
+                    };
                     reader.readAsText(file);
                   });
                 }}
               >
src/pages/ProjectFiles.tsx (2)

48-62: Center the spinner container reliably.

Parent div lacks dimensions; center via container styles instead of Spin’s internal css.

-  if (!project) {
-    return (
-      <div>
-        <Spin
-          size="large"
-          css={css`
-            flex: auto;
-            display: flex;
-            justify-content: center;
-            align-items: center;
-          `}
-        />
-      </div>
-    );
-  }
+  if (!project) {
+    return (
+      <div
+        css={css`
+          width: 100%;
+          height: 100%;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+        `}
+      >
+        <Spin size="large" />
+      </div>
+    );
+  }

88-139: Target selector UX looks good; tiny cleanup.

targets is always an array (initially []), so the guard is redundant. Show the header unconditionally.

-      targets && (
-        <ListItem
+      <ListItem
           disabled={true}
           className="Project__ListItemTitle"
           logo={
             !isMobile && (
               <Icon
                 className="ListItem__LogoBoxIcon"
                 icon="language"
                 style={{ height: '28px', width: '28px' }}
               ></Icon>
             )
           }
           name={formatMessage({ id: 'project.selectTarget' })}
-        />
-      ),
+        />,
src/pages/ImageTranslator.tsx (2)

10-20: Consolidate project-file imports.

Minor import tidy-up.

-import { ImageViewer, ImageSourceViewer } from '@/components/project-file';
+import {
+  ImageViewer,
+  ImageSourceViewer,
+  ImageTranslatorSettingMouse,
+  ImageTranslatorSettingHotKey,
+} from '@/components/project-file';
@@
-import { ImageTranslatorSettingMouse } from '@/components/project-file';
-import { ImageTranslatorSettingHotKey } from '@/components/project-file';

311-334: Keep iOS focus listeners in sync with runtime OS changes.

If osName can change at runtime (e.g., hydration), include isIOS in deps and adjust cleanup.

-  useEffect(() => {
+  useEffect(() => {
     const handleResize = () => {
       const width = window.innerWidth;
       const height = window.innerHeight;
       setWindowSize({ width, height });
     };
     handleResize();
     window.addEventListener('resize', handleResize);
     const setTimeoutHandleResize = () => {
       setTimeout(handleResize, 250);
     };
     if (isIOS) {
       window.addEventListener('focusin', setTimeoutHandleResize);
       window.addEventListener('focusout', setTimeoutHandleResize);
     }
     return () => {
       window.removeEventListener('resize', handleResize);
       if (isIOS) {
         window.removeEventListener('focusin', setTimeoutHandleResize);
         window.removeEventListener('focusout', setTimeoutHandleResize);
       }
     };
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, []);
+  }, [isIOS]);
src/components/FileList.tsx (5)

85-87: Encode dynamic route params

If ids contain unsafe URL chars, navigation can break. Encode to be safe.

-  const openInTranslator = (file: MFile) => {
-    history.push(routes.imageTranslator.build(file.id, target.id));
-  };
+  const openInTranslator = (file: MFile) => {
+    history.push(
+      routes.imageTranslator.build(
+        encodeURIComponent(file.id),
+        encodeURIComponent(target.id),
+      ),
+    );
+  };

Also applies to: 482-486


369-379: AI translate button renders with no action

Either wire it to a handler or render disabled/tooltip-only to avoid dead UI.

-        {companionService && (
+        {companionService && (
           <Button
             tooltipProps={{
               overlay: formatMessage({ id: 'fileList.aiTranslateTip' }),
             }}
             icon="robot"
-            // onClick={}
+            onClick={async () => {
+              const selected = items.filter((it) =>
+                selectedFileIds.includes(it.id),
+              );
+              if (selected.length === 0) {
+                message.info(formatMessage({ id: 'fileList.selectFilesFirst' }));
+                return;
+              }
+              await companionService.process(selected);
+            }}
           >
             {formatMessage({ id: 'fileList.aiTranslate' })}
           </Button>
         )}

575-585: Define a proper batch-process hook API and memoize the returned object

Expose a stable, typed API and reuse the connected client.

-function useMoeflowCompanionBatchProcess() {
-  const [serviceState, client] = useMoeflowCompanion();
-
-  if (serviceState !== moeflowCompanionServiceState.connected) {
-    return null;
-  }
-
-  return {
-    async f(files: MFile[]) {},
-  } as const;
-}
+import { useMemo } from 'react';
+import { multimodalTranslate } from '@/services/moeflow_companion/use_moeflow_companion';
+
+type BatchProcess = {
+  process(files: MFile[], opts?: { model?: string; targetLang?: string }): Promise<void>;
+};
+
+function useMoeflowCompanionBatchProcess(): BatchProcess | null {
+  const [serviceState, client] = useMoeflowCompanion();
+  return useMemo(() => {
+    if (serviceState !== moeflowCompanionServiceState.connected || !client) {
+      return null;
+    }
+    return {
+      async process(files: MFile[], opts = {}) {
+        // TODO: map MFile -> File blobs if needed, pull defaults from runtimeConfig
+        await multimodalTranslate(client, [], opts.targetLang ?? 'en', opts.model ?? '');
+      },
+    };
+  }, [serviceState, client]);
+}

70-71: currentPageSpecRef is written but never read

If intentional (future refresh), add a TODO; otherwise remove.


241-353: Optional: hide uploader until token and baseURL are ready

Prevents accidental 401s/invalid endpoints during initial load.

-      <FilePond
+      {token && baseURL && (
+        <FilePond
           ...
-        />
+        />
+      )}
src/services/moeflow_companion/use_moeflow_companion.ts (3)

49-68: Make multimodalTranslate robust and avoid hard-coded project name

Validate response shape, bubble meaningful errors, and parameterize export name. Also export TranslatedFile type for consumers.

-export async function multimodalTranslate(
+export async function multimodalTranslate(
   client: Client,
   files: File[],
   targetLang: string,
-  model: string,
-): Promise<TranslatedFile[]> {
+  model: string,
+  exportProjectName?: string,
+): Promise<TranslatedFile[]> {
@@
-  const predictRes = await client.predict('/multimodal_llm_process_files', {
+  const predictRes = await client.predict('/multimodal_llm_process_files', {
     gradio_temp_files: files, // uploadRes.files!.map(handle_file),
     model,
     target_language: targetLang,
-    export_moeflow_project_name: 'Hello!!',
+    ...(exportProjectName && { export_moeflow_project_name: exportProjectName }),
   });
-  const [{ files: translated }] = predictRes.data as MoeflowMultimodalResData;
+  const data = predictRes.data as unknown;
+  if (
+    !Array.isArray(data) ||
+    !data[0] ||
+    typeof (data[0] as any).files === 'undefined'
+  ) {
+    throw new Error('Unexpected response from moeflow companion');
+  }
+  const [{ files: translated }] = data as MoeflowMultimodalResData;
@@
   return translated;
 }
-interface TranslatedFile {
+export interface TranslatedFile {

Also applies to: 69-88


15-16: Minor: use a consistent logger name

Keep naming consistent with other modules (logger vs debugLogger).

-const debugLogger = createDebugLogger('service:moeflow_companion');
+const logger = createDebugLogger('service:moeflow_companion');

And update references accordingly.


58-64: Optional: expose the endpoint path as a constant

Eases future changes and test stubbing.

+const MULTIMODAL_ENDPOINT = '/multimodal_llm_process_files';
-  const predictRes = await client.predict('/multimodal_llm_process_files', {
+  const predictRes = await client.predict(MULTIMODAL_ENDPOINT, {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0a36d0c and b673879.

📒 Files selected for processing (21)
  • Makefile (1 hunks)
  • public/moeflow-runtime-config.sample.json (1 hunks)
  • src/components/FileList.tsx (14 hunks)
  • src/components/List.tsx (2 hunks)
  • src/components/TeamInsightProjectList.tsx (1 hunks)
  • src/components/project-file/ImageSelect.tsx (2 hunks)
  • src/components/project-file/ImageViewer.tsx (4 hunks)
  • src/components/project-set/ProjectCreateForm.tsx (5 hunks)
  • src/components/project/ProjectTargetList.tsx (6 hunks)
  • src/configs.tsx (2 hunks)
  • src/locales/en.json (6 hunks)
  • src/locales/messages.yaml (7 hunks)
  • src/locales/zh-cn.json (5 hunks)
  • src/pages/ImageTranslator.tsx (3 hunks)
  • src/pages/Project.tsx (1 hunks)
  • src/pages/ProjectFiles.tsx (3 hunks)
  • src/pages/ProjectSet.tsx (4 hunks)
  • src/pages/Team.tsx (2 hunks)
  • src/services/moeflow_companion/TranslateCompanion.tsx (1 hunks)
  • src/services/moeflow_companion/use_moeflow_companion.ts (1 hunks)
  • src/store/source/slice.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • public/moeflow-runtime-config.sample.json
  • src/services/moeflow_companion/TranslateCompanion.tsx
  • src/configs.tsx
🧰 Additional context used
🧬 Code graph analysis (8)
src/components/project-file/ImageSelect.tsx (1)
src/apis/index.ts (1)
  • api (289-313)
src/pages/ProjectFiles.tsx (3)
src/components/project/ProjectTargetList.tsx (1)
  • ProjectTargetList (30-131)
src/utils/storage.ts (3)
  • saveDefaultTargetID (10-20)
  • loadDefaultTargetID (22-32)
  • clearDefaultTargetID (34-44)
src/components/FileList.tsx (1)
  • FileList (43-573)
src/components/project/ProjectTargetList.tsx (3)
src/interfaces/target.ts (1)
  • Target (4-12)
src/apis/index.ts (1)
  • api (289-313)
src/components/index.ts (1)
  • EmptyTip (18-18)
src/components/project-file/ImageViewer.tsx (1)
src/utils/debug-logger.ts (1)
  • createDebugLogger (3-5)
src/components/project-set/ProjectCreateForm.tsx (1)
src/apis/index.ts (1)
  • api (289-313)
src/pages/ImageTranslator.tsx (5)
src/interfaces/common.ts (1)
  • FC (4-6)
src/apis/file.ts (1)
  • GetFileReturn (38-40)
src/store/source/slice.ts (1)
  • focusSource (191-214)
src/store/index.ts (1)
  • AppState (44-44)
src/components/HotKey/hooks/useHotKey.ts (1)
  • useHotKey (13-75)
src/components/FileList.tsx (4)
src/configs.tsx (1)
  • runtimeConfig (20-34)
src/components/List.tsx (1)
  • ListPageSpec (16-21)
src/pages/routes.ts (1)
  • routes (1-25)
src/services/moeflow_companion/use_moeflow_companion.ts (2)
  • useMoeflowCompanion (17-47)
  • moeflowCompanionServiceState (8-13)
src/services/moeflow_companion/use_moeflow_companion.ts (2)
src/utils/debug-logger.ts (1)
  • createDebugLogger (3-5)
src/store/index.ts (1)
  • AppState (44-44)
🪛 Biome (2.1.2)
src/store/source/slice.ts

[error] 51-51: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

🪛 checkmake (0.2.2)
Makefile

[warning] 1-1: Missing required phony target "all"

(minphony)


[warning] 1-1: Missing required phony target "clean"

(minphony)


[warning] 1-1: Missing required phony target "test"

(minphony)

🔇 Additional comments (24)
src/components/project-file/ImageViewer.tsx (1)

675-679: Self-closing ImageViewerSettingPanel — LGTM

No behavior change; cleaner JSX.

src/locales/en.json (1)

5-6: Zoom panel labels look good.

“Fit Width/Height” reads well and matches common UI phrasing.

src/locales/messages.yaml (4)

14-17: Zoom panel English copy LGTM.

“Fit Width/Height” is clearer than prior phrasing.


509-515: Create/Import Project rename looks correct and matches consuming code.


714-717: site.output → project.export rename is consistent.


1002-1009: Selection labels are concise and consistent.

src/locales/zh-cn.json (3)

152-152: 创建项目集:翻译与命名空间一致。


169-171: “创建项目/导入项目”键位迁移正确。


235-235: “导出”从 site → project 命名空间迁移正确。

src/pages/ProjectSet.tsx (2)

63-64: i18n key renames match messages.yaml.

Tooltip/Create/Import titles now use project.* keys; good.

Also applies to: 175-176, 190-191


2-2: React import removal approved
tsconfig.json’s compilerOptions.jsx is set to "react-jsx", so the new JSX transform is enabled and omitting the default React import is safe.

src/components/project-file/ImageSelect.tsx (2)

4-4: Hook-only React import is fine under the new JSX transform.

Same tsconfig caveat as noted elsewhere.


8-9: api import migration looks correct.

Matches the named export in src/apis/index.ts.

src/components/project/ProjectTargetList.tsx (2)

41-41: Rename to targetLangs is clear and consistent.

State naming now reflects the data shape. No concerns.


126-128: Empty state copy wiring looks good.

Renders localized tip directly; concise and readable.

src/components/List.tsx (2)

16-22: Introducing ListPageSpec is a good public contract.

Centralizes the pagination/search/cancelToken payload shape.


26-32: onChange now typed with ListPageSpec—LGTM.

This aligns consumers like ProjectTargetList with a single source of truth for the payload.

src/components/project-set/ProjectCreateForm.tsx (4)

12-12: Switch to api.project namespace is correct.

Matches the new nested API surface in apis/index.ts.


77-83: Project creation flow looks sound.

Dispatches state updates, resets slices, navigates, and surfaces server message. No issues.


115-117: Minor UI polish is fine.

Added margin-right on label improves spacing.


144-145: AntD Form: use requiredMark instead of hideRequiredMark—good modernization.

src/pages/ProjectFiles.tsx (2)

64-87: Wrapper via cloneElement is fine.

Encapsulates layout once and keeps branches lean. No functional issues.


151-166: Change-target handler behavior is sensible.

Clears default when multiple targets exist; shows tip when not. No issues.

src/pages/ImageTranslator.tsx (1)

49-56: New hooks improve readability and cohesion.

Hotkeys and window-size logic extracted cleanly. Nice separation of concerns.

Comment thread src/components/FileList.tsx
Comment on lines +327 to 329
title={formatMessage({ id: 'project.export' })}
key="project"
width="30%"

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.

💡 Verification agent

🧩 Analysis chain

Header i18n key updated to project.export — LGTM

Ensure the new key exists in locales to avoid runtime fallback.


🏁 Script executed:

#!/bin/bash
set -euo pipefail
for f in src/locales/en.json src/locales/zh-cn.json; do
  jq -e '.project.export' "$f" >/dev/null || { echo "Missing project.export in $f"; exit 1; }
done
echo "OK: project.export present"

Length of output: 227


Add missing i18n key project.export to locale files
Add an entry for project.export in src/locales/en.json (and ensure it’s present in src/locales/zh-cn.json) to match the usage in TeamInsightProjectList.tsx and prevent runtime fallbacks.

🤖 Prompt for AI Agents
In src/components/TeamInsightProjectList.tsx around lines 327 to 329, the
component uses formatMessage({ id: 'project.export' }) but the i18n key is
missing from locale files; add "project.export": "<English label>" to
src/locales/en.json and the corresponding translated string to
src/locales/zh-cn.json (or its appropriate value) so the key exists and prevents
runtime fallback; keep key naming consistent and run the app or i18n lint to
verify correct loading.

Comment thread src/locales/en.json Outdated
"fileList.changeMode": "Toggle Display Mode",
"file.parseNotStart": "Auto Mark as Not Started",
"fileList.ocrButtonTip": "Image Auto Tagging",
"fileList.autoTranslate": "Detect marks and Translate",

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.

💡 Verification agent

🧩 Analysis chain

Key mismatch with source-of-truth (messages.yaml) + copy tweak.

JSON adds fileList.autoTranslate, but messages.yaml defines fileList.aiTranslate and fileList.aiTranslateTip. This divergence will cause missing/i18n lookup issues after generation. Also, “Detect marks and Translate” should be sentence case.

Apply this diff (then regen JSON from YAML to avoid hand-edits):

-  "fileList.autoTranslate": "Detect marks and Translate",
+  "fileList.aiTranslate": "Translate with AI",
+  "fileList.aiTranslateTip": "Detect marks and translate",

Run to spot remaining key mismatches:


🏁 Script executed:

#!/bin/bash
rg -n 'fileList\.(autoTranslate|aiTranslate|aiTranslateTip)' src/locales

Length of output: 329


Align locale keys with messages.yaml

  • In src/locales/en.json, replace
    -  "fileList.autoTranslate": "Detect marks and Translate",
    +  "fileList.aiTranslate": "Translate with AI",
    +  "fileList.aiTranslateTip": "Detect marks and translate",
  • Regenerate all locale JSON from messages.yaml to sync fileList.aiTranslate and fileList.aiTranslateTip across every locale.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"fileList.autoTranslate": "Detect marks and Translate",
"fileList.aiTranslate": "Translate with AI",
"fileList.aiTranslateTip": "Detect marks and translate",
🤖 Prompt for AI Agents
In src/locales/en.json around line 266, the locale key and text are out of sync
with messages.yaml: replace the incorrect "fileList.autoTranslate": "Detect
marks and Translate" with the corresponding keys from messages.yaml (ensure keys
are named fileList.aiTranslate and fileList.aiTranslateTip with their correct
message strings), then regenerate all locale JSON files from messages.yaml so
every locale is synchronized with the updated keys and values.

Comment thread src/locales/messages.yaml
Comment thread src/locales/messages.yaml
Comment on lines +990 to 992
zhCn: (可选) 通过 LabalPlus “翻译数据.txt”创建:
en: '(Optional) Import marks from LabelPlus txt:'
site.delete:

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.

⚠️ Potential issue

Typo: “LabalPlus” → “LabelPlus”.

User-facing typo in zhCn copy.

-  zhCn: (可选) 通过 LabalPlus “翻译数据.txt”创建:
+  zhCn: (可选) 通过 LabelPlus “翻译数据.txt”创建:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
zhCn: (可选) 通过 LabalPlus “翻译数据.txt”创建:
en: '(Optional) Import marks from LabelPlus txt:'
site.delete:
zhCn: (可选) 通过 LabelPlus “翻译数据.txt”创建:
en: '(Optional) Import marks from LabelPlus txt:'
site.delete:
🤖 Prompt for AI Agents
In src/locales/messages.yaml around lines 990 to 992, the Chinese copy has a
typo "LabalPlus" that should be "LabelPlus"; update the zhCn string to replace
"LabalPlus" with "LabelPlus" so the user-facing translation reads correctly (可选)
通过 LabelPlus “翻译数据.txt”创建:.

Comment thread src/locales/zh-cn.json Outdated
Comment thread src/locales/zh-cn.json
"output.outputPartialExclude": "导出除了已选中的 {count} 个文件",
"output.invert": "反选",
"project.createViaLabelplus": "通过 LabalPlus “翻译数据.txt”创建:",
"project.createViaLabelplus": "(可选) 通过 LabalPlus “翻译数据.txt”创建:",

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.

⚠️ Potential issue

拼写错误:LabalPlus → LabelPlus。

-  "project.createViaLabelplus": "(可选) 通过 LabalPlus “翻译数据.txt”创建:",
+  "project.createViaLabelplus": "(可选) 通过 LabelPlus “翻译数据.txt”创建:",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"project.createViaLabelplus": "(可选) 通过 LabalPlus “翻译数据.txt”创建:",
"project.createViaLabelplus": "(可选) 通过 LabelPlus “翻译数据.txt”创建:",
🤖 Prompt for AI Agents
In src/locales/zh-cn.json around line 325, the translation string contains a
typo "LabalPlus"; update it to "LabelPlus" so the key
"project.createViaLabelplus" reads "(可选) 通过 LabelPlus “翻译数据.txt”创建:" (fix only
the displayed text, preserve surrounding punctuation and encoding).

Comment on lines +291 to +298
useHotKey(
{
disabled: Boolean(file?.id),
ignoreKeyboardElement: false,
},
() => {},
[file?.id],
);

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.

⚠️ Potential issue

Hotkeys gating inverted: fix disabled condition.

Comment says “ignore hotkeys when ImageViewer not loaded”, but code disables when file is loaded.

   // 快捷键 - 当 ImageViewer 未加载完成是,忽略所有快捷键
   useHotKey(
     {
-      disabled: Boolean(file?.id),
+      disabled: !Boolean(file?.id),
       ignoreKeyboardElement: false,
     },
     () => {},
     [file?.id],
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useHotKey(
{
disabled: Boolean(file?.id),
ignoreKeyboardElement: false,
},
() => {},
[file?.id],
);
useHotKey(
{
disabled: !Boolean(file?.id),
ignoreKeyboardElement: false,
},
() => {},
[file?.id],
);
🤖 Prompt for AI Agents
In src/pages/ImageTranslator.tsx around lines 291-298 the hotkey hook is
inverted: it currently sets disabled: Boolean(file?.id) which disables hotkeys
when a file is loaded, but the intent (per comment) is to ignore hotkeys until
the ImageViewer is loaded — so disable when there is NO file. Change the
condition to disabled: !Boolean(file?.id) (or simply !file?.id) so hotkeys are
disabled until file.id exists; keep ignoreKeyboardElement and the dependency
array as-is.

Comment thread src/pages/Project.tsx
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import { useEffect } from 'react';

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.

💡 Verification agent

🧩 Analysis chain

Remove default React import — confirm JSX runtime config

This relies on the new JSX transform. Please verify tsconfig and ESLint settings.


🏁 Script executed:

#!/bin/bash
set -euo pipefail
jq -e '.compilerOptions.jsx | IN("react-jsx","react-jsxdev")' tsconfig.json >/dev/null || { echo 'tsconfig.compilerOptions.jsx should be "react-jsx" or "react-jsxdev"'; exit 1; }
# Optional: ensure react/react-in-jsx-scope is disabled
rg -nP '"react/react-in-jsx-scope"\s*:\s*\b0\b|off' -g '{.eslintrc*,package.json}' -S || echo 'Note: consider disabling react/react-in-jsx-scope rule'
echo "OK: JSX runtime likely configured"

Length of output: 381


Disable react/react-in-jsx-scope rule in ESLint

tsconfig.json is already using the new JSX runtime ("jsx": "react-jsx"), but the react/react-in-jsx-scope rule is still enabled—add or update in your ESLint config (e.g. .eslintrc.json):

{
  "rules": {
    "react/react-in-jsx-scope": "off"
  }
}
🤖 Prompt for AI Agents
In src/pages/Project.tsx around line 1, ESLint is still enforcing
react/react-in-jsx-scope even though tsconfig uses the new JSX runtime ("jsx":
"react-jsx"); update your ESLint configuration (e.g. .eslintrc.json,
.eslintrc.js, or package.json eslint section) to set the rule
"react/react-in-jsx-scope" to "off" (or add it under rules if missing) so JSX
usage without importing React no longer triggers lint errors.

Comment thread src/store/source/slice.ts
noDebounce?: boolean;
}

export interface Singl {}

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.

💡 Verification agent

❓ Verification inconclusive

Remove stray empty exported interface 'Singl' (likely typo; outside PR scope).

This introduces a dead API surface and fails Biome with lint/suspicious/noEmptyInterface. Unless there’s a concrete type you intended to add here, drop it. If you truly need a placeholder, switch to a type alias with a clarifying comment.

Apply one of the following:

- export interface Singl {}

Or (temporary placeholder, still not recommended without usage):

- export interface Singl {}
+// TODO(jokester): clarify intended shape or remove; placeholder to satisfy noEmptyInterface.
+export type Singl = Record<string, never>;

To verify it’s unused:

Expected: no matches. If none, safe to remove.


🏁 Script executed:

#!/bin/bash
# Find any usages of `Singl` outside its declaration.
rg -nP --type=ts --type=tsx '\bSingl\b' -g '!src/store/source/slice.ts' -C2

Length of output: 105


🏁 Script executed:

#!/bin/bash
# Find any usages of `Singl` outside its declaration in TS/TSX files
rg -nP '\bSingl\b' -g '*.ts' -g '*.tsx' -g '!src/store/source/slice.ts' -C2

Length of output: 77


Remove unused empty exported interface Singl. Verified no references outside its declaration; drop it to eliminate dead API surface and satisfy the noEmptyInterface lint rule.

- export interface Singl {}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface Singl {}
🧰 Tools
🪛 Biome (2.1.2)

[error] 51-51: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

🤖 Prompt for AI Agents
In src/store/source/slice.ts around line 51 there is an exported empty interface
`Singl` which is unused; remove the entire `export interface Singl {}`
declaration to eliminate dead API surface and satisfy the noEmptyInterface lint
rule, and run the linter/build to confirm no references remain.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
src/components/project/ProjectImportForm.tsx (1)

55-66: Await entry.getData(), use TextWriter for text files, and close the ZipReader

  • Replace
    entry.getData?.(writer); project = await writer.getData();
    with
    project = await entry.getData(new zip.TextWriter());
    (and likewise for translations.txt) to avoid racing and extra blob→text conversions.
  • After iterating entries, add await zipReader.close(); to release resources.
  • For image entries, continue using await entry.getData(new zip.BlobWriter());.
src/components/shared/Header.tsx (1)

82-86: Fix target=_blank tabnabbing risk on external link.

Add rel="noopener noreferrer" to the GitHub anchor.

Apply:

-  <a className="login" href="https://github.com/moeflow-com" target="_blank">
+  <a
+    className="login"
+    href="https://github.com/moeflow-com"
+    target="_blank"
+    rel="noopener noreferrer"
+  >
src/components/project-file/MovableLabel.tsx (1)

121-139: useMemo dependencies missing positionType (stale UI bug).

Label won’t re-render when positionType toggles (middle-click). Include all props that affect LabelWithContext.

-  }, [styleTransition, content, children, index, saving]);
+  }, [styleTransition, content, children, index, saving, positionType, status, id]);
src/components/shared/Label.tsx (1)

185-217: Fix invalid CSS for cursor

When allowMove is true, this renders "cursor: ;". Also the false branch includes a stray semicolon inside the value.

Apply:

-          cursor: ${allowMove ? '' : 'not-allowed;'};
+          cursor: ${allowMove ? 'grab' : 'not-allowed'};
src/components/project-list/ProjectList.tsx (1)

100-104: Ensure total is numeric (headers are strings).

x-pagination-count is a string; pass a number to List to avoid pagination bugs.

- setTotal(result.headers['x-pagination-count']);
+ setTotal(Number(result.headers['x-pagination-count']));

(Apply in both user/team branches.)

Also applies to: 130-134

src/components/project/ProjectCreateForm.tsx (1)

72-77: Include projectSetID in the createProject call
The API wrapper createProject({ teamID, data }) doesn’t accept or send projectSetID, so new projects won’t be tied to the intended project‐set. Update the call in ProjectCreateForm.tsx (around lines 72–77) to include projectSetID (and extend CreateProjectData or query params) so the backend associates the project with the correct set.

src/components/index.ts (2)

1-64: Enforce barrel imports for components
External code under src/services and src/pages is deep-importing from @/components/shared/* and @/components/project/*; add the missing exports to src/components/index.ts and update those imports to @/components instead of subfolder paths.


1-64: Remove or update lingering imports of removed components
These files still import components that were removed in this PR—update or delete these imports to prevent build failures:

  • src/components/project-list/ProjectList.tsx:10 — import { ProjectItem } from './ProjectItem';
  • src/components/project/OutputList.tsx:16 — import { Output } from './Output';
  • src/components/project/FileList.tsx:12 — import { FileItem } from './FileItem';
♻️ Duplicate comments (2)
src/locales/zh-cn.json (1)

326-326: Fix typo “LabalPlus” → “LabelPlus”.

User-facing string has a misspelling. Keep the key name unchanged; fix only the value.

Apply:

-  "project.createViaLabelplus": "(可选) 通过 LabalPlus “翻译数据.txt”创建:",
+  "project.createViaLabelplus": "(可选) 通过 LabelPlus “翻译数据.txt”创建:",
src/locales/en.json (1)

266-268: Align copy with messages.yaml; fix sentence case.

Prefer “Translate with AI” and “Detect marks and translate” per prior guidance to keep consistency and sentence case.

Apply:

-  "fileList.aiTranslate": "Auto Translate",
-  "fileList.aiTranslateTip": "Detect marks and Translate",
+  "fileList.aiTranslate": "Translate with AI",
+  "fileList.aiTranslateTip": "Detect marks and translate",

Validate against messages.yaml to prevent drift.

#!/bin/bash
# Compare messages.yaml vs en.json for these IDs
set -euo pipefail
rg -n 'fileList\.(aiTranslate|aiTranslateTip)' src/locales/messages.yaml
rg -n 'fileList\.(aiTranslate|aiTranslateTip)' src/locales/en.json
🧹 Nitpick comments (40)
src/components/project/LanguageSelect.tsx (1)

47-56: Effect deps are fragile; derive disabled options with useMemo.

Depending on options.length and disabledLanguageIDs[0] misses many changes and can stale-disable entries. Derive displayOptions instead of mutating options.

import { useEffect, useMemo, useState } from 'react';

// keep `options` as raw API options
const displayOptions = useMemo(
  () =>
    options.map((o) => ({
      ...o,
      disabled: disabledLanguageIDs.includes(o.value),
    })),
  [options, disabledLanguageIDs]
);

// then in JSX:
<Select options={displayOptions} ... />

Remove the second useEffect.

src/components/project/Output.tsx (1)

94-96: Rename colorDisibled to colorDisabled across the Button component and its usages

The prop is currently defined (src/components/shared/Button.tsx lines 34, 53) and used (e.g. Output.tsx line 95, TeamInsightProjectList.tsx line 359) as colorDisibled. Update the interface, default values, styled CSS, and all JSX props to colorDisabled.

src/components/project-file/ImageViewer.tsx (1)

38-38: Use a type-only import for Client: replace both imports in src/components/project-file/ImageViewer.tsx (lines 38, 76) with

-import { Client } from '@gradio/client';
+import type { Client } from '@gradio/client';

to avoid bundling; @gradio/client@1.14.0 is already declared in package.json.

src/components/shared/ListItem.tsx (1)

128-138: Redundant CSS color declaration in RightButton.

Two color rules are set; the latter always overrides the former. Drop the first for clarity.

 .ListItem__RightButton {
-  color: ${style.textColorSecondary};
   width: 45px;
   height: 45px;
   flex: none;
   margin-left: auto;
   display: flex;
   justify-content: center;
   align-items: center;
   ${onRightButtonClick && clickEffect()};
   color: ${style.textColorSecondaryLightest};
 }
src/components/shared/Button.tsx (1)

6-9: Import path adjustments OK; consider using alias for style for consistency.

Optional: switch to "@/style" like other shared components to avoid brittle relative paths.

-import { Icon } from '..';
+import { Icon } from '..';
@@
-import style from '../../style';
+import style from '@/style';
src/components/shared/Header.tsx (2)

52-55: Prefer SPA navigation via Link to avoid full page reloads.

Swap to for internal routes.

Also applies to: 60-63, 131-145, 168-176


8-9: Unify Dropdown import with alias for consistency.

Import Dropdown from the central components barrel (as done elsewhere) to keep imports consistent.

-import { Dropdown } from './Dropdown';
+import { Dropdown } from '@/components';
src/components/setting/LocalePicker.tsx (1)

24-28: Add accessible name/role for the language trigger.

Improve a11y by making the trigger a button-like control with an accessible label.

-  <Dropdown menu={menuProps}>
-    <div>
-      <Icon icon="language" size="3x" />
-    </div>
-  </Dropdown>
+  <Dropdown menu={menuProps}>
+    <div role="button" aria-label="Change language" tabIndex={0}>
+      <Icon icon="language" size="3x" />
+    </div>
+  </Dropdown>
src/components/shared/Spin.tsx (1)

3-3: Avoid deep type import from antd internals.

Import SpinProps from "antd" to reduce breakage risk across antd versions.

-import { SpinProps as AntdSpinProps } from 'antd/lib/spin';
+import type { SpinProps as AntdSpinProps } from 'antd';
src/components/shared/Movable.tsx (4)

13-15: Use alias paths for consistency.

Match the repo-wide "@/..." convention.

-import { FC } from '../../interfaces';
-import { useStateRef } from '../../hooks';
+import { FC } from '@/interfaces';
+import { useStateRef } from '@/hooks';

54-61: Guard window usage for SSR compatibility.

Top-level access to window breaks SSR. Compute Bowser-derived flags only in browser.

-const browser = Bowser.getParser(window.navigator.userAgent);
-const isSafariForMacOS = browser.satisfies({
-  macos: {
-    safari: '>9',
-  },
-});
-const isIOS = browser.getOSName() === 'iOS';
+let isSafariForMacOS = false;
+let isIOS = false;
+if (typeof window !== 'undefined') {
+  const browser = Bowser.getParser(window.navigator.userAgent);
+  isSafariForMacOS = browser.satisfies({ macos: { safari: '>9' } });
+  isIOS = browser.getOSName() === 'iOS';
+}

660-681: Avoid re-attaching gesture listeners on every render.

Add a dependency array to run once.

-  useEffect(() => {
+  useEffect(() => {
     // 仅为 Safari for macOS 的触摸板双指手势注册 gesture 事件(iOS 上浏览器通过 pointer 事件控制)
     if (isSafariForMacOS) {
       const dom = domRef.current as HTMLElement;
       // 接管本 Item 上的双指缩放手势(必须加上 passive)
       dom.addEventListener('gesturestart', handleGestureStart, {
         passive: false,
       });
       dom.addEventListener('gesturechange', handleGestureChange, {
         passive: false,
       });
       dom.addEventListener('gestureend', handleGestureEnd, {
         passive: false,
       });
       return () => {
         dom.removeEventListener('gesturestart', handleGestureStart);
         dom.removeEventListener('gesturechange', handleGestureChange);
         dom.removeEventListener('gestureend', handleGestureEnd);
       };
     }
-  });
+  }, []);

966-966: Avoid non-null assertion on itemIndex; guard before calling.

Prevents passing undefined when MovableItem is used outside MovableArea.

-      onFocusIndexChange && onFocusIndexChange(itemIndex!);
+      if (itemIndex != null) {
+        onFocusIndexChange?.(itemIndex);
+      }
src/components/project-file/MovableLabel.tsx (1)

96-101: Typo in comment.

“中建单击” → “中键单击”.

src/components/ProjectSetList.tsx (1)

24-24: Unify imports via the barrel to reduce path drift.

Consider re-exporting LIST_ITEM_DEFAULT_HEIGHT from the components barrel and importing from '@/components' for consistency with List/ListItem.

- import { LIST_ITEM_DEFAULT_HEIGHT } from '@/components/shared/ListItem';
+ import { LIST_ITEM_DEFAULT_HEIGHT } from '@/components';

Outside this hunk (barrel):

// src/components/index.ts
export { ListItem, LIST_ITEM_DEFAULT_HEIGHT } from './shared/ListItem';
src/components/project/FileUploadProgress.tsx (1)

59-67: Clamp and default upload percent to avoid NaN/overflow UI.

If uploadPercent is undefined or >100/<0, text/width can look wrong. Clamp and reuse a local percent.

   return (
     <div
@@
-        {file.uploadState === 'uploading' && (
+        {file.uploadState === 'uploading' && (
           <span className="FileUploadProgress__TextProgressItem">
             <Icon
               icon="cloud-upload-alt"
               className="FileUploadProgress__TextProgressItemIcon"
             />
-            {formatMessage({ id: 'file.uploading' })} {file.uploadPercent}%
+            {formatMessage({ id: 'file.uploading' })}{' '}
+            {Math.min(100, Math.max(0, file.uploadPercent ?? 0))}%
           </span>
         )}
@@
-        <div className="FileUploadProgress__Progress">
+        <div className="FileUploadProgress__Progress">
           <div
             className="FileUploadProgress__ProgressLine FileUploadProgress__ProgressUpload"
-            style={{ width: `${file.uploadPercent}%` }}
+            style={{
+              width: `${Math.min(100, Math.max(0, file.uploadPercent ?? 0))}%`,
+            }}
           ></div>
         </div>

Also applies to: 91-94

src/components/project/FileItem.tsx (2)

48-53: Avoid non-null assertion on fileTargetCache.

Guard when hasTarget is true but cache missing to prevent runtime crash.

-  const translatedSourceCount = hasTarget
-    ? file.fileTargetCache!.translatedSourceCount
-    : file.translatedSourceCount;
-  const checkedSourceCount = hasTarget
-    ? file.fileTargetCache!.checkedSourceCount
-    : file.checkedSourceCount;
+  const translatedSourceCount = hasTarget && file.fileTargetCache
+    ? file.fileTargetCache.translatedSourceCount
+    : file.translatedSourceCount;
+  const checkedSourceCount = hasTarget && file.fileTargetCache
+    ? file.fileTargetCache.checkedSourceCount
+    : file.checkedSourceCount;

243-245: Use a single source of truth for “uploading” state.

Elsewhere you key off uploadState; prefer that here too.

-        {file.uploading ? (
+        {file.uploadState === 'uploading' ? (
           <FileUploadProgress file={file} />
         ) : (
src/components/shared/Label.tsx (2)

162-172: Expose content on focus/touch for accessibility

Content only shows on hover for fine pointers. Keyboard focus and touch users won’t see it.

Consider:

         &.Label--focus {
           .Label__Number {
             top: ${numberTop + numberBorderSize}px;
             background-color: rgba(${backgroundRGB}, 1);
             border: ${numberBorderSize}px solid rgba(255, 255, 255, 1);
             box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
           }
           .Label__FocusRing {
             display: block;
           }
+          .Label__ContentWrapper {
+            display: flex;
+          }
         }

Also applies to: 173-184


21-24: Tighten typing of positionType

positionType is typed as number but uses SOURCE_POSITION_TYPE values. Prefer the enum type.

-  positionType?: number;
+  positionType?: typeof SOURCE_POSITION_TYPE[keyof typeof SOURCE_POSITION_TYPE];

Also applies to: 55-56

src/services/moeflow_companion/BatchProcessModal.ts (2)

2-2: Remove unused import

Modal is imported but not used.

-import { Modal } from 'antd';
+// import { Modal } from 'antd';

4-6: Implement a minimal usable modal or drop the stub

Returning null makes this component a dead stub; either implement a thin shell or omit until ready.

Example scaffold (adjust prop names to your flow):

-export const MoeflowCompanionBatchProcessModal: FC = (props: {}) => {
-  return null;
-};
+interface Props {
+  open: boolean;
+  onCancel: () => void;
+  onStart: () => void;
+  busy?: boolean;
+}
+export const MoeflowCompanionBatchProcessModal: FC<Props> = ({
+  open,
+  onCancel,
+  onStart,
+  busy = false,
+}) => {
+  return null; // TODO: render Modal with basic controls when wiring is ready
+};

If you want, I can wire a basic antd Modal with i18n strings and a primary “Start” button.

src/components/project/OutputList.tsx (4)

146-152: Invalid CSS value for justify-content

stretch isn’t a valid value for justify-content; property is ignored.

-        .OutputList__Buttons {
-          display: flex;
-          justify-content: stretch;
+        .OutputList__Buttons {
+          display: flex;
+          justify-content: space-between;
           .Button {
             width: 50%;
           }
         }

183-183: Avoid i18n key for a colon

Using formatMessage({ id: ':' }) is fragile and likely missing. Use a literal or a proper shared key with defaultMessage.

-        {formatMessage({ id: ':' })}
+        :
-          {formatMessage({ id: 'output.otherResource' })}
-          {formatMessage({ id: ':' })}
+          {formatMessage({ id: 'output.otherResource' })}:

Also applies to: 222-222


70-76: Cancel in-flight request on unmount

fetchOutputs calls are cancellable, but the in-flight request on unmount isn’t canceled.

 export const OutputList: FC<OutputListProps> = ({
@@
   const fetchOutputsCancelRef = useRef<Canceler>();
+  useEffect(() => {
+    return () => {
+      fetchOutputsCancelRef.current?.();
+    };
+  }, []);

Also applies to: 128-136, 238-241


375-386: Pagination is controlled; drop default props*

You pass current and pageSize, so defaultCurrent/defaultPageSize are redundant.

-          <Pagination
+          <Pagination
             className="List__Pagination"
             size="small"
-            defaultCurrent={1}
             current={page}
             onChange={handlePageChange}
-            defaultPageSize={pageSize}
             pageSize={pageSize}
             total={total}
             hideOnSinglePage
             simple
             {...paginationProps}
           />
src/components/shared/List.tsx (3)

190-207: Reuse ListPageSpec in handleChange signature

Avoids duplicating the shape; communicates intent better.

-  const handleChange = ({
-    page,
-    pageSize,
-    word,
-  }: {
-    page: number;
-    pageSize: number;
-    word: string;
-  }) => {
+  const handleChange = ({ page, pageSize, word }: Omit<ListPageSpec, 'cancelToken'>) => {
     // 尝试取消上一个请求
     if (cancelRef.current) {
       cancelRef.current();
     }
     // 记录本次请求的 cancel
     const [cancelToken, cancel] = getCancelToken();
     cancelRef.current = cancel;
     return onChange({ page, pageSize, word, cancelToken });
   };

375-386: Pagination is controlled; drop default props*

Same nit as OutputList.

           <Pagination
             className="List__Pagination"
             size="small"
-            defaultCurrent={1}
             current={page}
             onChange={handlePageChange}
-            defaultPageSize={pageSize}
             pageSize={pageSize}
             total={total}
             hideOnSinglePage
             simple
             {...paginationProps}
           />

152-160: Null-ref guard on first mount (low risk)

domRef.current should exist post-mount, but a defensive check avoids crashes in edge cases.

-    const width = (domRef.current as HTMLElement).offsetWidth;
+    const node = domRef.current as HTMLElement | null;
+    if (!node) return;
+    const width = node.offsetWidth;
@@
-      const height = (domRef.current as HTMLElement).offsetHeight;
+      const height = node.offsetHeight;
src/components/project-list/ProjectItem.tsx (1)

15-19: Redundant union type on prop.

project: Project | Project should just be Project.

Apply:

 interface ProjectItemProps {
   from: 'team' | 'user';
-  project: Project | Project;
+  project: Project;
   className?: string;
 }
src/components/project/ProjectCreateForm.tsx (2)

269-289: Use Upload.LIST_IGNORE to skip default upload list.

Rejecting a Promise marks the file as error. Use the v5 pattern to ignore and still read content.

- beforeUpload={(file) => {
-   return new Promise((resolve, reject) => {
-     const reader = new FileReader();
-     reader.onload = function (event) {
-       if (event.target) {
-         const content = event.target.result;
-         if (typeof content === 'string') {
-           setLabelplusTXT(content);
-         }
-       }
-       reject();
-     };
-     reader.readAsText(file);
-   });
-}}
+ beforeUpload={async (file) => {
+   const reader = new FileReader();
+   reader.onload = (event) => {
+     const content = event.target?.result;
+     if (typeof content === 'string') setLabelplusTXT(content);
+   };
+   reader.readAsText(file);
+   return Upload.LIST_IGNORE;
+}}

56-69: Dead modal flow (never opened).

showFinishModal isn’t used; either wire onFinish={showFinishModal} or remove the modal code.

src/components/project/FileList.tsx (7)

72-72: Initialize ref with null.

Prevents transient undefined reads.

- const filePondRef = useRef<FilePond | null>();
+ const filePondRef = useRef<FilePond | null>(null);

125-178: Nice: optimistic placeholder for uploading items.

Preview generation and optimistic insert are solid. Consider a fallback for non-images.

- if (/^image.+/.test(file.file.type)) {
+ if (/^image\//.test(file.file.type)) {
    // ...
- }
+ } else {
+   // non-image: keep placeholder without coverUrl
+ }

306-315: Avoid mutating items in-place during progress.

Return new objects for reliable React updates.

- if (item.id === file.id) {
-   item.uploadPercent = Math.floor(progress * 100);
- }
- return item;
+ return item.id === file.id
+   ? { ...item, uploadPercent: Math.floor(progress * 100) }
+   : item;

317-336: Harden server response parsing.

serverId may be non-JSON under some server configs; guard with try/catch.

- const result = toLowerCamelCase(JSON.parse(file.serverId) as MFile);
+ let parsed: MFile | null = null;
+ try {
+   parsed = JSON.parse(file.serverId) as MFile;
+ } catch {
+   message.error(formatMessage({ id: 'file.uploadParseError' }));
+   return;
+ }
+ const result = toLowerCamelCase(parsed);

338-349: Same here: avoid in-place mutation on error.

- if (file && file.id) {
-   setItems((items) =>
-     items.map((item) => {
-       if (item.id === file.id) {
-         item.uploadState = 'failure';
-       }
-       return item;
-     }),
-   );
- }
+ if (file?.id) {
+   setItems((items) =>
+     items.map((item) =>
+       item.id === file.id ? { ...item, uploadState: 'failure' } : item,
+     ),
+   );
+ }

372-382: AI translate button is rendered but inert.

Wire it to the companion or hide until implemented.

-{companionService && (
-  <Button
-    tooltipProps={{
-      overlay: formatMessage({ id: 'fileList.aiTranslateTip' }),
-    }}
-    icon="robot"
-    // onClick={}
-  >
-    {formatMessage({ id: 'fileList.aiTranslate' })}
-  </Button>
-)}
+{companionService && (
+  <Button
+    tooltipProps={{
+      overlay: formatMessage({ id: 'fileList.aiTranslateTip' }),
+    }}
+    icon="robot"
+    disabled={items.length === 0}
+    onClick={async () => {
+      const selected =
+        selectedFileIds.length > 0
+          ? items.filter((f) => selectedFileIds.includes(f.id))
+          : items.filter((f) => f.uploadState !== 'uploading');
+      await companionService.f(selected);
+    }}
+  >
+    {formatMessage({ id: 'fileList.aiTranslate' })}
+  </Button>
+)}

578-588: Stubbed companion batch-process hook.

Return a no-op only if connected; otherwise the UI should not expose actions. If batching is pending, consider leaving the button disabled or feature-flagged.

src/components/index.ts (1)

26-26: Avoid re-exporting from an 'unused' module.

Re-exporting an unused/experimental component makes it part of the public API surface. Prefer keeping it internal or removing the line.

-export { ImageOCRProgress } from './unused/ImageOCRProgress';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b673879 and 88b2938.

📒 Files selected for processing (49)
  • src/components/ApplicationList.tsx (1 hunks)
  • src/components/AuthLoginedTip.tsx (1 hunks)
  • src/components/CAPTCHAInput.tsx (1 hunks)
  • src/components/InvitationList.tsx (1 hunks)
  • src/components/InviteUser.tsx (1 hunks)
  • src/components/MemberList.tsx (1 hunks)
  • src/components/ProjectSetList.tsx (1 hunks)
  • src/components/TeamInsightProjectList.tsx (2 hunks)
  • src/components/TeamList.tsx (1 hunks)
  • src/components/TeamSearchList.tsx (1 hunks)
  • src/components/TeamSettingBase.tsx (1 hunks)
  • src/components/UserInvitationList.tsx (1 hunks)
  • src/components/index.ts (3 hunks)
  • src/components/project-file/ImageViewer.tsx (5 hunks)
  • src/components/project-file/MovableLabel.tsx (1 hunks)
  • src/components/project-list/ProjectItem.tsx (1 hunks)
  • src/components/project-list/ProjectList.tsx (1 hunks)
  • src/components/project/FileItem.tsx (1 hunks)
  • src/components/project/FileList.tsx (14 hunks)
  • src/components/project/FileUploadProgress.tsx (1 hunks)
  • src/components/project/LanguageSelect.tsx (1 hunks)
  • src/components/project/Output.tsx (1 hunks)
  • src/components/project/OutputList.tsx (1 hunks)
  • src/components/project/ProjectCreateForm.tsx (5 hunks)
  • src/components/project/ProjectImportForm.tsx (1 hunks)
  • src/components/project/ProjectSettingTarget.tsx (1 hunks)
  • src/components/setting/LocalePicker.tsx (1 hunks)
  • src/components/setting/UserBasicSettings.tsx (1 hunks)
  • src/components/shared/Avatar.tsx (1 hunks)
  • src/components/shared/AvatarUpload.tsx (1 hunks)
  • src/components/shared/Button.tsx (1 hunks)
  • src/components/shared/Header.tsx (1 hunks)
  • src/components/shared/Label.tsx (1 hunks)
  • src/components/shared/List.tsx (2 hunks)
  • src/components/shared/ListItem.tsx (1 hunks)
  • src/components/shared/ListSearchInput.tsx (1 hunks)
  • src/components/shared/ListSkeletonItem.tsx (1 hunks)
  • src/components/shared/Movable.tsx (2 hunks)
  • src/components/shared/Spin.tsx (1 hunks)
  • src/components/shared/Tooltip.tsx (1 hunks)
  • src/components/unused/FileCover.tsx (1 hunks)
  • src/components/unused/ImageOCRProgress.tsx (1 hunks)
  • src/locales/en.json (6 hunks)
  • src/locales/messages.yaml (7 hunks)
  • src/locales/zh-cn.json (5 hunks)
  • src/pages/ProjectSet.tsx (5 hunks)
  • src/services/moeflow_companion/BatchProcessModal.ts (1 hunks)
  • src/services/moeflow_companion/TranslateCompanion.tsx (1 hunks)
  • src/style.ts (1 hunks)
✅ Files skipped from review due to trivial changes (4)
  • src/components/UserInvitationList.tsx
  • src/components/shared/ListSearchInput.tsx
  • src/components/MemberList.tsx
  • src/components/TeamList.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/services/moeflow_companion/TranslateCompanion.tsx
  • src/pages/ProjectSet.tsx
  • src/components/TeamInsightProjectList.tsx
  • src/locales/messages.yaml
🧰 Additional context used
🧬 Code graph analysis (4)
src/services/moeflow_companion/BatchProcessModal.ts (1)
src/interfaces/common.ts (1)
  • FC (4-6)
src/components/project/FileList.tsx (4)
src/configs.tsx (1)
  • runtimeConfig (20-34)
src/components/shared/List.tsx (1)
  • ListPageSpec (16-21)
src/pages/routes.ts (1)
  • routes (1-25)
src/services/moeflow_companion/use_moeflow_companion.ts (2)
  • useMoeflowCompanion (17-47)
  • moeflowCompanionServiceState (8-13)
src/components/project-file/ImageViewer.tsx (1)
src/utils/debug-logger.ts (1)
  • createDebugLogger (3-5)
src/components/project/ProjectCreateForm.tsx (1)
src/apis/index.ts (1)
  • api (289-313)
🔇 Additional comments (43)
src/components/shared/Tooltip.tsx (1)

10-10: Alias import switch LGTM.

Importing style via the alias is consistent with the broader refactor; no behavioral changes here.

src/components/project/ProjectImportForm.tsx (1)

3-3: React named import LGTM.

No issues with this import adjustment.

src/components/project/LanguageSelect.tsx (1)

5-5: React named import LGTM.

Matches project-wide import cleanup.

src/components/project/ProjectSettingTarget.tsx (2)

5-5: React named import LGTM.

No behavioral change.


17-17: Local import of LanguageSelect LGTM.

Aligns with file relocation; keeps coupling explicit.

src/components/project/Output.tsx (1)

5-11: Import path updates LGTM.

Consistent with component re-org and shared/Button move.

src/style.ts (1)

101-101: Approve changes: style object is immutable
No direct mutations of style.* detected across .ts/.tsx files; compile-time readonly enforcement confirmed.

src/components/unused/FileCover.tsx (1)

3-4: Path updates look correct.

../../interfaces and ../../style resolve as expected from components/unused. No runtime changes introduced.

src/components/shared/AvatarUpload.tsx (1)

4-4: Import path realignments LGTM.

Moves to central configs/store/interfaces without behavior change.

Also applies to: 10-10, 13-14

src/components/setting/UserBasicSettings.tsx (1)

7-7: Updated AvatarUpload path is correct.

No functional change; component usage remains the same.

src/components/TeamSettingBase.tsx (1)

20-20: Shared import path for AvatarUpload is correct and consistent with the refactor.

No behavioral changes.

src/locales/zh-cn.json (2)

152-152: Key renames/additions look correct and consistent.

  • projectSet.createProjectSet
  • project.createProject / project.importProject
  • project.export
  • fileList.aiTranslate / fileList.aiTranslateTip

All align with the namespace split and new AI actions.

Also applies to: 169-171, 235-235, 266-268


266-268: AI-translate keys synced across locales
Verified that fileList.aiTranslate and fileList.aiTranslateTip exist in messages.yaml and in both zh-cn.json and en.json, with no stray autoTranslate entries.

src/components/project-file/ImageViewer.tsx (3)

21-21: Import path moves to shared/ are good.

Movable and Tooltip now resolve from shared/. Consistent with the repo reorg.

Also applies to: 35-35


675-679: LGTM: self-closing ImageViewerSettingPanel.

No behavior change; keeps JSX tidy.


680-688: No change needed: disibled matches the prop in ImageViewerPagingPanelProps and is intentionally used downstream.

src/locales/en.json (3)

5-6: LGTM: “Fit Width/Height”.


152-152: Namespace renames and selection labels look good.

  • projectSet.createProjectSet
  • project.createProject / project.importProject
  • project.export
  • Select All / Invert selection / Deselect All

Also applies to: 169-171, 235-235, 330-332


326-326: LGTM: LabelPlus wording.

Correct capitalization and product name.

src/components/AuthLoginedTip.tsx (1)

7-7: LGTM: Avatar moved under shared/.

Import path update aligns with shared components reorg.

src/components/ApplicationList.tsx (1)

25-25: LGTM: Spin moved under shared/.

Consistent with other components’ shared/ paths.

src/components/InvitationList.tsx (1)

20-20: Approve Spin import path refactor
All imports now use ./shared/Spin or the @/components/shared/Spin alias and no occurrences of the old from "./Spin" remain.

src/components/shared/ListItem.tsx (1)

5-8: Approve code changes Confirmed src/style.ts exports a default object (line 98), so import style from '@/style' is valid.

src/components/shared/Header.tsx (1)

3-3: Alias import migration looks good.

Import path consolidation to "@/..." improves maintainability and avoids brittle relative paths.

Also applies to: 9-16

src/components/setting/LocalePicker.tsx (1)

1-1: Alias import migration LGTM.

Also applies to: 3-5

src/components/shared/Spin.tsx (1)

5-5: FC import switch to alias is fine.

src/components/project-file/MovableLabel.tsx (1)

9-9: Import path updates to shared components look good.

Also applies to: 16-16

src/components/shared/Avatar.tsx (1)

6-7: Verify @ alias across TS, bundler and tests

  • tsconfig.json defines "paths": { "@/*": ["./src/*"] } but lacks a baseUrl—add "baseUrl": "." or confirm TS resolves it
  • No alias found in webpack/vite/Next config—ensure your bundler is configured to resolve @/src/
  • Add Jest moduleNameMapper entry ^@/(.*)$<rootDir>/src/$1
src/components/project/FileUploadProgress.tsx (1)

2-2: Alias import consolidation LGTM.

Imports now come from '@/…'; matches repo-wide change.

Also applies to: 5-6

src/components/project/FileItem.tsx (1)

10-10: Barrel/alias imports look consistent.

Matches the repo’s move to '@/…' and centralized exports.

Also applies to: 17-17

src/components/shared/ListSkeletonItem.tsx (1)

3-5: Alias import alignment LGTM.

Consistent with shared style/utils centralization.

src/components/shared/Label.tsx (1)

4-14: Alias import migration looks good

Consolidates imports to "@/..." paths and shared Spin. No functional issues spotted.

src/components/project/OutputList.tsx (1)

5-15: React import/runtime + alias migration looks good

src/components/unused/ImageOCRProgress.tsx (2)

4-8: Import path reorg is fine

Aligned with barrel exports and alias scheme.


67-73: antd Tooltip prop name may be outdated

Newer antd uses title instead of overlay. Verify project antd version and adjust if needed.

You can change it if on antd v5:

-        <Tooltip
-          overlay={parseErrorTypeDetailName}
+        <Tooltip
+          title={parseErrorTypeDetailName}
           className="ImageOCRProgress__Icon ImageOCRProgress__ErrorIcon"
         >
src/components/shared/List.tsx (1)

16-21: Good addition: shared ListPageSpec type

Helps downstream consumers (e.g., FileList) and keeps contracts consistent.

src/components/CAPTCHAInput.tsx (1)

7-12: Alias import migration looks good.

Paths resolve to the new barrels; no logic changes.

src/components/project-list/ProjectItem.tsx (1)

6-13: Alias imports cleanup LGTM.

Matches repo-wide migration; no behavioral changes.

src/components/project-list/ProjectList.tsx (1)

9-24: Import reorg to alias barrels is consistent.

No issues spotted.

src/components/project/ProjectCreateForm.tsx (1)

5-8: Component/API import changes look correct.

Barrel usage and named api import align with the new structure.

src/components/index.ts (3)

31-35: LGTM: coherent List re-exports (component + constant).*

Nice that LIST_ITEM_DEFAULT_HEIGHT is exported alongside ListItem; keeps API discoverable.


41-41: Verify resolver handles project-list path consistently.

Hyphenated folder names are fine, but double-check case and path resolution across OS/devcontainers.


5-6: No changes needed—every re-export is a matching named export.

Comment thread src/components/InviteUser.tsx Outdated
Comment on lines 16 to 18
import { Spin } from './shared/Spin';
import { LIST_ITEM_DEFAULT_HEIGHT } from '@/components/shared/ListItem';

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.

💡 Verification agent

🧩 Analysis chain

Spin and ListItem constant imports updated to shared modules.

All good. If migrating fully to antd v5 (Modal/Drawer use open), plan to replace SelectValue with SelectProps['value'] to avoid internal imports.


🏁 Script executed:

#!/bin/bash
# Find internal antd type imports to modernize
rg -nP "from ['\"]/antd/(lib|es)/select['\"]|SelectValue\b" src

Length of output: 564


Replace internal SelectValue imports with SelectProps['value'] across components
Remove import { SelectValue } from 'antd/lib/select' and replace with

import type { SelectProps } from 'antd';

Then update all onChange (and other) type annotations from SelectValue to SelectProps['value'] in:
• src/components/RoleSelect.tsx (line 3 import, line 46 annotation)
• src/components/InvitationList.tsx (line 4 import, line 294 annotation)
• src/components/InviteUser.tsx (line 4 import, line 197 annotation)

🤖 Prompt for AI Agents
In src/components/InviteUser.tsx around lines 16 to 18, replace the internal
SelectValue import with a typed import from antd and update usages: remove any
import of SelectValue, add import type { SelectProps } from 'antd'; then change
the onChange (and any other) type annotations that currently use SelectValue to
use SelectProps['value'] (specifically update the annotation at ~line 197). Also
ensure similar changes are applied in RoleSelect.tsx and InvitationList.tsx as
noted in the review (remove SelectValue import, add import type { SelectProps }
from 'antd', and replace SelectValue annotations with SelectProps['value']).

Comment thread src/components/project/FileList.tsx Outdated
Comment on lines +58 to 62
const runtimeConfig = useSelector(
(state: AppState) => state.site.runtimeConfig,
);
const uploadAPI = `${runtimeConfig.baseURL}/v1/projects/${project.id}/files`;
const token = useSelector((state: AppState) => state.user.token);

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.

⚠️ Potential issue

Double-slash risk in upload URL.

runtimeConfig.baseURL defaults to /api/; interpolating /${...} yields /api//v1/.... Some proxies don’t normalize this.

- const uploadAPI = `${runtimeConfig.baseURL}/v1/projects/${project.id}/files`;
+ const base = runtimeConfig.baseURL.replace(/\/+$/, '');
+ const uploadAPI = `${base}/v1/projects/${project.id}/files`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const runtimeConfig = useSelector(
(state: AppState) => state.site.runtimeConfig,
);
const uploadAPI = `${runtimeConfig.baseURL}/v1/projects/${project.id}/files`;
const token = useSelector((state: AppState) => state.user.token);
const runtimeConfig = useSelector(
(state: AppState) => state.site.runtimeConfig,
);
const base = runtimeConfig.baseURL.replace(/\/+$/, '');
const uploadAPI = `${base}/v1/projects/${project.id}/files`;
const token = useSelector((state: AppState) => state.user.token);
🤖 Prompt for AI Agents
In src/components/project/FileList.tsx around lines 58 to 62, the uploadURL is
constructed by naive interpolation of runtimeConfig.baseURL which may end with a
slash (default "/api/") causing a double-slash in the path; fix by normalizing
baseURL before interpolation — e.g. trim any trailing slashes from
runtimeConfig.baseURL (runtimeConfig.baseURL.replace(/\/+$/, '')) or use a
URL-join approach, then build
`${normalizedBaseURL}/v1/projects/${project.id}/files` so the final URL never
contains a double slash.

import { AppState } from '../../store';
import { setUserInfo } from '../../store/user/slice';
import { setCurrentTeamInfo } from '../../store/team/slice';
import { Avatar } from '..';

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.

🛠️ Refactor suggestion

Avoid potential circular import via barrel (..). Import Avatar directly.

If components/index.ts re-exports AvatarUpload, this creates a cycle and can yield undefined at runtime in ESM. Import Avatar from its file.

- import { Avatar } from '..';
+ import { Avatar } from './Avatar';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Avatar } from '..';
import { Avatar } from './Avatar';
🤖 Prompt for AI Agents
In src/components/shared/AvatarUpload.tsx around line 15, the code currently
imports Avatar via the barrel export (".."), which can create a circular import;
replace that barrel import with a direct import from the Avatar implementation
file (use the correct relative path to the Avatar component, e.g., ./Avatar or
../avatar/Avatar) so Avatar is imported from its module file rather than the
index barrel.

import { FC, UserTeam } from '../interfaces';
import { Team } from '../interfaces';
import { LIST_ITEM_DEFAULT_HEIGHT } from './ListItem';
import { LIST_ITEM_DEFAULT_HEIGHT } from '@/components/shared/ListItem';

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.

💡 Verification agent

🧩 Analysis chain

Switch to shared ListItem constant is aligned with the alias move.

Looks good. Verify alias setup for "@/..." across TS, bundler, and tests, and that the constant remains a named export.


🏁 Script executed:

#!/bin/bash
# 1) TS path alias
rg -nP --glob 'tsconfig*.json' '"paths"\s*:\s*{[^}]*"@/\*"' -S

# 2) Bundler alias (vite/webpack/craco)
rg -nP '(alias|resolve\.alias).*@' -S

# 3) Jest/Vitest mapping
rg -nP 'moduleNameMapper|alias.*@' -S

# 4) Ensure named export exists
rg -nP "export\s+const\s+LIST_ITEM_DEFAULT_HEIGHT\b" src/components/shared/ListItem.tsx

# 5) (Optional) locate deprecated antd internal types to migrate off if using antd v5
rg -nP "antd/(lib|es)/select" -S

Length of output: 647


Alias configuration for @/ imports is missing
I didn’t find any @/* path mapping in tsconfig.json, your bundler config, or your test runner config—so

import { LIST_ITEM_DEFAULT_HEIGHT } from '@/components/shared/ListItem';

will fail. Add the @/* alias under compilerOptions.paths in tsconfig.json, configure resolve.alias in your bundler (e.g. vite.config.ts or webpack.config.js), and set up moduleNameMapper (Jest) or alias (Vitest). The export const LIST_ITEM_DEFAULT_HEIGHT = 45; remains correctly declared.

🤖 Prompt for AI Agents
In src/components/TeamSearchList.tsx around line 18 the import using the '@/...'
alias will fail because no path mapping is configured; add the '@/*' mapping
under compilerOptions.paths in tsconfig.json (pointing '@/*' to 'src/*'), add
the same alias to your bundler resolve.alias (e.g. vite.config.ts or
webpack.config.js) and add the equivalent mapping for your test runner
(moduleNameMapper for Jest or alias in Vitest) so imports like
'@/components/...' resolve correctly; keep the existing export of
LIST_ITEM_DEFAULT_HEIGHT as-is.

@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2025

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
3.3% Duplication on New Code (required ≤ 3%)
D Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@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: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
src/components/shared-member/UserInvitationList.tsx (1)

106-111: Parse pagination header to number.

Axios headers are strings; total expects a number. Coerce to avoid type/logic errors.

Apply:

-        setTotal(result.headers['x-pagination-count']);
+        setTotal(Number(result.headers['x-pagination-count'] ?? 0));
src/components/team/TeamInsightProjectList.tsx (3)

235-238: Duplicate Column keys (“project” used twice).

Duplicate React keys can cause unstable renders. Make column keys unique.

-<Column
-  title={formatMessage({ id: 'site.projectName' })}
-  key="project"
+<Column
+  title={formatMessage({ id: 'site.projectName' })}
+  key="projectName"
...
-<Column
-  title={formatMessage({ id: 'project.export' })}
-  key="project"
+<Column
+  title={formatMessage({ id: 'project.export' })}
+  key="projectExport"

Also applies to: 327-330


347-375: Missing key on list children rendered by record.outputs.map.

Add a stable key to the wrapping element.

- {record.outputs.map((output) => (
-   <div>
+ {record.outputs.map((output) => (
+   <div key={output.id ?? `${output.type}-${output.createTime}-${output.target?.id ?? ''}`}>
      <CustomButton
        className="TeamInsightProjectList__OutputDownloadButton"

358-361: Typo: colorDisibled likely meant colorDisabled (prop won’t apply).

Fix the prop name to ensure disabled color styling works.

-  colorDisibled={style.textColorSecondary}
+  colorDisabled={style.textColorSecondary}
src/components/shared-form/CAPTCHAInput.tsx (2)

69-75: Fix: useImperativeHandle spreads a possibly null ref (runtime TypeError).

inputRef.current can be null on initial render; spreading it crashes. Safeguard the spread.

Apply:

   useImperativeHandle(ref, () => {
-    const imperatives = {
-      ...inputRef.current,
-      refresh: getCAPTCHA,
-    };
-    return imperatives as CAPTCHAInputRef;
+    const imperatives = {
+      ...(inputRef.current ?? {}),
+      refresh: getCAPTCHA,
+    };
+    return imperatives as CAPTCHAInputRef;
   });

114-116: Ensure loading clears on CAPTCHA fetch failure.

captchaLoading remains true after a failed request. Move the loading reset to finally.

Apply:

       .catch((result) => {
         result.default();
-      });
+      })
+      .finally(() => {
+        setCaptchaLoading(false);
+      });
♻️ Duplicate comments (4)
src/services/moeflow_companion/use_moeflow_companion.ts (2)

9-15: Strengthen typing for state and hook return (literal union, readonly tuple)

Exposes precise states to consumers and prevents stringly-typed usage.

 export const moeflowCompanionServiceState = {
   disabled: 'disabled',
   connecting: 'connecting',
   connected: 'connected',
   disconnected: 'disconnected',
 } as const;
 
+export type MoeflowCompanionServiceState =
+  (typeof moeflowCompanionServiceState)[keyof typeof moeflowCompanionServiceState];
+
 export function useMoeflowCompanion(): [
-  string,
-  MoeflowCompanionService | null,
-] {
+  MoeflowCompanionServiceState,
+  MoeflowCompanionService | null,
+] {
   const serviceRef = useRef<MoeflowCompanionService | null>(null);
-  const [clientState, setClientState] = useState<string>(
+  const [clientState, setClientState] = useState<MoeflowCompanionServiceState>(
     moeflowCompanionServiceState.connecting,
   );
@@
-  return [clientState, serviceRef.current] as const;
+  return [clientState, serviceRef.current] as const;

Also applies to: 24-31, 66-67


32-35: Guard against undefined runtimeConfig and reduce unnecessary reconnects

Optional-chain the selector to prevent crashes and depend on gradioUrl for reconnects; keep serviceConf for metadata.

-  const serviceConf = useSelector(
-    (s: AppState) => s.site.runtimeConfig.moeflowCompanion,
-  );
+  const serviceConf = useSelector(
+    (s: AppState) => s.site.runtimeConfig?.moeflowCompanion,
+  );
+  const gradioUrl = useSelector(
+    (s: AppState) => s.site.runtimeConfig?.moeflowCompanion?.gradioUrl,
+  );
@@
-      if (
-        !(
-          serviceConf &&
-          serviceConf.gradioUrl &&
-          serviceConf.defaultMultimodalModel
-        )
-      ) {
+      if (!gradioUrl) {
         serviceRef.current = null;
         setClientState(moeflowCompanionServiceState.disabled);
         return;
       }
       try {
-        const client = await Client.connect(serviceConf.gradioUrl);
+        const client = await Client.connect(gradioUrl);
         serviceRef.current = {
           client,
           multimodalTranslate,
           serviceConf,
         };
         setClientState(moeflowCompanionServiceState.connected);
         released.then(() => client.close());
       } catch (e) {
-        debugLogger('error connecting', e, serviceConf.gradioUrl);
+        debugLogger('error connecting', e, gradioUrl);
         serviceRef.current = null;
         setClientState(moeflowCompanionServiceState.disconnected);
       }
     },
-    [serviceConf],
+    [gradioUrl],

Also applies to: 36-65, 59-60

src/components/project/FileList.tsx (2)

29-29: LGTM! Import path is correctly updated.

The import path for ListPageSpec has been corrected to use the proper module location.


58-61: LGTM! Runtime config access is properly implemented.

The Redux-based runtime config access is correctly implemented and the upload URL construction looks good.

🧹 Nitpick comments (43)
src/components/shared-form/TypeRadioGroup.tsx (2)

8-8: Prefer type-only import for FC

Avoids emitting a runtime import for a type.

-import { FC } from '@/interfaces';
+import type { FC } from '@/interfaces';

68-75: Fix typo: rename handelChange → handleChange

Improves readability and editor symbol search.

-  const handelChange = (e: RadioChangeEvent) => {
+  const handleChange = (e: RadioChangeEvent) => {
     const type = (types as TypeData[]).find(
       (x) => x.id === e.target.value,
     ) as TypeData;
     changeType(type);
   };
...
-      onChange={handelChange}
+      onChange={handleChange}

Also applies to: 114-114

src/components/shared-form/GroupJoinForm.tsx (1)

6-8: Use type-only import for FC

Keeps the import purely for types.

-import { FC } from '@/interfaces';
+import type { FC } from '@/interfaces';
src/components/shared/ContentTitle.tsx (1)

3-4: Type-only import for FC

Minor emit/treeshake win.

-import { FC } from '@/interfaces';
+import type { FC } from '@/interfaces';
src/components/shared/ContentItem.tsx (1)

3-4: Type-only import for FC

Same rationale as other files.

-import { FC } from '@/interfaces';
+import type { FC } from '@/interfaces';
src/components/shared/Content.tsx (1)

3-4: Type-only import for FC

Consistent with the alias refactor.

-import { FC } from '@/interfaces';
+import type { FC } from '@/interfaces';
src/interfaces/file.ts (3)

39-48: Keep server DTOs clean: move browser-only fields to a UI-only type

Avoid polluting the shared File interface with browser-only fields; define a UI-layer MFile that extends File instead.

Example (outside this file):

// in UI layer
export type MFile = File & {
  // browser-only
  uploadOverwrite?: boolean;
  uploadState?: 'uploading' | 'success' | 'failure';
  uploadPercent?: number; // 0-100
};

43-46: Single source of truth for upload status

uploading duplicates uploadState === 'uploading'. Prefer the union-only approach to prevent drift.

-  uploading?: boolean;
   uploadOverwrite?: boolean;
   /** undefined when fetched from server */
   uploadState?: 'uploading' | 'success' | 'failure';

47-47: Percent range nit

Progress usually includes 0. Update the note to 0-100.

-  uploadPercent?: number; // 1-100
+  uploadPercent?: number; // 0-100
src/services/moeflow_companion/use_moeflow_companion.ts (2)

78-83: Future-proof: make endpoint path a constant

If the backend route changes, centralizing reduces churn.

-  const predictRes = await client.predict('/multimodal_llm_process_files', payload);
+  const MULTIMODAL_ROUTE = '/multimodal_llm_process_files' as const;
+  const predictRes = await client.predict(MULTIMODAL_ROUTE, payload);

1-7: Minor: logger name consistency

Consider naming logger to logger for consistency with the rest of the codebase.

src/components/shared-form/Form.tsx (1)

4-4: Use a type-only import for FC

Prevents emitting an unnecessary runtime import and silences possible TS “is a type and cannot be used as a value” edge cases.

-import { FC } from '@/interfaces';
+import type { FC } from '@/interfaces';
src/components/shared-form/FormItem.tsx (1)

3-3: Prefer type-only import for FC

Minor cleanup; keeps the bundle leaner.

-import { FC } from '@/interfaces';
+import type { FC } from '@/interfaces';
src/pages/Login.tsx (1)

16-19: Split type and value imports

CAPTCHAInputRef is a type; mark it as such to avoid emitting runtime code.

-import {
-  CAPTCHAInputRef,
-  checkCAPTCHA,
-} from '../components/shared-form/CAPTCHAInput';
+import type { CAPTCHAInputRef } from '../components/shared-form/CAPTCHAInput';
+import { checkCAPTCHA } from '../components/shared-form/CAPTCHAInput';
src/components/shared/EmptyTip.tsx (1)

4-5: Type-only import for FC

Small TypeScript hygiene improvement; no behavior change.

-import { FC } from '@/interfaces';
+import type { FC } from '@/interfaces';
src/pages/Dashboard.tsx (1)

16-16: Unify imports for DashboardMenu/MENU_COLLAPSED_WIDTH (avoid mixed barrel + deep path).

Importing DashboardMenu from the barrel and MENU_COLLAPSED_WIDTH from the leaf creates inconsistency and tighter coupling. Prefer a single source.

Option A — import both from the leaf:

- import { ApplicationList, DashboardMenu, Icon, TabBarM } from '../components';
+ import { ApplicationList, Icon, TabBarM } from '../components';
+ import { DashboardMenu, MENU_COLLAPSED_WIDTH } from '@/components/dashboard/DashboardMenu';

Option B — re-export MENU_COLLAPSED_WIDTH from the barrel and import both from there (if feasible):

- import { MENU_COLLAPSED_WIDTH } from '@/components/dashboard/DashboardMenu';
+ import { MENU_COLLAPSED_WIDTH } from '../components';

Also applies to: 37-37

src/components/shared/DashboardBox.tsx (1)

4-4: Use alias path for styles for consistency.

Most files use '@/style'. Align this one as well.

-import style from '../../style';
+import style from '@/style';
src/components/dashboard/DashboardMenu.tsx (2)

8-8: Prefer alias import for consistency.

Use '@/components' instead of a relative parent to align with repo-wide alias usage.

-import { Avatar, Dropdown, Icon, ListItem, TeamList, Tooltip } from '..';
+import { Avatar, Dropdown, Icon, ListItem, TeamList, Tooltip } from '@/components';

13-13: Unify style import to alias.

Other files use '@/style'. Recommend switching for consistency.

-import style from '../../style';
+import style from '@/style';
src/components/shared-form/AuthLoginedTip.tsx (1)

5-5: Import useHistory from react-router-dom for web apps.

Project-wide, other files import from 'react-router-dom'. Recommend aligning for consistency and to avoid subtle type/runtime mismatches.

-import { useHistory } from 'react-router';
+import { useHistory } from 'react-router-dom';
src/components/shared-member/UserInvitationList.tsx (2)

60-69: Avoid mutating items inside setState mapper.

Mutating item breaks immutability and can cause stale renders. Return a new object for the matched element.

Apply:

-        setItems((items) => {
-          return items.map((item) => {
-            // 改变状态
-            if (item.id === invitation.id) {
-              item.status = allow
-                ? INVITATION_STATUS.ALLOW
-                : INVITATION_STATUS.DENY;
-            }
-            return item;
-          });
-        });
+        setItems((items) =>
+          items.map((it) =>
+            it.id === invitation.id
+              ? {
+                  ...it,
+                  status: allow
+                    ? INVITATION_STATUS.ALLOW
+                    : INVITATION_STATUS.DENY,
+                }
+              : it,
+          ),
+        );

151-151: Prefer includes() over indexOf(...) > -1.

Clearer and avoids magic comparison.

Apply:

-            <Spin spinning={spinningIDs.indexOf(item.id) > -1}>
+            <Spin spinning={spinningIDs.includes(item.id)}>
src/components/shared-form/CAPTCHAModal.tsx (1)

2-2: Tidy imports and mark type-only imports.

  • Combine '@/components' imports into one line.
  • Use import type for refs/value types to prevent accidental runtime imports.

Apply:

-import { Icon } from '@/components';
+import { Icon, CAPTCHAInput } from '@/components';
...
-import { CAPTCHAInput } from '@/components';
-import { CAPTCHAInputRef, CAPTCHAInputValue } from './CAPTCHAInput';
+import type { CAPTCHAInputRef, CAPTCHAInputValue } from './CAPTCHAInput';

Also applies to: 7-12

src/components/shared-form/RoleRadioGroup.tsx (1)

5-7: Imports look good; consider marking type-only imports.

FC (and possibly TypeRadioGroupProps if imported from barrel later) are types—use import type to reduce emitted code.

Example:

-import { FC } from '@/interfaces';
+import type { FC } from '@/interfaces';
src/components/team/TeamInsightUserList.tsx (1)

63-97: Avoid index-based updates; derive target row by user.id to prevent stale-index bugs.
List reorders or paging can make index point to a different user.

-const handleLoadMoreClick = ({ user, index }: { user: APIUser; index: number }): void => {
-  const projectsPage = items[index].projectsPage;
+const handleLoadMoreClick = ({ user }: { user: APIUser }): void => {
+  const index = items.findIndex((it) => it.user.id === user.id);
+  if (index < 0) return;
+  const projectsPage = items[index].projectsPage;
   const page = projectsPage ? projectsPage : 1;
   setItems(produce((draft) => { draft[index].projectsLoading = true; }));
   apis.getTeamInsightUserProjects({ teamID: team.id, userID: user.id, params: { page, limit: 20 }})
     .then((result) => {
       const data = toLowerCamelCase(result.data);
       setItems(produce((draft) => {
         draft[index].projectsLoading = false;
         draft[index].projectsPage = page + 1;
         if (page === 1) draft[index].projects = data;
         else draft[index].projects.push(...data);
       }));
     });
};
src/components/shared-form/UserEmailEditForm.tsx (1)

11-11: Unify to alias import for utils.
Keep imports consistent with the rest of the PR.

-import { toLowerCamelCase } from '../../utils';
+import { toLowerCamelCase } from '@/utils';
src/components/project-set/ProjectSetEditForm.tsx (2)

35-39: Minor: dependency array.
Including form in deps removes the need for eslint-disable, or memoize values. Low priority.

-useEffect(() => {
+useEffect(() => {
   form.setFieldsValue(toLowerCamelCase(currentProjectSet));
   // eslint-disable-next-line react-hooks/exhaustive-deps
-}, [currentProjectSet.id]);
+}, [form, currentProjectSet.id]);

72-74: Remove unused initialValues.intro.
It’s not bound to any FormItem.

-        initialValues={{ intro: '' }}
+        // initialValues reserved for bound fields
src/components/project-set/ProjectSetList.tsx (2)

2-2: Optional: drop default React import and use type-only imports for React types.
Aligns with files that rely on the automatic JSX runtime.

-import React, { useState } from 'react';
+import { useState, type ReactNode, type ReactElement } from 'react';
@@
-interface ProjectSetListProps {
+interface ProjectSetListProps {
   className?: string;
-  searchRightButton?: React.ReactNode | React.ReactElement;
+  searchRightButton?: ReactNode | ReactElement;
   onSearchRightButtonClick?: (e: React.MouseEvent) => void;
 }

91-94: Parse pagination header as number.
Avoid type drift; headers are strings.

-        setTotal(result.headers['x-pagination-count']);
+        setTotal(Number(result.headers['x-pagination-count'] ?? 0));
src/components/team/TeamInsightProjectList.tsx (2)

11-11: Keep import path conventions consistent with alias refs.

Other imports use @-aliases; consider aligning CustomButton to avoid brittle relative paths.

-import { Button as CustomButton } from '../shared/Button';
+import { Button as CustomButton } from '@/components/shared/Button';

20-26: Mixed API imports (default “apis” here vs named “api” elsewhere).

TeamList moved to named api.team.*; consider standardizing here for consistency and future deprecation of the default export.

Example:

-import apis from '@/apis';
+import { api } from '@/apis';
...
-    apis.createTeamOutput({ teamID, })
+    api.output.createTeamOutput({ teamID, })
...
-    apis.createAllOutput({ projectID, })
+    api.output.createAllOutput({ projectID, })
...
-    apis.getTeamInsightProjectUsers({ ... })
+    api.insight.getTeamInsightProjectUsers({ ... })
src/components/shared-form/MemberList.tsx (1)

24-24: Consider using path alias for user utility

The import from '../../utils/user' could potentially be updated to use the path alias pattern like from '@/utils/user' for consistency, though this may depend on the specific directory structure.

src/components/shared-form/CAPTCHAInput.tsx (1)

120-134: Nit: avoid mutating the synthetic event’s target value.

Compute a sanitized value and set state; mutating e.target.value is unnecessary.

-  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-    // 只允许输入数字
-    e.target.value = e.target.value.replace(/[^\d]/g, '');
-    // 如果过滤后和之前值相同,则跳过
-    if (e.target.value === captchaValue) return;
-    // 记录输入的验证码
-    setCaptchaValue(e.target.value);
+  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    const next = e.target.value.replace(/[^\d]/g, '');
+    if (next === captchaValue) return;
+    setCaptchaValue(next);
     // 发送到外部
     if (onChange) {
       onChange({
-        value: e.target.value,
+        value: next,
         info: captchaInfo,
       });
     }
   };
src/components/shared-member/InviteUser.tsx (2)

104-109: Parse pagination header to number.

Axios headers are strings; setTotal expects a number.

-            // 设置数量
-            setTotal(result.headers['x-pagination-count']);
+            // 设置数量
+            setTotal(Number(result.headers['x-pagination-count'] || 0));

135-138: Same as above: coerce header to number.

-        // 设置数量
-        setTotal(result.headers['x-pagination-count']);
+        // 设置数量
+        setTotal(Number(result.headers['x-pagination-count'] || 0));
src/components/project/FileItem.tsx (2)

176-181: Guard non-null assertions on fileTargetCache.

Using ! risks a crash if hasTarget is true but cache not hydrated yet. Prefer optional chaining with fallback.

-  const translatedSourceCount = hasTarget
-    ? file.fileTargetCache!.translatedSourceCount
-    : file.translatedSourceCount;
-  const checkedSourceCount = hasTarget
-    ? file.fileTargetCache!.checkedSourceCount
-    : file.checkedSourceCount;
+  const translatedSourceCount = hasTarget
+    ? (file.fileTargetCache?.translatedSourceCount ?? 0)
+    : file.translatedSourceCount;
+  const checkedSourceCount = hasTarget
+    ? (file.fileTargetCache?.checkedSourceCount ?? 0)
+    : file.checkedSourceCount;

8-9: Import from 'unused' path signals dead code risk.

ImageOCRProgress imported from '@/components/unused/...'. If this is intentionally deprecated, consider tree-shaking or lazy import to avoid bundling unused code.

- import { ImageOCRProgress } from '@/components/unused/ImageOCRProgress';
+ // TODO: remove or lazy-load if kept for legacy pages
+ import { ImageOCRProgress } from '@/components/unused/ImageOCRProgress';
src/components/project/FileList.tsx (1)

68-69: Add coverage for AI translate hook edge cases.

  • Add unit tests in FileListAiTranslate.tsx (lines 44–46) for error, loading, and empty-input scenarios.
  • Ensure FileList.tsx (lines 68–69) properly disables translation UI and handles aiTranslateAvailable === false.
src/components/project/FileListAiTranslate.tsx (4)

82-83: Clarify resource pool configuration purpose.

The resource pools are configured with specific limits but lack documentation explaining the reasoning behind these numbers.

-    const fileLimiter = ResourcePool.multiple([1, 2]);
-    const moeflowApiLimiter = ResourcePool.multiple([1, 2, 3, 4]);
+    // Limit concurrent file translations to prevent overwhelming the AI service
+    const fileLimiter = ResourcePool.multiple([1, 2]);
+    // Throttle API calls to respect rate limits when saving translation results
+    const moeflowApiLimiter = ResourcePool.multiple([1, 2, 3, 4]);

139-139: Hard-coded target language should be configurable.

The target language is hard-coded to 'Chinese Traditional', which limits the component's reusability for other translation scenarios.

Consider making the target language configurable through props or runtime configuration, similar to how the model is configured via serviceConf.defaultMultimodalModel.


25-42: Empty console.log statements in event handlers.

The onOk and onCancel handlers contain only console.log statements, which appear to be placeholder implementations.

    onOk: () => {
-      console.log('ok');
+      // Modal will close automatically
    },
    onCancel: () => {
-      console.log('cancel');
+      // Modal will close automatically
    },

195-195: Use internationalization for user-facing text.

The text "files to translate" should use the internationalization system for consistency with the rest of the application.

-      {files.length} files to translate
+      {intl.formatMessage({ id: 'fileList.filesToTranslate' }, { count: files.length })}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 88b2938 and 83ac745.

📒 Files selected for processing (53)
  • src/apis/index.ts (3 hunks)
  • src/components/admin/AdminSiteSetting.tsx (1 hunks)
  • src/components/admin/AdminUserList.tsx (1 hunks)
  • src/components/dashboard/DashboardMenu.tsx (1 hunks)
  • src/components/index.ts (1 hunks)
  • src/components/project-set/ProjectSetCreateForm.tsx (1 hunks)
  • src/components/project-set/ProjectSetEditForm.tsx (1 hunks)
  • src/components/project-set/ProjectSetList.tsx (1 hunks)
  • src/components/project-set/ProjectSetSettingBase.tsx (1 hunks)
  • src/components/project/FileItem.tsx (3 hunks)
  • src/components/project/FileList.tsx (17 hunks)
  • src/components/project/FileListAiTranslate.tsx (1 hunks)
  • src/components/project/ProjectTargetList.tsx (6 hunks)
  • src/components/shared-form/AuthFormWrapper.tsx (1 hunks)
  • src/components/shared-form/AuthLoginedTip.tsx (1 hunks)
  • src/components/shared-form/CAPTCHAInput.tsx (1 hunks)
  • src/components/shared-form/CAPTCHAModal.tsx (1 hunks)
  • src/components/shared-form/EmailVCodeInputItem.tsx (1 hunks)
  • src/components/shared-form/Form.tsx (1 hunks)
  • src/components/shared-form/FormItem.tsx (1 hunks)
  • src/components/shared-form/GroupJoinForm.tsx (1 hunks)
  • src/components/shared-form/MemberList.tsx (1 hunks)
  • src/components/shared-form/RoleRadioGroup.tsx (1 hunks)
  • src/components/shared-form/RoleSelect.tsx (1 hunks)
  • src/components/shared-form/TypeRadioGroup.tsx (1 hunks)
  • src/components/shared-form/UserEmailEditForm.tsx (1 hunks)
  • src/components/shared-form/UserPasswordEditForm.tsx (1 hunks)
  • src/components/shared-member/ApplicationList.tsx (2 hunks)
  • src/components/shared-member/InvitationList.tsx (1 hunks)
  • src/components/shared-member/InviteUser.tsx (1 hunks)
  • src/components/shared-member/UserInvitationList.tsx (1 hunks)
  • src/components/shared/Content.tsx (1 hunks)
  • src/components/shared/ContentItem.tsx (1 hunks)
  • src/components/shared/ContentTitle.tsx (1 hunks)
  • src/components/shared/DashboardBox.tsx (1 hunks)
  • src/components/shared/DebounceStatus.tsx (1 hunks)
  • src/components/shared/EmptyTip.tsx (1 hunks)
  • src/components/shared/NavTab.tsx (1 hunks)
  • src/components/shared/NavTabs.tsx (1 hunks)
  • src/components/shared/TabBarM.tsx (1 hunks)
  • src/components/shared/TranslationProgress.tsx (1 hunks)
  • src/components/team/TeamCreateForm.tsx (1 hunks)
  • src/components/team/TeamEditForm.tsx (1 hunks)
  • src/components/team/TeamInsightProjectList.tsx (2 hunks)
  • src/components/team/TeamInsightUserList.tsx (1 hunks)
  • src/components/team/TeamList.tsx (2 hunks)
  • src/components/team/TeamSearchList.tsx (1 hunks)
  • src/components/team/TeamSettingBase.tsx (1 hunks)
  • src/interfaces/file.ts (1 hunks)
  • src/pages/Dashboard.tsx (1 hunks)
  • src/pages/Login.tsx (1 hunks)
  • src/pages/ProjectSetSetting.tsx (1 hunks)
  • src/services/moeflow_companion/use_moeflow_companion.ts (1 hunks)
✅ Files skipped from review due to trivial changes (4)
  • src/components/shared-form/EmailVCodeInputItem.tsx
  • src/pages/ProjectSetSetting.tsx
  • src/components/shared/TabBarM.tsx
  • src/components/project-set/ProjectSetSettingBase.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/components/project/ProjectTargetList.tsx (2)
src/interfaces/target.ts (1)
  • Target (4-12)
src/components/shared/EmptyTip.tsx (1)
  • EmptyTip (16-39)
src/services/moeflow_companion/use_moeflow_companion.ts (3)
src/utils/debug-logger.ts (1)
  • createDebugLogger (3-5)
src/configs.tsx (1)
  • RuntimeConfig (3-11)
src/store/index.ts (1)
  • AppState (44-44)
🔇 Additional comments (41)
src/components/shared-form/TypeRadioGroup.tsx (2)

4-4: Hooks import looks good

Switching to named imports is consistent with the JSX runtime and avoids unused default React imports.


49-66: Confirm deps suppression is intentional

Effect only re-runs on URL changes. If typeName/groupType/useDefaultType/configs can change without URL changes, defaults may go stale.

src/services/moeflow_companion/use_moeflow_companion.ts (1)

32-35: Scan for other unsafe runtimeConfig accesses
Ensure all direct runtimeConfig.* references across the codebase are guarded with optional chaining (e.g. runtimeConfig?.foo) to prevent runtime errors. You can run:

grep -R -n "runtimeConfig\." src/ | grep -v "runtimeConfig\\?\."
src/components/shared/DebounceStatus.tsx (1)

1-1: Keep importing from @emotion/core
Project’s package.json shows Emotion v10.3.1 is installed (no @emotion/react), so the import should remain:

import { css } from '@emotion/core';

Likely an incorrect or invalid review comment.

src/components/admin/AdminUserList.tsx (1)

20-22: Alias import migration looks good

Shared-form imports align with the refactor; no functional changes observed.

src/components/admin/AdminSiteSetting.tsx (1)

11-12: LGTM: moved Form/FormItem to shared-form.

Import-path refactor is clean and keeps API usage unchanged.

src/components/shared-member/ApplicationList.tsx (1)

5-5: JSX transform enabled: tsconfig.json’s compilerOptions.jsx is set to react-jsx, so removing the default React import is safe.

src/components/shared/NavTab.tsx (1)

2-9: Automatic JSX runtime confirmed. tsconfig.json’s compilerOptions.jsx is set to react-jsx, so the automatic JSX transform is enabled.

src/components/shared-form/UserPasswordEditForm.tsx (2)

7-11: Alias imports LGTM.

Barrels and default exports for components/apis/interfaces/store/utils appear consistent with the refactor.


3-3: No action required: new JSX runtime confirmed
tsconfig.json is set to "jsx": "react-jsx", so the new JSX transform supports JSX without importing React.

src/components/shared-form/AuthLoginedTip.tsx (1)

6-11: Alias import migration looks good.

The component barrel, store barrel, and style default import align with the new convention.

src/components/shared-member/UserInvitationList.tsx (1)

5-5: Consolidate @/components imports: merged Spin into the existing import; tsconfig.json has "jsx": "react-jsx" and React 17.0.2 is in use, so automatic JSX runtime is enabled.

src/components/shared/NavTabs.tsx (1)

2-2: LGTM! Import consolidation and Menu removal are appropriate.

The removal of the unused Menu import from antd and migration to alias-based imports aligns with the project's refactoring goals. Only MenuProps is retained, which is correctly used in the component logic.

Also applies to: 7-11

src/components/team/TeamSettingBase.tsx (1)

2-2: LGTM! Comprehensive import refactoring executed correctly.

The systematic migration from relative imports to alias-based imports (@/) and removal of default React import aligns with the project-wide refactoring. The AvatarUpload import path change to the shared components directory maintains consistency with the new component organization structure.

Also applies to: 4-4, 8-10, 15-18, 20-20

src/components/shared-member/InvitationList.tsx (1)

2-2: LGTM! Consistent import modernization and consolidation.

The changes demonstrate good refactoring practices:

  • Consolidation of Icon and Spin imports from the same @/components module
  • Complete migration to alias-based imports for better maintainability
  • Removal of default React import following modern JSX transform patterns

Also applies to: 7-7, 10-21

src/components/shared-form/AuthFormWrapper.tsx (1)

2-2: Confirmed automatic JSX runtime; removal of default React import is safe. tsconfig.json is set to "jsx": "react-jsx" and project uses React 17.0.2, which supports the new transform.

src/components/shared-form/UserEmailEditForm.tsx (1)

6-7: All imported components are correctly re-exported from '@/components'. Export statements exist in src/components/index.ts at lines 16 (EmailVCodeInputItem), 19 (Form), 20 (FormItem), and 60 (VCodeInput).

src/components/project-set/ProjectSetEditForm.tsx (1)

6-13: Alias import migration: looks good.
No behavioral changes detected.

src/components/project-set/ProjectSetList.tsx (1)

11-25: Alias-based imports: LGTM.
Consistent with project-wide refactor.

src/apis/index.ts (2)

12-12: LGTM: runtimeConfig import aligns with new runtime-config flow.


219-220: 401 default handler no longer clears token — verify intended UX.

If global auth state is now managed elsewhere (e.g., a dedicated auth flow), this is fine. Otherwise, users may get stuck in an unauthorized state without remediation.

Would you like a small response-interceptor that centralizes 401 handling (logout + redirect to login) to replace the per-call default?

src/components/team/TeamInsightProjectList.tsx (1)

327-327: LGTM: i18n key switched to project.export.

Assuming locales were updated, this is correct.

src/components/team/TeamList.tsx (2)

8-16: LGTM: path-alias refactor for imports.


57-68: LGTM – getUserTeams is defined and exported on the api.team namespace.

src/components/team/TeamSearchList.tsx (2)

2-2: LGTM: React import modernization

The shift to named import for useState is consistent with the project-wide modernization pattern.

Also applies to: 6-6


10-18: LGTM: Path alias migration executed correctly

The comprehensive migration to @/ path aliases improves maintainability. Notable changes:

  • Consolidated component imports from @/components
  • API imports from @/apis
  • Constants, store, utils, and interfaces properly aliased
  • LIST_ITEM_DEFAULT_HEIGHT import updated to specific path @/components/shared/ListItem

All import paths appear correct and functional.

src/components/project/ProjectTargetList.tsx (5)

4-4: LGTM: React import pattern modernized

Named import for useState follows the project-wide pattern.


7-18: LGTM: Path aliases and API refactoring

The changes show proper migration to path aliases and API restructuring:

  • Changed from default api import to named { api, resultTypes } import
  • All other imports properly aliased with @/
  • This aligns with API surface refactoring mentioned in the summary

The API namespace change (api.target.getProjectTargets) suggests better API organization.


41-41: LGTM: Improved variable naming

The rename from items to targetLangs is more descriptive and improves code readability. The variable correctly stores the target language data.


56-74: LGTM: API namespace usage and state management

The API call correctly uses the new api.target namespace structure. The data flow maintains consistency:

  • Results are mapped through toLowerCamelCase
  • Stored in targetLangs state
  • onLoad callback receives the processed items array

State variable usage is consistent throughout.

Also applies to: 98-98


126-128: LGTM: Simplified empty tip creator

The arrow function syntax for emptyTipCreater is more concise and follows modern JavaScript patterns.

src/components/shared-form/MemberList.tsx (1)

8-26: LGTM: Comprehensive path alias migration

Excellent consolidation of imports using path aliases. The migration includes:

  • Consolidated component imports from @/components
  • API, constants, interfaces, store, and style imports properly aliased
  • Maintained one relative import for can utility from ../../utils/user

All import paths appear correct and the consolidation improves readability.

src/components/shared-member/InviteUser.tsx (1)

2-3: Alias import migration: verify project-wide config.

Ensure tsconfig paths, bundler aliases, and test runners map '@/*' correctly to avoid broken imports.

(You can reuse the alias-check script from CAPTCHAInput.tsx.)

Also applies to: 7-7, 9-18

src/components/team/TeamCreateForm.tsx (1)

7-14: LGTM on alias migration and logic.

Only import path updates; form logic unchanged and consistent with existing patterns.

src/components/project-set/ProjectSetCreateForm.tsx (1)

7-16: LGTM on alias migration and creation flow.

No functional changes; success path updates Redux and navigation as before.

src/components/project/FileItem.tsx (1)

45-54: OCR progress is always hidden. Intentional?

.fileItemStyle sets .FileItem__ImageOCRProgressWrapper to display: none with no override; the OCR widget will never show.

If you intend to show it on hover, add:

   .FileItem__ImageOCRProgressWrapper {
     display: none;
     position: absolute;
     top: ${imageHeight - 17}px;
     left: 6px;
     ...
   }
+  &:hover .FileItem__ImageOCRProgressWrapper {
+    display: block;
+  }
src/components/project/FileList.tsx (4)

127-180: Approve refactored file handling logic.

The handleFileAdd function is well-structured and correctly creates MFile objects with appropriate initial state. The logic for generating cover URLs and setting upload state is sound.


183-217: LGTM! Clean pagination and data loading.

The loadPage function properly manages pagination state and correctly transforms API responses. The error handling and loading state management is appropriate.


383-393: Clean conditional rendering for AI translate feature.

The AI translate button is properly gated by availability and uses appropriate internationalization keys.


585-585: Verify modal context holder placement.

The modal context holder is properly placed at the end of the component's JSX.

src/components/index.ts (1)

1-61: Approve component exports reorganization. Verified that all moved components exist at their new locations and exports are correctly defined.

Comment thread src/apis/index.ts
Comment on lines 42 to 57
@@ -205,7 +216,7 @@ export async function request<T = unknown>(

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.

🛠️ Refactor suggestion

Authorization header only set at creation and only in development — will drop auth in production and on token refresh.

  • In prod, no Authorization header is ever attached.
  • After login/refresh, the token in headers won’t update because it’s captured once at instance creation.

Switch to a request interceptor that reads the latest store token for every request (works in all envs) and remove the dev-only header path.

Apply:

-const instanceP = lazyThenable(() => {
-  return runtimeConfig.then((r) => {
-    const token = store.getState().user.token;
-    // dev-only workaround: hot reload may create another instanceP with no common headers sets
-    const commonHeaders =
-      process.env.NODE_ENV === 'development' && token
-        ? { Authorization: `Bearer ${token}` }
-        : {};
-    return axios.create({
-      baseURL: `${r.baseURL}`,
-      headers: {
-        common: commonHeaders,
-      },
-    });
-  });
-});
+const instanceP = lazyThenable(() => {
+  return runtimeConfig.then((r) => {
+    const inst = axios.create({
+      baseURL: r.baseURL,
+      // optional but recommended
+      timeout: 30000,
+    });
+    inst.interceptors.request.use((req) => {
+      const t = store.getState().user.token;
+      if (t) {
+        req.headers = { ...(req.headers || {}), Authorization: `Bearer ${t}` };
+      }
+      return req;
+    });
+    return inst;
+  });
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const instanceP = lazyThenable(() => {
return runtimeConfig.then((r) => {
const token = store.getState().user.token;
// dev-only workaround: hot reload may create another instanceP with no common headers sets
const commonHeaders =
process.env.NODE_ENV === 'development' && token
? { Authorization: `Bearer ${token}` }
: {};
return axios.create({
baseURL: `${r.baseURL}`,
headers: {
common: commonHeaders,
},
});
});
});
const instanceP = lazyThenable(() => {
return runtimeConfig.then((r) => {
const inst = axios.create({
baseURL: r.baseURL,
// optional but recommended
timeout: 30000,
});
inst.interceptors.request.use((req) => {
const t = store.getState().user.token;
if (t) {
req.headers = { ...(req.headers || {}), Authorization: `Bearer ${t}` };
}
return req;
});
return inst;
});
});
🤖 Prompt for AI Agents
In src/apis/index.ts around lines 42–57 the axios instance sets an Authorization
header only at creation time and only in development, so production requests and
subsequent token updates lose auth; remove the dev-only common header logic and
instead register a request interceptor on the created axios instance that, for
every outgoing request, reads the latest token from store.getState().user.token
and sets request.headers.Authorization = `Bearer ${token}` when present (or
deletes the header when absent); add the interceptor before returning the
instance from lazyThenable so every request uses the current token in all
environments.

Comment on lines +93 to +94
files.map((f, idx) => fileLimiter.use(() => translateFile(f, idx))),
]);

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.

⚠️ Potential issue

Incorrect Promise.allSettled usage.

The files.map() call returns an array of promises, but it's wrapped in another array, creating Promise<[PromiseSettledResult<any>[]]> instead of Promise<PromiseSettledResult<any>[]>.

    const tasksEnded = Promise.allSettled(
-      files.map((f, idx) => fileLimiter.use(() => translateFile(f, idx))),
+      files.map((f, idx) => fileLimiter.use(() => translateFile(f, idx)))
    );
🤖 Prompt for AI Agents
In src/components/project/FileListAiTranslate.tsx around lines 93-94, the
Promise.allSettled call is incorrectly passed an array containing the result of
files.map(...), producing a nested array of promises; remove the extra outer
array and call Promise.allSettled(files.map((f, idx) => fileLimiter.use(() =>
translateFile(f, idx)))) so it receives the iterable of promises directly, and
adjust any result typing/handling if necessary to expect
PromiseSettledResult<any>[].

Comment on lines +122 to +124
if (refetched.data.sourceCount) {
setFileState(f, 'skip: source count not 0');
}

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.

⚠️ Potential issue

Missing return statement in conditional block.

The condition checks if sourceCount exists but doesn't return early, causing the function to continue executing even when it should skip processing.

      if (refetched.data.sourceCount) {
        setFileState(f, 'skip: source count not 0');
+        return;
      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (refetched.data.sourceCount) {
setFileState(f, 'skip: source count not 0');
}
if (refetched.data.sourceCount) {
setFileState(f, 'skip: source count not 0');
return;
}
🤖 Prompt for AI Agents
In src/components/project/FileListAiTranslate.tsx around lines 122 to 124, the
if-block sets the file state when refetched.data.sourceCount is truthy but does
not stop further execution; add an immediate early return after setFileState
(i.e., return;) so the function exits and no further processing runs for that
file.

Comment on lines +160 to +161
x: (tb.left + tb.right) / 2 / r.image_w,
y: (tb.top + tb.bottom) / 2 / r.image_h,

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.

⚠️ Potential issue

Undefined variable r causing compilation error.

The variable r is referenced but not defined in this scope. It appears to be referencing the TranslatedFile result from the multimodalTranslate call, but r is only available within the translateFile function scope.

    async function saveTextBlock(
      f: MFile,
      tb: TranslatedFile['text_blocks'][number],
+     imageWidth: number,
+     imageHeight: number,
    ) {
      const src = await api.source.createSource({
        fileID: f.id,
        data: {
-          x: (tb.left + tb.right) / 2 / r.image_w,
-          y: (tb.top + tb.bottom) / 2 / r.image_h,
+          x: (tb.left + tb.right) / 2 / imageWidth,
+          y: (tb.top + tb.bottom) / 2 / imageHeight,
          content: tb.source,
        },
        configs: { cancelToken },
      });

And update the call site in the commit function:

        await Promise.all(
          r.text_blocks.map((tb) =>
-            moeflowApiLimiter.use(() => saveTextBlock(f, tb)),
+            moeflowApiLimiter.use(() => saveTextBlock(f, tb, r.image_w, r.image_h)),
          ),
        );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
x: (tb.left + tb.right) / 2 / r.image_w,
y: (tb.top + tb.bottom) / 2 / r.image_h,
// src/components/project/FileListAiTranslate.tsx
async function saveTextBlock(
f: MFile,
tb: TranslatedFile['text_blocks'][number],
imageWidth: number,
imageHeight: number,
) {
const src = await api.source.createSource({
fileID: f.id,
data: {
x: (tb.left + tb.right) / 2 / imageWidth,
y: (tb.top + tb.bottom) / 2 / imageHeight,
content: tb.source,
},
configs: { cancelToken },
});
// ... any remaining logic
}
// Later, in the commit function:
await Promise.all(
r.text_blocks.map((tb) =>
moeflowApiLimiter.use(() => saveTextBlock(f, tb, r.image_w, r.image_h)),
),
);
🤖 Prompt for AI Agents
In src/components/project/FileListAiTranslate.tsx around lines 160-161, the code
uses an undefined variable `r` when computing normalized box center coordinates;
`r` should be the TranslatedFile result returned by multimodalTranslate but is
out of scope. Fix by replacing `r` with the correct local variable that holds
the translate result in this scope (e.g., the `result` or `translated` variable
returned from translateFile/multimodalTranslate), or pass the needed
image_w/image_h into this scope so you can compute x and y from that object;
then update the call site in the commit function to pass or use the same
translated result variable so the coordinates are computed from the actual
TranslatedFile instance rather than `r`.

Comment on lines +7 to +12
import api from '@/apis';
import { LoadingIcon } from '@/components';
import { getIntl } from '@/locales';
import style from '@/style';
import { getCancelToken } from '@/utils/api';
import { clickEffect, imageClickEffect } from '@/utils/style';

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.

💡 Verification agent

🧩 Analysis chain

Alias import migration: verify build/test tooling is configured.

Confirm '@' path aliases are wired in tsconfig, bundler, and tests to prevent runtime/import failures.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tsconfig paths =="
fd -a '^tsconfig(\..+)?\.json$' | while read -r f; do
  echo "--- $f"
  jq -r '.compilerOptions.baseUrl, .compilerOptions.paths' "$f" || true
done

echo "== Jest moduleNameMapper =="
fd -a '^jest\.config\.(js|ts|mjs|cjs)$' | xargs -I{} sh -c 'echo "--- {}"; cat {}' | rg -n "moduleNameMapper|@/.*"

echo "== Vite/Webpack alias =="
fd -a '^(vite|webpack)\.config\.(js|ts|mjs|cjs)$' | xargs -I{} sh -c 'echo "--- {}"; rg -n "alias|@/" {} -n -C2 || true'

echo "== ESLint import/resolver alias =="
fd -a '^\.?eslintrc(\.(js|cjs|json|yml|yaml))?$' | xargs -I{} sh -c 'echo "--- {}"; cat {}' | rg -n "settings|import/resolver|alias|@/" -n -C2 || true

Length of output: 525


Configure '@' path alias in bundler, test runner & ESLint

  • tsconfig.json already defines "@/*": ["./src/*"]
  • Bundler: add resolve.alias for '@' → 'src' in vite.config.js, webpack.config.js or next.config.js
  • Tests: add Jest moduleNameMapper (or Vitest resolve.alias) mapping ^@/(.*)$ → <rootDir>/src/$1
  • ESLint: add import/resolver.alias settings for '@' → './src' in .eslintrc.* to prevent import errors
🤖 Prompt for AI Agents
In src/components/shared-form/CAPTCHAInput.tsx lines 7-12, imports using the '@'
path alias will fail at build/test/lint if the alias isn't configured across
tools; update your project configs: add resolve.alias { '@':
path.resolve(__dirname, 'src') } in your bundler (vite.config.js,
webpack.config.js or next.config.js), add a Jest moduleNameMapper '^@/(.*)$':
'<rootDir>/src/$1' (or Vitest resolve.alias) for tests, and add the
import/resolver.alias entry mapping '@' → './src' in your ESLint config so
imports resolve consistently.

Comment on lines +6 to +14
import { Form, FormItem, RoleRadioGroup, TypeRadioGroup } from '@/components';
import api from '@/apis';
import { GROUP_ALLOW_APPLY_TYPE, TEAM_PERMISSION } from '@/constants';
import { FC, UserTeam } from '@/interfaces';
import { AppState } from '@/store';
import { editTeam, setCurrentTeam } from '@/store/team/slice';
import { toLowerCamelCase } from '@/utils';
import { TEAM_NAME_REGEX } from '@/utils/regex';
import { can } from '@/utils/user';

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.

💡 Verification agent

🧩 Analysis chain

Alias refactor looks good; ensure barrels/defaults exist.

Confirm these are exported correctly:

  • '@/components' -> Form, FormItem, RoleRadioGroup, TypeRadioGroup (named).
  • '@/apis' -> default export.
  • '@/constants' -> GROUP_ALLOW_APPLY_TYPE, TEAM_PERMISSION (named).
  • '@/interfaces' -> FC, UserTeam (named).
  • '@/store' -> AppState (named).

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -nP '\bexport\b.*\b(Form|FormItem|RoleRadioGroup|TypeRadioGroup)\b' src/components/index.ts || echo "WARN: components not exported"
rg -n 'export default' src/apis/index.ts || echo "WARN: apis default export missing"
rg -nP '\bexport\b.*\b(GROUP_ALLOW_APPLY_TYPE|TEAM_PERMISSION)\b' src/constants/index.ts || echo "WARN: constants missing"
rg -nP '\bexport\b.*\b(FC|UserTeam)\b' src/interfaces/index.ts || echo "WARN: interfaces missing"
rg -nP '\bexport\b.*\bAppState\b' src/store/index.ts || echo "WARN: AppState missing from store barrel"

Length of output: 797


Add missing barrel exports in constants and interfaces

  • src/constants/index.ts does not re-export GROUP_ALLOW_APPLY_TYPE or TEAM_PERMISSION; add
  • src/interfaces/index.ts does not re-export FC or UserTeam; add
🤖 Prompt for AI Agents
In src/components/team/TeamEditForm.tsx around lines 6 to 14, imports reference
GROUP_ALLOW_APPLY_TYPE and TEAM_PERMISSION from src/constants and FC and
UserTeam from src/interfaces but the barrel index files are missing those
re-exports; open src/constants/index.ts and add named exports for
GROUP_ALLOW_APPLY_TYPE and TEAM_PERMISSION that re-export them from their
original module paths, and open src/interfaces/index.ts and add named exports
for FC and UserTeam that re-export their definitions from their source files;
save and run the typecheck/build to ensure the new barrel exports resolve the
import errors.

import produce from 'immer';
import qs from 'qs';
import React, { useState } from 'react';
import { useState } from 'react';

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.

💡 Verification agent

🧩 Analysis chain

Alias import refactor looks good; ensure tooling supports "@/".
Confirm tsconfig/jest alias mapping to avoid runtime/test breaks.

Also applies to: 10-18


🏁 Script executed:

#!/bin/bash
# Verify TS/Jest alias config for "@/"
rg -nC2 '"baseUrl"|\"paths\"' tsconfig*.json
rg -nC2 '@/(.*)' jest*.config.* package.json

Length of output: 245


🏁 Script executed:

#!/bin/bash
# Verify TS alias config in tsconfig files
rg -nC2 '"baseUrl"' tsconfig*.json
rg -nC2 '"paths"' tsconfig*.json
rg -nC2 '@/' tsconfig*.json

# Verify Jest alias mapping in config files or package.json
rg -nC2 'moduleNameMapper' jest.config*.js jest.config*.ts package.json
rg -nC2 '@/' jest.config*.js jest.config*.ts package.json

Length of output: 704


🏁 Script executed:

#!/bin/bash
# Locate any Jest configuration files
find . -maxdepth 2 -type f -name "jest.config.*"

# Show any "jest" config in package.json
grep -R '"jest"' -n package.json -C2

Length of output: 594


Add Jest moduleNameMapper for "@/".

tsconfig.json already maps "@/" → "./src/" (tsconfig.json lines 17–19), but package.json’s Jest config (around line 62) lacks a corresponding moduleNameMapper, so imports using "@/…" will break in tests. In your package.json “jest” section, add:

"moduleNameMapper": {
  "^@/(.*)$": "<rootDir>/src/$1"
}
🤖 Prompt for AI Agents
In src/components/team/TeamInsightUserList.tsx around line 6, tests will fail
because Jest lacks a moduleNameMapper for the "@/..." path used in imports;
update package.json's "jest" section to add a moduleNameMapper entry mapping
"^@/(.*)$" to "<rootDir>/src/$1" so Jest resolves "@/..." imports the same way
TypeScript does.

Comment on lines +18 to +22
export interface MoeflowCompanionService {
client: Client;
serviceConf: RuntimeConfig['moeflowCompanion'];
multimodalTranslate: typeof multimodalTranslate;
}

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.

🛠️ Refactor suggestion

Non-null serviceConf in public API

RuntimeConfig['moeflowCompanion'] is optional; narrow to NonNullable to reflect post-check guarantees.

 export interface MoeflowCompanionService {
   client: Client;
-  serviceConf: RuntimeConfig['moeflowCompanion'];
+  serviceConf: NonNullable<RuntimeConfig['moeflowCompanion']>;
   multimodalTranslate: typeof multimodalTranslate;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface MoeflowCompanionService {
client: Client;
serviceConf: RuntimeConfig['moeflowCompanion'];
multimodalTranslate: typeof multimodalTranslate;
}
export interface MoeflowCompanionService {
client: Client;
serviceConf: NonNullable<RuntimeConfig['moeflowCompanion']>;
multimodalTranslate: typeof multimodalTranslate;
}
🤖 Prompt for AI Agents
In src/services/moeflow_companion/use_moeflow_companion.ts around lines 18 to
22, the public interface declares serviceConf as
RuntimeConfig['moeflowCompanion'] which is optional; change the type to
NonNullable<RuntimeConfig['moeflowCompanion']> so the API reflects the
post-check guarantee that the value is present, updating the interface
declaration accordingly.

Comment on lines +36 to +45
useAsyncEffect(
async (_, released) => {
if (
!(
serviceConf &&
serviceConf.gradioUrl &&
serviceConf.defaultMultimodalModel
)
) {
serviceRef.current = null;

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.

🛠️ Refactor suggestion

Don’t gate connection on defaultMultimodalModel

defaultMultimodalModel is optional in config and not required for establishing a client. Only require gradioUrl.

🤖 Prompt for AI Agents
In src/services/moeflow_companion/use_moeflow_companion.ts around lines 36 to
45, the connection logic is incorrectly gated on
serviceConf.defaultMultimodalModel; change the condition to only require
serviceConf and serviceConf.gradioUrl. Remove defaultMultimodalModel from the
truthiness check so the client can be established when gradioUrl is present;
keep the existing handling for missing gradioUrl (set serviceRef.current = null
and return early).

Comment on lines +69 to +88
async function multimodalTranslate(
client: Client,
files: Blob[],
targetLang: string,
model: string,
): Promise<TranslatedFile[]> {
// const uploadRes = await client.upload_files(hfSpaceUrl, files)
// files.forEach(file => formData.append('files[]', file));
// debugLogger('Upload response:', uploadRes);
const predictRes = await client.predict('/multimodal_llm_process_files', {
gradio_temp_files: files, // uploadRes.files!.map(handle_file),
model,
target_language: targetLang,
export_moeflow_project_name: 'Hello!!',
});
const [{ files: translated }] = predictRes.data as MoeflowMultimodalResData;

debugLogger('Predict response:', translated);
return translated;
}

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.

🛠️ Refactor suggestion

Avoid hardcoded project name and hard destructuring of response

Parameterize export_moeflow_project_name (or omit when undefined) and guard response shape to prevent runtime errors.

-async function multimodalTranslate(
+async function multimodalTranslate(
   client: Client,
   files: Blob[],
   targetLang: string,
   model: string,
-): Promise<TranslatedFile[]> {
+  projectName?: string,
+): Promise<TranslatedFile[]> {
@@
-  const predictRes = await client.predict('/multimodal_llm_process_files', {
-    gradio_temp_files: files, // uploadRes.files!.map(handle_file),
-    model,
-    target_language: targetLang,
-    export_moeflow_project_name: 'Hello!!',
-  });
-  const [{ files: translated }] = predictRes.data as MoeflowMultimodalResData;
+  const payload: Record<string, unknown> = {
+    gradio_temp_files: files,
+    model,
+    target_language: targetLang,
+  };
+  if (projectName) payload.export_moeflow_project_name = projectName;
+  const predictRes = await client.predict('/multimodal_llm_process_files', payload);
+  const data = predictRes.data as unknown;
+  if (!Array.isArray(data) || !data[0] || typeof data[0] !== 'object' || !('files' in data[0] as any)) {
+    throw new Error('Unexpected multimodal response shape');
+  }
+  const translated = (data as MoeflowMultimodalResData)[0].files;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function multimodalTranslate(
client: Client,
files: Blob[],
targetLang: string,
model: string,
): Promise<TranslatedFile[]> {
// const uploadRes = await client.upload_files(hfSpaceUrl, files)
// files.forEach(file => formData.append('files[]', file));
// debugLogger('Upload response:', uploadRes);
const predictRes = await client.predict('/multimodal_llm_process_files', {
gradio_temp_files: files, // uploadRes.files!.map(handle_file),
model,
target_language: targetLang,
export_moeflow_project_name: 'Hello!!',
});
const [{ files: translated }] = predictRes.data as MoeflowMultimodalResData;
debugLogger('Predict response:', translated);
return translated;
}
async function multimodalTranslate(
client: Client,
files: Blob[],
targetLang: string,
model: string,
projectName?: string,
): Promise<TranslatedFile[]> {
// const uploadRes = await client.upload_files(hfSpaceUrl, files)
// files.forEach(file => formData.append('files[]', file));
// debugLogger('Upload response:', uploadRes);
const payload: Record<string, unknown> = {
gradio_temp_files: files,
model,
target_language: targetLang,
};
if (projectName) payload.export_moeflow_project_name = projectName;
const predictRes = await client.predict('/multimodal_llm_process_files', payload);
const data = predictRes.data as unknown;
if (
!Array.isArray(data) ||
!data[0] ||
typeof data[0] !== 'object' ||
!('files' in (data[0] as any))
) {
throw new Error('Unexpected multimodal response shape');
}
const translated = (data as MoeflowMultimodalResData)[0].files;
debugLogger('Predict response:', translated);
return translated;
}
🤖 Prompt for AI Agents
In src/services/moeflow_companion/use_moeflow_companion.ts around lines 69 to
88, the code hardcodes export_moeflow_project_name and blindly destructures
predictRes.data which can cause runtime errors; change the function to accept an
optional projectName parameter (or read from config/env) and only include
export_moeflow_project_name in the payload when it is defined, and replace the
direct destructuring with defensive checks: validate predictRes.data is the
expected array/object shape, handle missing or unexpected data by returning an
empty array or throwing a clear error, and extract translated files safely (e.g.
check for predictRes.data[0]?.files) before returning.

@jokester

jokester commented Sep 1, 2025

Copy link
Copy Markdown
Member Author

closing: it seems unwise to force user to setup a gradio API

@jokester jokester closed this Sep 1, 2025
@jokester jokester deleted the connect-gradio-companion branch September 1, 2025 16:43
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