|
|
@@ -0,0 +1,620 @@
|
|
|
+**使用中文与用户对话**
|
|
|
+
|
|
|
+# LabelStudio 编辑器集成规范
|
|
|
+
|
|
|
+## 概述
|
|
|
+
|
|
|
+本文档记录了在 React 应用中集成 LabelStudio 编辑器的完整流程、常见问题和解决方案。这些经验来自实际项目中遇到的样式问题和配置挑战。
|
|
|
+
|
|
|
+## 核心原则
|
|
|
+
|
|
|
+1. **动态导入编辑器**:使用 `import('@humansignal/editor')` 动态加载,不要静态导入
|
|
|
+2. **CSS 模块前缀**:必须配置 webpack 添加 `lsf-` 前缀
|
|
|
+3. **样式加载顺序**:只导入 UI 库样式,让编辑器样式自动加载
|
|
|
+4. **参考成功案例**:遵循 playground 和 labelstudio 应用的实现方式
|
|
|
+
|
|
|
+## 项目结构
|
|
|
+
|
|
|
+```
|
|
|
+web/
|
|
|
+├── apps/
|
|
|
+│ ├── lq_label/ # 你的应用
|
|
|
+│ ├── playground/ # 参考:简单的编辑器集成
|
|
|
+│ └── labelstudio/ # 参考:完整的 Label Studio 应用
|
|
|
+├── libs/
|
|
|
+│ ├── editor/ # LabelStudio 编辑器库
|
|
|
+│ └── ui/ # UI 组件库
|
|
|
+└── webpack.config.js # 根配置(包含 CSS 前缀处理)
|
|
|
+```
|
|
|
+
|
|
|
+## 集成步骤
|
|
|
+
|
|
|
+### 1. Webpack 配置(最关键)
|
|
|
+
|
|
|
+在应用的 `webpack.config.js` 中添加 CSS 模块配置:
|
|
|
+
|
|
|
+```javascript
|
|
|
+const { composePlugins, withNx } = require('@nx/webpack');
|
|
|
+const { withReact } = require('@nx/react');
|
|
|
+const webpack = require('webpack');
|
|
|
+
|
|
|
+const css_prefix = 'lsf-';
|
|
|
+
|
|
|
+module.exports = composePlugins(
|
|
|
+ withNx({ skipTypeChecking: true }),
|
|
|
+ withReact(),
|
|
|
+ (config) => {
|
|
|
+ // 1. 定义 CSS_PREFIX 环境变量
|
|
|
+ config.plugins = [
|
|
|
+ ...config.plugins,
|
|
|
+ new webpack.DefinePlugin({
|
|
|
+ 'process.env.CSS_PREFIX': JSON.stringify(css_prefix),
|
|
|
+ }),
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 2. 配置 CSS 模块添加 lsf- 前缀
|
|
|
+ config.module.rules.forEach((rule) => {
|
|
|
+ const testString = rule.test?.toString() || '';
|
|
|
+
|
|
|
+ if (rule.test?.toString().match(/scss|sass/) && !testString.includes('.module')) {
|
|
|
+ const r = rule.oneOf?.filter((r) => {
|
|
|
+ if (!r.use) return false;
|
|
|
+ const testString = r.test?.toString() || '';
|
|
|
+ if (testString.match(/module|raw|antd/)) return false;
|
|
|
+ return testString.match(/scss|sass/) &&
|
|
|
+ r.use.some((u) => u.loader && u.loader.includes('css-loader'));
|
|
|
+ });
|
|
|
+
|
|
|
+ r?.forEach((_r) => {
|
|
|
+ const cssLoader = _r.use.find((use) =>
|
|
|
+ use.loader && use.loader.includes('css-loader')
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!cssLoader) return;
|
|
|
+
|
|
|
+ const isSASS = _r.use.some((use) =>
|
|
|
+ use.loader && use.loader.match(/sass|scss/)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (isSASS) _r.exclude = /node_modules/;
|
|
|
+
|
|
|
+ if (cssLoader.options) {
|
|
|
+ cssLoader.options.modules = {
|
|
|
+ localIdentName: `${css_prefix}[local]`,
|
|
|
+ getLocalIdent(_ctx, _ident, className) {
|
|
|
+ if (className.includes('ant')) return className;
|
|
|
+ },
|
|
|
+ };
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 3. 其他必要配置
|
|
|
+ config.experiments = {
|
|
|
+ ...config.experiments,
|
|
|
+ asyncWebAssembly: true,
|
|
|
+ syncWebAssembly: true,
|
|
|
+ topLevelAwait: true,
|
|
|
+ };
|
|
|
+
|
|
|
+ config.resolve = {
|
|
|
+ ...config.resolve,
|
|
|
+ fallback: {
|
|
|
+ path: false,
|
|
|
+ fs: false,
|
|
|
+ crypto: false,
|
|
|
+ stream: false,
|
|
|
+ buffer: false,
|
|
|
+ util: false,
|
|
|
+ process: false,
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ return config;
|
|
|
+ }
|
|
|
+);
|
|
|
+```
|
|
|
+
|
|
|
+**关键点**:
|
|
|
+- `css_prefix = 'lsf-'`:所有 LabelStudio 的 CSS 类名前缀
|
|
|
+- `localIdentName: 'lsf-[local]'`:将 `.label` 转换为 `.lsf-label`
|
|
|
+- 排除 `.module.scss` 文件(它们有自己的命名规则)
|
|
|
+- 保留 `ant` 开头的类名(Ant Design 组件)
|
|
|
+
|
|
|
+### 2. 样式导入(main.tsx)
|
|
|
+
|
|
|
+**正确的方式**:
|
|
|
+```typescript
|
|
|
+// main.tsx
|
|
|
+import { StrictMode } from 'react';
|
|
|
+import * as ReactDOM from 'react-dom/client';
|
|
|
+import { BrowserRouter } from 'react-router-dom';
|
|
|
+import App from './app/app';
|
|
|
+
|
|
|
+// 只导入 UI 库样式
|
|
|
+// LabelStudio 编辑器样式会在动态导入时自动加载
|
|
|
+import '../../../libs/ui/src/styles.scss';
|
|
|
+import '../../../libs/ui/src/tailwind.css';
|
|
|
+
|
|
|
+const root = ReactDOM.createRoot(
|
|
|
+ document.getElementById('root') as HTMLElement
|
|
|
+);
|
|
|
+root.render(
|
|
|
+ <StrictMode>
|
|
|
+ <BrowserRouter>
|
|
|
+ <App />
|
|
|
+ </BrowserRouter>
|
|
|
+ </StrictMode>
|
|
|
+);
|
|
|
+```
|
|
|
+
|
|
|
+**错误的方式**(不要这样做):
|
|
|
+```typescript
|
|
|
+// ❌ 不要静态导入 editor 的全局样式
|
|
|
+import '../../../libs/editor/src/assets/styles/global.scss';
|
|
|
+```
|
|
|
+
|
|
|
+### 3. 组件实现
|
|
|
+
|
|
|
+参考 playground 的 `PreviewPanel` 组件实现:
|
|
|
+
|
|
|
+```typescript
|
|
|
+import React, { useEffect, useState, useRef } from 'react';
|
|
|
+import { onSnapshot } from 'mobx-state-tree';
|
|
|
+
|
|
|
+// 清除 localStorage
|
|
|
+if (typeof localStorage !== 'undefined') {
|
|
|
+ localStorage.removeItem('labelStudio:settings');
|
|
|
+}
|
|
|
+
|
|
|
+export const EditorComponent: React.FC = () => {
|
|
|
+ const [editorReady, setEditorReady] = useState(false);
|
|
|
+ const [error, setError] = useState<string | null>(null);
|
|
|
+
|
|
|
+ const editorContainerRef = useRef<HTMLDivElement>(null);
|
|
|
+ const lsfInstanceRef = useRef<any>(null);
|
|
|
+ const rafIdRef = useRef<number | null>(null);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ let LabelStudio: any;
|
|
|
+ let dependencies: any;
|
|
|
+ let snapshotDisposer: any;
|
|
|
+
|
|
|
+ function cleanup() {
|
|
|
+ if (typeof window !== 'undefined' && (window as any).LabelStudio) {
|
|
|
+ delete (window as any).LabelStudio;
|
|
|
+ }
|
|
|
+ setEditorReady(false);
|
|
|
+ if (lsfInstanceRef.current) {
|
|
|
+ try {
|
|
|
+ lsfInstanceRef.current.destroy();
|
|
|
+ } catch {
|
|
|
+ // Ignore cleanup errors in HMR scenarios
|
|
|
+ }
|
|
|
+ lsfInstanceRef.current = null;
|
|
|
+ }
|
|
|
+ if (rafIdRef.current !== null) {
|
|
|
+ cancelAnimationFrame(rafIdRef.current);
|
|
|
+ rafIdRef.current = null;
|
|
|
+ }
|
|
|
+ if (snapshotDisposer) {
|
|
|
+ snapshotDisposer();
|
|
|
+ snapshotDisposer = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async function loadLSF() {
|
|
|
+ try {
|
|
|
+ // 动态导入 LabelStudio
|
|
|
+ dependencies = await import('@humansignal/editor');
|
|
|
+ LabelStudio = dependencies.LabelStudio;
|
|
|
+
|
|
|
+ if (!LabelStudio) {
|
|
|
+ setError('编辑器加载失败');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanup();
|
|
|
+ setEditorReady(true);
|
|
|
+
|
|
|
+ // 初始化 LabelStudio 实例
|
|
|
+ setTimeout(() => {
|
|
|
+ if (!editorContainerRef.current) return;
|
|
|
+
|
|
|
+ lsfInstanceRef.current = new LabelStudio(editorContainerRef.current, {
|
|
|
+ config: yourConfig,
|
|
|
+ task: yourTask,
|
|
|
+ interfaces: [
|
|
|
+ 'panel',
|
|
|
+ 'update',
|
|
|
+ 'submit',
|
|
|
+ 'controls',
|
|
|
+ 'side-column',
|
|
|
+ 'annotations:menu',
|
|
|
+ 'annotations:add-new',
|
|
|
+ 'annotations:delete',
|
|
|
+ 'predictions:menu',
|
|
|
+ ],
|
|
|
+ instanceOptions: {
|
|
|
+ reactVersion: 'v18',
|
|
|
+ },
|
|
|
+ settings: {
|
|
|
+ forceBottomPanel: true,
|
|
|
+ collapsibleBottomPanel: true,
|
|
|
+ defaultCollapsedBottomPanel: false,
|
|
|
+ fullscreen: false,
|
|
|
+ },
|
|
|
+ onStorageInitialized: (LS: any) => {
|
|
|
+ const initAnnotation = () => {
|
|
|
+ const as = LS.annotationStore;
|
|
|
+ const annotation = as.createAnnotation();
|
|
|
+ as.selectAnnotation(annotation.id);
|
|
|
+
|
|
|
+ if (annotation) {
|
|
|
+ snapshotDisposer = onSnapshot(annotation, () => {
|
|
|
+ // 处理标注更新
|
|
|
+ const result = annotation.serializeAnnotation();
|
|
|
+ console.log('Annotation updated:', result);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+ setTimeout(initAnnotation);
|
|
|
+ },
|
|
|
+ });
|
|
|
+ });
|
|
|
+ } catch (err: any) {
|
|
|
+ console.error('Error loading LabelStudio:', err);
|
|
|
+ setError(err.message || '初始化编辑器失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ rafIdRef.current = requestAnimationFrame(() => {
|
|
|
+ loadLSF();
|
|
|
+ });
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ cleanup();
|
|
|
+ };
|
|
|
+ }, [yourConfig, yourTask]);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="editor-container">
|
|
|
+ {editorReady ? (
|
|
|
+ <div
|
|
|
+ ref={editorContainerRef}
|
|
|
+ className="w-full h-full flex flex-col"
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <div>加载中...</div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### 4. 组件样式(.module.scss)
|
|
|
+
|
|
|
+```scss
|
|
|
+.root {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100%;
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ // LabelStudio 样式覆盖
|
|
|
+ :global(.lsf-wrapper) {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ :global(.lsf-editor) {
|
|
|
+ min-width: 320px;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 修复 relations-overlay 橙色遮罩问题
|
|
|
+ :global(.relations-overlay) {
|
|
|
+ background: transparent !important;
|
|
|
+ fill: none !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :global(.lsf-container) {
|
|
|
+ background: transparent !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.editorContainer {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ min-height: 0;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 常见问题和解决方案
|
|
|
+
|
|
|
+### 问题 1:样式完全不显示
|
|
|
+
|
|
|
+**症状**:
|
|
|
+- 标签按钮没有颜色
|
|
|
+- 按钮没有边框和背景
|
|
|
+- 整体看起来像是没有加载 CSS
|
|
|
+
|
|
|
+**原因**:
|
|
|
+- Webpack 配置缺少 CSS 模块的 `lsf-` 前缀处理
|
|
|
+- HTML 元素有 `lsf-label` 类名,但 CSS 只有 `.label` 选择器
|
|
|
+
|
|
|
+**解决方案**:
|
|
|
+1. 在 webpack.config.js 中添加 CSS 模块配置(见上文)
|
|
|
+2. 重启开发服务器(必须重启!)
|
|
|
+3. 硬刷新浏览器(Ctrl+Shift+R)
|
|
|
+
|
|
|
+**验证方法**:
|
|
|
+```bash
|
|
|
+# 1. 检查 HTML 元素
|
|
|
+<div class="lsf-label lsf-label_selected">Positive</div>
|
|
|
+
|
|
|
+# 2. 检查 CSS 文件(Network 标签)
|
|
|
+.lsf-label {
|
|
|
+ /* 样式定义 */
|
|
|
+}
|
|
|
+
|
|
|
+# 3. 两者应该匹配
|
|
|
+```
|
|
|
+
|
|
|
+### 问题 2:橙色到粉色的渐变遮罩
|
|
|
+
|
|
|
+**症状**:
|
|
|
+- 整个编辑器被一个橙色渐变覆盖
|
|
|
+- 无法点击或交互
|
|
|
+
|
|
|
+**原因**:
|
|
|
+- `relations-overlay` 是 LabelStudio 的关系标注覆盖层(SVG)
|
|
|
+- 默认有一个背景色
|
|
|
+
|
|
|
+**解决方案**:
|
|
|
+在组件样式中添加:
|
|
|
+```scss
|
|
|
+:global(.relations-overlay) {
|
|
|
+ background: transparent !important;
|
|
|
+ fill: none !important;
|
|
|
+}
|
|
|
+
|
|
|
+:global(.lsf-container) {
|
|
|
+ background: transparent !important;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 问题 3:静态导入 editor 样式导致冲突
|
|
|
+
|
|
|
+**症状**:
|
|
|
+- 样式加载但不生效
|
|
|
+- 或者样式冲突
|
|
|
+
|
|
|
+**原因**:
|
|
|
+- 静态导入 `editor/src/assets/styles/global.scss` 会导致样式加载顺序问题
|
|
|
+- 与 Tailwind CSS 冲突
|
|
|
+
|
|
|
+**解决方案**:
|
|
|
+- 不要静态导入 editor 的全局样式
|
|
|
+- 让样式随动态导入自动加载
|
|
|
+- 参考 playground 和 labelstudio 应用的实现
|
|
|
+
|
|
|
+### 问题 4:TypeScript 类型错误
|
|
|
+
|
|
|
+**症状**:
|
|
|
+```
|
|
|
+Could not find a declaration file for module '@humansignal/editor'
|
|
|
+```
|
|
|
+
|
|
|
+**解决方案**:
|
|
|
+在 webpack 配置中添加:
|
|
|
+```javascript
|
|
|
+withNx({
|
|
|
+ skipTypeChecking: true,
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+### 问题 5:音频解码器错误
|
|
|
+
|
|
|
+**症状**:
|
|
|
+```
|
|
|
+Can't resolve '@humansignal/audio-file-decoder'
|
|
|
+```
|
|
|
+
|
|
|
+**解决方案**:
|
|
|
+1. 创建空模块:
|
|
|
+```javascript
|
|
|
+// src/utils/empty-module.js
|
|
|
+module.exports = {};
|
|
|
+```
|
|
|
+
|
|
|
+2. 在 webpack 配置中添加别名:
|
|
|
+```javascript
|
|
|
+config.resolve = {
|
|
|
+ ...config.resolve,
|
|
|
+ alias: {
|
|
|
+ ...config.resolve.alias,
|
|
|
+ '@humansignal/audio-file-decoder': require.resolve('./src/utils/empty-module.js'),
|
|
|
+ },
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+## 调试技巧
|
|
|
+
|
|
|
+### 1. 使用浏览器开发者工具
|
|
|
+
|
|
|
+**检查 HTML 元素**:
|
|
|
+```
|
|
|
+右键点击元素 → 检查
|
|
|
+查看类名:应该是 lsf-label, lsf-button 等
|
|
|
+```
|
|
|
+
|
|
|
+**检查 CSS 文件**:
|
|
|
+```
|
|
|
+Network 标签 → 找到 libs_editor_src_index_js.css
|
|
|
+搜索 .lsf-label
|
|
|
+应该能找到样式定义
|
|
|
+```
|
|
|
+
|
|
|
+**检查样式应用**:
|
|
|
+```
|
|
|
+Elements 标签 → Styles 面板
|
|
|
+查看 .lsf-label 的样式是否被应用
|
|
|
+检查是否有被覆盖的样式
|
|
|
+```
|
|
|
+
|
|
|
+### 2. 控制台日志
|
|
|
+
|
|
|
+在组件中添加详细日志:
|
|
|
+```typescript
|
|
|
+console.log('开始加载 LabelStudio 编辑器...');
|
|
|
+console.log('LabelStudio 加载成功');
|
|
|
+console.log('初始化 LabelStudio 实例...');
|
|
|
+console.log('Storage 初始化完成');
|
|
|
+console.log('Annotation 创建成功');
|
|
|
+console.log('Annotation 更新:', result);
|
|
|
+```
|
|
|
+
|
|
|
+### 3. 创建测试页面
|
|
|
+
|
|
|
+创建一个独立的测试页面来隔离问题:
|
|
|
+```typescript
|
|
|
+// src/views/editor-test/editor-test.tsx
|
|
|
+// 使用简单的配置和数据
|
|
|
+// 添加调试面板显示状态和错误
|
|
|
+```
|
|
|
+
|
|
|
+## 最佳实践
|
|
|
+
|
|
|
+### 1. 参考成功案例
|
|
|
+
|
|
|
+**Playground 应用**(推荐参考):
|
|
|
+- 简单的编辑器集成
|
|
|
+- 清晰的组件结构
|
|
|
+- 位置:`web/apps/playground/src/components/PreviewPanel/`
|
|
|
+
|
|
|
+**LabelStudio 应用**(完整实现):
|
|
|
+- 完整的 Label Studio 应用
|
|
|
+- 复杂的功能集成
|
|
|
+- 位置:`web/apps/labelstudio/`
|
|
|
+
|
|
|
+### 2. 样式隔离
|
|
|
+
|
|
|
+使用 CSS 模块和 `:global()` 选择器:
|
|
|
+```scss
|
|
|
+.root {
|
|
|
+ // 组件自己的样式
|
|
|
+
|
|
|
+ // LabelStudio 样式覆盖
|
|
|
+ :global(.lsf-wrapper) {
|
|
|
+ // 全局样式
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 3. 清理逻辑
|
|
|
+
|
|
|
+始终实现完整的清理逻辑:
|
|
|
+```typescript
|
|
|
+function cleanup() {
|
|
|
+ // 清除 window.LabelStudio
|
|
|
+ // 销毁编辑器实例
|
|
|
+ // 取消动画帧
|
|
|
+ // 移除事件监听器
|
|
|
+}
|
|
|
+
|
|
|
+useEffect(() => {
|
|
|
+ // 初始化逻辑
|
|
|
+ return () => {
|
|
|
+ cleanup();
|
|
|
+ };
|
|
|
+}, [dependencies]);
|
|
|
+```
|
|
|
+
|
|
|
+### 4. 错误处理
|
|
|
+
|
|
|
+提供友好的错误提示:
|
|
|
+```typescript
|
|
|
+const [error, setError] = useState<string | null>(null);
|
|
|
+
|
|
|
+try {
|
|
|
+ // 加载和初始化
|
|
|
+} catch (err: any) {
|
|
|
+ console.error('Error loading LabelStudio:', err);
|
|
|
+ setError(err.message || '初始化编辑器失败');
|
|
|
+}
|
|
|
+
|
|
|
+// 在 UI 中显示错误
|
|
|
+{error && (
|
|
|
+ <div className="error-message">
|
|
|
+ {error}
|
|
|
+ </div>
|
|
|
+)}
|
|
|
+```
|
|
|
+
|
|
|
+## 配置检查清单
|
|
|
+
|
|
|
+在集成 LabelStudio 之前,确保:
|
|
|
+
|
|
|
+- [ ] Webpack 配置添加了 CSS 前缀处理
|
|
|
+- [ ] 定义了 `CSS_PREFIX` 环境变量
|
|
|
+- [ ] 配置了 CSS 模块的 `localIdentName`
|
|
|
+- [ ] main.tsx 只导入 UI 库样式
|
|
|
+- [ ] 没有静态导入 editor 的全局样式
|
|
|
+- [ ] 使用动态导入 `import('@humansignal/editor')`
|
|
|
+- [ ] 实现了完整的清理逻辑
|
|
|
+- [ ] 添加了 relations-overlay 透明背景修复
|
|
|
+- [ ] 配置了 WebAssembly 支持
|
|
|
+- [ ] 添加了 Node.js 模块 fallback
|
|
|
+- [ ] 处理了音频解码器问题
|
|
|
+
|
|
|
+## 故障排除流程
|
|
|
+
|
|
|
+1. **检查 webpack 配置**
|
|
|
+ - 确认 CSS 前缀配置已添加
|
|
|
+ - 确认服务器已重启
|
|
|
+
|
|
|
+2. **检查样式加载**
|
|
|
+ - 打开 Network 标签
|
|
|
+ - 确认 `libs_editor_src_index_js.css` 已加载
|
|
|
+ - 检查文件大小(应该是几 MB)
|
|
|
+
|
|
|
+3. **检查类名匹配**
|
|
|
+ - HTML 元素的类名应该以 `lsf-` 开头
|
|
|
+ - CSS 文件中的选择器也应该以 `lsf-` 开头
|
|
|
+
|
|
|
+4. **清除缓存**
|
|
|
+ - 硬刷新浏览器(Ctrl+Shift+R)
|
|
|
+ - 清除浏览器缓存
|
|
|
+ - 重启开发服务器
|
|
|
+
|
|
|
+5. **查看控制台**
|
|
|
+ - 检查是否有 JavaScript 错误
|
|
|
+ - 检查是否有 CSS 加载错误
|
|
|
+ - 查看自定义日志输出
|
|
|
+
|
|
|
+## 相关资源
|
|
|
+
|
|
|
+- **LabelStudio 文档**:https://labelstud.io/guide/
|
|
|
+- **CSS 模块文档**:https://github.com/css-modules/css-modules
|
|
|
+- **Webpack CSS Loader**:https://webpack.js.org/loaders/css-loader/
|
|
|
+- **项目参考**:
|
|
|
+ - `web/apps/playground/` - 简单集成示例
|
|
|
+ - `web/apps/labelstudio/` - 完整应用示例
|
|
|
+ - `web/webpack.config.js` - 根配置参考
|
|
|
+
|
|
|
+## 总结
|
|
|
+
|
|
|
+集成 LabelStudio 编辑器的关键是:
|
|
|
+
|
|
|
+1. **正确的 Webpack 配置**:添加 CSS 模块的 `lsf-` 前缀处理
|
|
|
+2. **正确的样式导入**:只导入 UI 库样式,让编辑器样式自动加载
|
|
|
+3. **动态导入编辑器**:使用 `import('@humansignal/editor')`
|
|
|
+4. **样式修复**:添加 relations-overlay 透明背景
|
|
|
+5. **参考成功案例**:遵循 playground 和 labelstudio 的实现方式
|
|
|
+
|
|
|
+遵循这些规范,可以避免大部分常见问题,快速集成 LabelStudio 编辑器!
|