Add optional moeflow-companion service#38
Conversation
WalkthroughIntroduces 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Possibly related PRs
Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/configs.tsx (1)
19-29: Fix runtime crash when /moeflow-runtime-config.json is missing or non-JSONoverriden 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 READMEThis 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 annotationDefine 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 consistentlyEither 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 expectationsConsider 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 TLATop-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 assertionsUsing 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.
📒 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-originIf 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 correctThe 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 robustThis 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 sliceImports look correct and match the new store action and config export.
Also applies to: 18-18
26-27: LGTM: lightweight startup loggerNon-intrusive and useful for initial state troubleshooting.
64-64: LGTM: initial state logging after i18n initThis executes after early dispatches; order looks fine.
src/store/site/slice.ts (2)
36-38: LGTM: reducer for runtimeConfigSimple assignment is correct; immutability is handled by RTK.
47-47: Exporting setRuntimeConfig is correctMatches usage in index.tsx.
| } catch (error: any) { | ||
| error.default(); |
There was a problem hiding this comment.
🛠️ 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.
| } 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.
There was a problem hiding this comment.
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.allsaves 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.
debugformats 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.
📒 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 runtimeConfiglater matches the lazy-thenable pattern.
26-27: Scoped debug logger is a good addition.Nice namespacing with
moeflow:app.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (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 suggestedpnpm prettier/yarn prettiercommands 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 UISet 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 cleanupClear 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
connectresolves after release,setClientStatecan 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 bindingsHandle both
Errorand 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 timeoutsConsider 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/clientexposes an AbortSignal forClient.connectso 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: Narrowpayloadtype.
objectis 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 narrowpayloadfor 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/mitcouples 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 unusedrunningparameter or wire cancellation.
runningis never read insidestartTranslateFile; 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=gpt4in 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.
⛔ Files ignored due to path filters (1)
package-lock.jsonis 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
requestis exported fromsrc/apis/index.ts:164anduploadRequestfromsrc/apis/_request.ts:4. EnsureuploadRequestuses the same configured Axios instance (baseURL, interceptors, auth, timeouts) asrequest.src/services/moeflow_companion/TranslateCompanion.tsx (1)
251-254: Progress updater tweak LGTM.Using
Math.maxavoids regressions on out-of-order completions; updatingnumPagesfromtotalkeeps state consistent.
| export const moeflowCompanionServiceState = { | ||
| disabled: 'disabled', | ||
| connecting: 'connecting', | ||
| connected: 'connected', | ||
| disconnected: 'disconnected', | ||
| } as const; | ||
|
|
There was a problem hiding this comment.
🛠️ 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.
| const serviceConf = useSelector( | ||
| (s: AppState) => s.site.runtimeConfig.moeflowCompanion, | ||
| ); |
There was a problem hiding this comment.
🛠️ 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
There was a problem hiding this comment.
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.createProjectSetis missing insrc/locales/en.json; add this key (and verifysite.projectSet) in bothsrc/locales/en.jsonandsrc/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.filesrc/components/FileList.tsx (2)
259-266: Avoid mutating items in setState mappersMutating 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 crashesfile.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 connectNarrow 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
createDebugLoggerimport anddebugLoggerinstantiation.- Either drop the unused
companionClient?: Clientprop or wire it up where it’s consumed.Makefile (1)
1-14: Add .PHONY and switch to npx tsx
Add a.PHONYdeclaration to avoid collisions and replace hard-codednode_modules/.bin/tsxwithnpx tsxso 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.tssrc/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 Setsrc/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 paramsIf 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 actionEither 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 objectExpose 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 readIf intentional (future refresh), add a TODO; otherwise remove.
241-353: Optional: hide uploader until token and baseURL are readyPrevents 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 nameValidate 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 nameKeep 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 constantEases 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.
📒 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 — LGTMNo 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.
| title={formatMessage({ id: 'project.export' })} | ||
| key="project" | ||
| width="30%" |
There was a problem hiding this comment.
💡 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.
| "fileList.changeMode": "Toggle Display Mode", | ||
| "file.parseNotStart": "Auto Mark as Not Started", | ||
| "fileList.ocrButtonTip": "Image Auto Tagging", | ||
| "fileList.autoTranslate": "Detect marks and Translate", |
There was a problem hiding this comment.
💡 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/localesLength 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.yamlto syncfileList.aiTranslateandfileList.aiTranslateTipacross 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.
| "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.
| zhCn: (可选) 通过 LabalPlus “翻译数据.txt”创建: | ||
| en: '(Optional) Import marks from LabelPlus txt:' | ||
| site.delete: |
There was a problem hiding this comment.
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.
| 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”创建:.
| "output.outputPartialExclude": "导出除了已选中的 {count} 个文件", | ||
| "output.invert": "反选", | ||
| "project.createViaLabelplus": "通过 LabalPlus “翻译数据.txt”创建:", | ||
| "project.createViaLabelplus": "(可选) 通过 LabalPlus “翻译数据.txt”创建:", |
There was a problem hiding this comment.
拼写错误: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.
| "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).
| useHotKey( | ||
| { | ||
| disabled: Boolean(file?.id), | ||
| ignoreKeyboardElement: false, | ||
| }, | ||
| () => {}, | ||
| [file?.id], | ||
| ); |
There was a problem hiding this comment.
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.
| 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.
| @@ -1,4 +1,4 @@ | |||
| import React, { useEffect } from 'react'; | |||
| import { useEffect } from 'react'; | |||
There was a problem hiding this comment.
💡 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.
| noDebounce?: boolean; | ||
| } | ||
|
|
||
| export interface Singl {} |
There was a problem hiding this comment.
💡 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' -C2Length 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' -C2Length 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.
| 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.
There was a problem hiding this comment.
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: Awaitentry.getData(), useTextWriterfor text files, and close the ZipReader
- Replace
entry.getData?.(writer); project = await writer.getData();
with
project = await entry.getData(new zip.TextWriter());
(and likewise fortranslations.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 cursorWhen 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-countis 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: IncludeprojectSetIDin thecreateProjectcall
The API wrappercreateProject({ teamID, data })doesn’t accept or sendprojectSetID, so new projects won’t be tied to the intended project‐set. Update the call inProjectCreateForm.tsx(around lines 72–77) to includeprojectSetID(and extendCreateProjectDataor 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 undersrc/servicesandsrc/pagesis deep-importing from@/components/shared/*and@/components/project/*; add the missing exports tosrc/components/index.tsand update those imports to@/componentsinstead 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: RenamecolorDisibledtocolorDisabledacross the Button component and its usagesThe 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 tocolorDisabled.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.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 accessibilityContent 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 positionTypepositionType 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 importModal is imported but not used.
-import { Modal } from 'antd'; +// import { Modal } from 'antd';
4-6: Implement a minimal usable modal or drop the stubReturning 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-contentstretch 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 colonUsing 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 unmountfetchOutputs 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 signatureAvoids 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 | Projectshould just beProject.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).
showFinishModalisn’t used; either wireonFinish={showFinishModal}or remove the modal code.src/components/project/FileList.tsx (7)
72-72: Initialize ref with null.Prevents transient
undefinedreads.- 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.
serverIdmay 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.
📒 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 ofstyle.*detected across.ts/.tsxfiles; compile-time readonly enforcement confirmed.src/components/unused/FileCover.tsx (1)
3-4: Path updates look correct.
../../interfacesand../../styleresolve as expected fromcomponents/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: UpdatedAvatarUploadpath is correct.No functional change; component usage remains the same.
src/components/TeamSettingBase.tsx (1)
20-20: Shared import path forAvatarUploadis 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 thatfileList.aiTranslateandfileList.aiTranslateTipexist in messages.yaml and in both zh-cn.json and en.json, with no strayautoTranslateentries.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:disibledmatches 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/Spinor the@/components/shared/Spinalias and no occurrences of the oldfrom "./Spin"remain.src/components/shared/ListItem.tsx (1)
5-8: Approve code changes Confirmedsrc/style.tsexports a default object (line 98), soimport 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 abaseUrl—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
moduleNameMapperentry^@/(.*)$→<rootDir>/src/$1src/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 goodConsolidates imports to "@/..." paths and shared Spin. No functional issues spotted.
src/components/project/OutputList.tsx (1)
5-15: React import/runtime + alias migration looks goodsrc/components/unused/ImageOCRProgress.tsx (2)
4-8: Import path reorg is fineAligned with barrel exports and alias scheme.
67-73: antd Tooltip prop name may be outdatedNewer 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 typeHelps 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
apiimport align with the new structure.src/components/index.ts (3)
31-35: LGTM: coherent List re-exports (component + constant).*Nice that
LIST_ITEM_DEFAULT_HEIGHTis exported alongsideListItem; keeps API discoverable.
41-41: Verify resolver handlesproject-listpath 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.
| import { Spin } from './shared/Spin'; | ||
| import { LIST_ITEM_DEFAULT_HEIGHT } from '@/components/shared/ListItem'; | ||
|
|
There was a problem hiding this comment.
💡 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" srcLength 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']).
| 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); |
There was a problem hiding this comment.
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.
| 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 '..'; |
There was a problem hiding this comment.
🛠️ 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.
| 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'; |
There was a problem hiding this comment.
💡 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" -SLength 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.
|
There was a problem hiding this comment.
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;
totalexpects 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 byrecord.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:colorDisibledlikely meantcolorDisabled(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 reconnectsOptional-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
ListPageSpechas 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 FCAvoids emitting a runtime import for a type.
-import { FC } from '@/interfaces'; +import type { FC } from '@/interfaces';
68-75: Fix typo: rename handelChange → handleChangeImproves 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 FCKeeps 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 FCMinor emit/treeshake win.
-import { FC } from '@/interfaces'; +import type { FC } from '@/interfaces';src/components/shared/ContentItem.tsx (1)
3-4: Type-only import for FCSame 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 FCConsistent 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 typeAvoid 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 statusuploading 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 nitProgress usually includes 0. Update the note to 0-100.
- uploadPercent?: number; // 1-100 + uploadPercent?: number; // 0-100src/services/moeflow_companion/use_moeflow_companion.ts (2)
78-83: Future-proof: make endpoint path a constantIf 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 consistencyConsider 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 FCPrevents 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 FCMinor 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 importsCAPTCHAInputRef 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 FCSmall 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
itembreaks 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 typefor 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 possiblyTypeRadioGroupPropsif imported from barrel later) are types—useimport typeto 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 fieldssrc/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 utilityThe import
from '../../utils/user'could potentially be updated to use the path alias pattern likefrom '@/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
onOkandonCancelhandlers 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.
📒 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 goodSwitching to named imports is consistent with the JSX runtime and avoids unused default React imports.
49-66: Confirm deps suppression is intentionalEffect 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 directruntimeConfig.*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’spackage.jsonshows 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 goodShared-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’scompilerOptions.jsxis set toreact-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@/componentsimports: mergedSpininto 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
Menuimport from antd and migration to alias-based imports aligns with the project's refactoring goals. OnlyMenuPropsis 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
IconandSpinimports from the same@/componentsmodule- 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 insrc/components/index.tsat 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 toproject.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 –getUserTeamsis defined and exported on theapi.teamnamespace.src/components/team/TeamSearchList.tsx (2)
2-2: LGTM: React import modernizationThe shift to named import for
useStateis consistent with the project-wide modernization pattern.Also applies to: 6-6
10-18: LGTM: Path alias migration executed correctlyThe 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_HEIGHTimport updated to specific path@/components/shared/ListItemAll import paths appear correct and functional.
src/components/project/ProjectTargetList.tsx (5)
4-4: LGTM: React import pattern modernizedNamed import for
useStatefollows the project-wide pattern.
7-18: LGTM: Path aliases and API refactoringThe changes show proper migration to path aliases and API restructuring:
- Changed from default
apiimport 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 namingThe rename from
itemstotargetLangsis more descriptive and improves code readability. The variable correctly stores the target language data.
56-74: LGTM: API namespace usage and state managementThe API call correctly uses the new
api.targetnamespace structure. The data flow maintains consistency:
- Results are mapped through
toLowerCamelCase- Stored in
targetLangsstateonLoadcallback receives the processed items arrayState variable usage is consistent throughout.
Also applies to: 98-98
126-128: LGTM: Simplified empty tip creatorThe arrow function syntax for
emptyTipCreateris more concise and follows modern JavaScript patterns.src/components/shared-form/MemberList.tsx (1)
8-26: LGTM: Comprehensive path alias migrationExcellent 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
canutility from../../utils/userAll 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
handleFileAddfunction 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
loadPagefunction 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.
| @@ -205,7 +216,7 @@ export async function request<T = unknown>( | |||
There was a problem hiding this comment.
🛠️ 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.
| 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.
| files.map((f, idx) => fileLimiter.use(() => translateFile(f, idx))), | ||
| ]); |
There was a problem hiding this comment.
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>[].
| if (refetched.data.sourceCount) { | ||
| setFileState(f, 'skip: source count not 0'); | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| x: (tb.left + tb.right) / 2 / r.image_w, | ||
| y: (tb.top + tb.bottom) / 2 / r.image_h, |
There was a problem hiding this comment.
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.
| 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`.
| 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'; |
There was a problem hiding this comment.
💡 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 || trueLength of output: 525
Configure '@' path alias in bundler, test runner & ESLint
- tsconfig.json already defines
"@/*": ["./src/*"] - Bundler: add
resolve.aliasfor'@' → 'src'in vite.config.js, webpack.config.js or next.config.js - Tests: add Jest
moduleNameMapper(or Vitestresolve.alias) mapping^@/(.*)$ → <rootDir>/src/$1 - ESLint: add
import/resolver.aliassettings 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.
| 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'; |
There was a problem hiding this comment.
💡 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'; |
There was a problem hiding this comment.
💡 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.jsonLength 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.jsonLength 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 -C2Length 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.
| export interface MoeflowCompanionService { | ||
| client: Client; | ||
| serviceConf: RuntimeConfig['moeflowCompanion']; | ||
| multimodalTranslate: typeof multimodalTranslate; | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| 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.
| useAsyncEffect( | ||
| async (_, released) => { | ||
| if ( | ||
| !( | ||
| serviceConf && | ||
| serviceConf.gradioUrl && | ||
| serviceConf.defaultMultimodalModel | ||
| ) | ||
| ) { | ||
| serviceRef.current = null; |
There was a problem hiding this comment.
🛠️ 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).
| 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; | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| 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.
|
closing: it seems unwise to force user to setup a gradio API |




service : https://github.com/moeflow-com/manga-image-translator/tree/moeflow-companion-main/moeflow_companion
Summary by CodeRabbit
New Features
Documentation
Localization
Refactor
Chores