Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@

# misc
**/.DS_Store
**/.env.local
**/.env.development.local
**/.env.test.local
**/.env.production.local

**/npm-debug.log*
**/yarn-debug.log*
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ yarn-error.log*
.vscode/*
.idea
stats.html
/public/moeflow-runtime-config.json
63 changes: 2 additions & 61 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.1.19",
"@gradio/client": "1.14.0",
"@gradio/client": "^1.14.0",
"@jokester/ts-commonutil": "^0.6.1",
"@reduxjs/toolkit": "^1.9.7",
"@zip.js/zip.js": "^2.7.60",
Expand Down
6 changes: 6 additions & 0 deletions public/moeflow-runtime-config.sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"comment": "Runtime configuration overrides. See src/configs.tsx",
"moeflowCompanion": {
"gradioUrl": "http://localhost:7860"
}
}
2 changes: 1 addition & 1 deletion src/apis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import axios, {
import qs from 'qs';
import { createElement } from 'react';
import { Icon } from '../components';
import { configs, runtimeConfig } from '@/configs';
import { runtimeConfig } from '@/configs';
import { createDebugLogger } from '@/utils/debug-logger';
import { getIntl } from '@/locales';
import store from '../store';
Expand Down
30 changes: 6 additions & 24 deletions src/components/FileList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ import { Button as AntdButton, Drawer, message, Modal, Spin } from 'antd';
import { CancelToken } from 'axios';
import loadImage from 'blueimp-load-image';
import classNames from 'classnames';
import React, { useEffect, useRef, useState } from 'react';
import { useRef, useState } from 'react';
import { FilePond } from 'react-filepond';
import { useIntl } from 'react-intl';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { Button, EmptyTip, FileItem, List, OutputList } from '.';
import { api, resultTypes } from '../apis';
import { runtimeConfig } from '@/configs';
import {
FILE_NOT_EXIST_REASON,
FILE_SAFE_STATUS,
Expand All @@ -25,7 +24,7 @@ import { setFilesState } from '@/store/file/slice';
import style from '../style';
import { toLowerCamelCase } from '@/utils';
import { can } from '@/utils/user';
import { usePromised } from '@jokester/ts-commonutil/lib/react/hook/use-promised';
import { routes } from '@/pages/routes';

/** 文件列表的属性接口 */
interface FileListProps {
Expand All @@ -49,10 +48,8 @@ export const FileList: FC<FileListProps> = ({
const [loading, setLoading] = useState(true);
const [listMode] = useState<'image' | 'text'>('image');
const [total, setTotal] = useState(0); // 元素总个数
const runtimeConfigLoaded = usePromised(runtimeConfig);
const uploadAPI =
runtimeConfigLoaded.fulfilled &&
`${runtimeConfigLoaded.value.baseURL}/v1/projects/${project.id}/files`;
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);
Comment thread
jokester marked this conversation as resolved.
const platform = useSelector((state: AppState) => state.site.platform);
const isMobile = platform === 'mobile';
Expand All @@ -64,9 +61,6 @@ export const FileList: FC<FileListProps> = ({
const [spinningIDs, setSpinningIDs] = useState<string[]>([]); // 删除请求中
const filePondRef = useRef<FilePond | null>();

const [team, setTeam] = useState<Team>();
const currentTeam = useSelector((state: AppState) => state.team.currentTeam);

const defaultPage = useSelector(
(state: AppState) => state.file.filesState.page,
);
Expand All @@ -80,20 +74,8 @@ export const FileList: FC<FileListProps> = ({
(state: AppState) => state.file.filesState.selectedFileIds,
);

useEffect(() => {
if (!currentTeam) {
api.project.getProject({ id: project.id }).then((result) => {
const data = toLowerCamelCase(result.data);
setTeam(data.team);
});
} else {
setTeam(currentTeam);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [project.id]);

const toTranslator = (file: File) => {
history.push(`/image-translator/${file.id}-${target?.id}`);
history.push(routes.imageTranslator.build(file.id, target.id));
};

const deleteFile = (file: File) => {
Expand Down Expand Up @@ -123,7 +105,7 @@ export const FileList: FC<FileListProps> = ({
setSpinningIDs((ids) => ids.filter((id) => id !== file.id));
});
},
onCancel: () => {},
onCancel: () => { },
okText: formatMessage({ id: 'form.ok' }),
cancelText: formatMessage({ id: 'form.cancel' }),
});
Expand Down
8 changes: 6 additions & 2 deletions src/configs.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { lazyThenable } from '@jokester/ts-commonutil/lib/concurrency/lazy-thenable';

interface RuntimeConfig {
export interface RuntimeConfig {
// base URL for API requests
baseURL: string;

moeflowCompanion?: {
gradioUrl?: string;
};
}

/**
Expand All @@ -16,7 +20,7 @@ export const runtimeConfig = lazyThenable<RuntimeConfig>(async () => {
const overriden: RuntimeConfig = await fetch('/moeflow-runtime-config.json')
.then((res) => res.json())
.catch(() => null);
const merged = {
const merged: RuntimeConfig = {
...{
// defaults
baseURL: process.env.REACT_APP_BASE_URL || '/api/',
Expand Down
7 changes: 6 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ import App from './App';
import './fontAwesome'; // Font Awesome
import './index.css';
import store from './store';
import { setOSName, setPlatform } from './store/site/slice';
import { setOSName, setPlatform, setRuntimeConfig } from './store/site/slice';
import { setUserToken } from './store/user/slice';
import { getToken } from './utils/cookie';
import { OSName, Platform } from './interfaces';
import { runtimeConfig } from './configs';
import {
getDefaultHotKey,
hotKeyInitialState,
HotKeyState,
setHotKey,
} from './store/hotKey/slice';
import { loadHotKey } from './utils/storage';
import { createDebugLogger } from './utils/debug-logger';
const debugLogger = createDebugLogger('app');

// 时间插件
if (false && process.env.NODE_ENV === 'development') {
Expand Down Expand Up @@ -55,8 +58,10 @@ for (const hotKeyName in hotKeyInitialState) {
}

async function mountApp() {
store.dispatch(setRuntimeConfig(await runtimeConfig));
Comment thread
jokester marked this conversation as resolved.
const { intlMessages, locale, antdLocale, antdValidateMessages } =
await initI18n;
debugLogger('initial state', store.getState());
/**
* Set user token from cookie
*/
Expand Down
10 changes: 5 additions & 5 deletions src/services/moeflow_companion/TranslateCompanion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { createMoeflowProjectZip, LPFile } from '../labelplus_packager';
import { FailureResults } from '@/apis';
import { measureImgSize } from '@jokester/ts-commonutil/lib/web/measure-img';
import { clamp } from 'lodash-es';
import { BBox, mitPreprocess, TextQuad } from '@/apis/mit_preprocess';
import { BBox, mitPreprocess, TextQuad } from './mit_preprocess';
import { ResourcePool } from '@jokester/ts-commonutil/lib/concurrency/resource-pool';

const MAX_FILE_COUNT = 30;
Expand Down Expand Up @@ -248,10 +248,10 @@ export const DemoOcrFiles: FC<{}> = (props) => {
setWorking((s) =>
s?.nonce === initState.nonce
? {
...s,
finished: Math.max(s.finished, finished),
numPages: total,
}
...s,
finished: Math.max(s.finished, finished),
numPages: total,
}
: s,
),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { request } from '.';
import { uploadRequest } from './_request';
import { request } from '../../apis';
import { uploadRequest } from '../../apis/_request';
import { wait } from '@jokester/ts-commonutil/lib/concurrency/timing';

const mitApiPrefix = `/v1/mit`;
Expand Down
49 changes: 49 additions & 0 deletions src/services/moeflow_companion/use_moeflow_companion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useState, useRef } from 'react';
import { Client } from '@gradio/client';
import { useAsyncEffect } from '@jokester/ts-commonutil/lib/react/hook/use-async-effect';
import { useSelector } from 'react-redux';
import { AppState } from '@/store';
import { createDebugLogger } from '@/utils/debug-logger';

export const moeflowCompanionServiceState = {
disabled: 'disabled',
connecting: 'connecting',
connected: 'connected',
disconnected: 'disconnected',
} as const;

Comment on lines +9 to +15

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Strengthen typing for state and public API

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

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

Also applies to: 17-21, 46-46

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

const logger = createDebugLogger('service:moeflow_companion');

export function useMoeflowCompanion() {
const clientRef = useRef<Client | null>(null);
const [clientState, setClientState] = useState<string>(
moeflowCompanionServiceState.disabled,
);
const serviceConf = useSelector(
(s: AppState) => s.site.runtimeConfig.moeflowCompanion,
);
Comment on lines +32 to +34

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

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

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

Apply:

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

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


useAsyncEffect(
async (_, released) => {
if (!serviceConf?.gradioUrl) {
clientRef.current = null;
setClientState(moeflowCompanionServiceState.disabled);
return;
}
try {
const client = await Client.connect(serviceConf.gradioUrl);
clientRef.current = client;
setClientState(moeflowCompanionServiceState.connected);
released.then(() => client.close());
} catch (e) {
logger('error connecting', e, serviceConf.gradioUrl);
clientRef.current = null;
setClientState(moeflowCompanionServiceState.disconnected);
}
},
[serviceConf],
);
return [clientState, clientRef.current] as const;
}

export async function x(client: Client) {}
2 changes: 1 addition & 1 deletion src/store/project/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function* setCurrentProjectWorker(
configs: { cancelToken },
});
yield put(setCurrentProject(toLowerCamelCase(result.data)));
} catch (error) {
} catch (error: any) {
error.default();
} finally {
Comment thread
jokester marked this conversation as resolved.
if (yield cancelled()) {
Expand Down
4 changes: 2 additions & 2 deletions src/store/projectSet/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function* setCurrentProjectSetWorker(
) {
// 清空当前 projectSet
yield put(clearCurrentProjectSet());
const projectSets = yield select(
const projectSets: UserProjectSet[] = yield select(
(state: AppState) => state.projectSet.projectSets,
);
const projectSet = projectSets.find(
Expand All @@ -35,7 +35,7 @@ function* setCurrentProjectSetWorker(
configs: { cancelToken },
});
yield put(setCurrentProjectSet(toLowerCamelCase(result.data)));
} catch (error) {
} catch (error: any) {
error.default();
Comment on lines +38 to 39

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

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

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

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

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

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

} finally {
if (yield cancelled()) {
Expand Down
Loading
Loading