shang-chunyu 2 săptămâni în urmă
părinte
comite
005974129a
41 a modificat fișierele cu 6916 adăugiri și 1211 ștergeri
  1. 521 0
      all_used_keys.txt
  2. 184 0
      clean_translations.js
  3. 87 0
      clean_translations.py
  4. 37 0
      extract_keys.js
  5. 89 0
      final_cleanup.js
  6. 26 0
      final_fix_braces.js
  7. 21 0
      fix_empty_translations.js
  8. BIN
      lint_output.txt
  9. 1551 0
      log_dups.txt
  10. 42 6
      package-lock.json
  11. 12 0
      server/check_schema.js
  12. 47 0
      server/debug_es.js
  13. BIN
      server/es_results.txt
  14. 0 0
      server/metadata.db
  15. BIN
      server/schema_output.txt
  16. 8 1
      server/src/import-task/import-task.controller.ts
  17. 4 0
      server/src/import-task/import-task.entity.ts
  18. 171 89
      server/src/import-task/import-task.service.ts
  19. 3 0
      server/src/knowledge-base/knowledge-base.service.ts
  20. 2 7
      server/src/knowledge-group/knowledge-group.controller.ts
  21. 12 0
      server/src/knowledge-group/knowledge-group.entity.ts
  22. 72 26
      server/src/knowledge-group/knowledge-group.service.ts
  23. 18 0
      server/src/migrations/1772340000000-AddParentIdToKnowledgeGroups.ts
  24. 80 0
      sync_translations.js
  25. 2090 0
      tmp_duplicates.txt
  26. 4 4
      web/components/DragDropUpload.tsx
  27. 5 5
      web/components/GlobalDragDropOverlay.tsx
  28. 414 122
      web/components/ImportFolderDrawer.tsx
  29. 174 0
      web/components/drawers/ImportTasksDrawer.tsx
  30. 352 134
      web/components/views/KnowledgeBaseView.tsx
  31. 23 23
      web/components/views/MemosView.tsx
  32. 3 3
      web/components/views/NotebookDetailView.tsx
  33. 64 13
      web/components/views/NotebooksView.tsx
  34. 128 91
      web/components/views/SettingsView.tsx
  35. 6 0
      web/postcss.config.js.bak
  36. 10 0
      web/services/importService.ts
  37. 4 3
      web/src/pages/workspace/SettingsPage.tsx
  38. 13 0
      web/tailwind.config.js.bak
  39. 4 0
      web/types.ts
  40. 572 203
      web/utils/translations.ts
  41. 63 481
      yarn.lock

+ 521 - 0
all_used_keys.txt

@@ -0,0 +1,521 @@
+2d
+Authorization
+a
+actionFailed
+actions
+addFile
+addUser
+admin
+agentDesc
+agentTitle
+aiAssistant
+aiCommandsApplyResult
+aiCommandsCustom
+aiCommandsCustomPlaceholder
+aiCommandsError
+aiCommandsGenerating
+aiCommandsGoBack
+aiCommandsModalApply
+aiCommandsModalBasedOnSelection
+aiCommandsModalCustom
+aiCommandsModalCustomPlaceholder
+aiCommandsModalPreset
+aiCommandsModalResult
+aiCommandsPreset
+aiCommandsReferenceContext
+aiCommandsReset
+aiCommandsResult
+aiCommandsStartGeneration
+aiDisclaimer
+all
+allDocuments
+allFormats
+allKnowledgeGroups
+allNotes
+analyzing
+analyzingFile
+analyzingImage
+apiError
+associateKnowledgeGroup
+autoAdjustChunk
+autoAdjustOverlap
+autoAdjustOverlapMin
+back
+backToWorkspace
+baseApi
+broad
+browseFiles
+browseManageFiles
+btnChat
+cancel
+canvas
+categories
+category
+categoryCreated
+categoryDesc
+categoryName
+changePassword
+changeUserPassword
+chatDesc
+chatHyperparameters
+chatTitle
+chatWithGroup
+checkPDFStatusFailed
+chunkConfig
+chunkIndex
+chunkInfo
+chunkNumber
+chunkOverlap
+chunkSize
+citationSources
+clearFailed
+clickToSelectAndNote
+clickToSelectFolder
+configured
+confirm
+confirmChange
+confirmChangeEmbeddingModel
+confirmClear
+confirmClearKB
+confirmDeleteCategory
+confirmDeleteFile
+confirmDeleteGroup
+confirmDeleteHistory
+confirmDeleteNote
+confirmDeleteNotebook
+confirmDeleteUser
+confirmPassword
+confirmPreciseCost
+confirmRegeneratePDF
+confirmRemoveFileFromGroup
+confirmTitle
+confirmUnsupportedFile
+contentLength
+contentOCR
+conversionFailed
+convertingInProgress
+convertingPDF
+copied
+copy
+copyContent
+copySuccess
+create
+createAgent
+createCategory
+createCategoryBtn
+createFailed
+createFailedRetry
+createGroupDesc
+createNotebook
+createNotebookTitle
+createNow
+createPDFNote
+createUserFailed
+createdAt
+creating
+creatingRegularUser
+creative
+ctx
+currentPassword
+daysAgo
+defaultBadge
+defaultForUploads
+defaultLLMModel
+defaultSettingFailed
+defaultTenant
+defaultVisionModel
+delete
+deleteFailed
+deleteHistoryFailed
+deleteHistorySuccess
+deleteUser
+deleteUserFailed
+descPlaceholder
+dimensions
+dims
+directoryLabel
+documentsAndText
+domainOptional
+done
+downloadPDF
+downloadPDFFailed
+dragDropUploadDesc
+dragDropUploadTitle
+dragToSelect
+dropAnywhere
+dropToIngest
+editCategory
+editNote
+editNotebookTitle
+editUserRole
+embeddingModel
+embeddingModelWarning
+enableHyDE
+enableHybridSearch
+enableQueryExpansion
+enableReranking
+enterNamePlaceholder
+enterNewPassword
+enterNoteTitle
+enterPageNumber
+envLimitWeaker
+error
+errorGeneric
+errorLabel
+errorLoadData
+errorMessage
+errorNoModel
+errorSaveFailed
+errorTitleContentRequired
+errorUploadFile
+exampleResearch
+exitFullscreen
+exitSelectionMode
+expandMenu
+extractingText
+failedToAddToGroup
+failedToCreateCategory
+failedToDeleteCategory
+failedToRemoveFromGroup
+failedToSaveSettings
+fastMode
+fastModeDesc
+fastModeFeatures
+featureUpdated
+fileAddedToGroup
+fileDeleted
+fileRemovedFromGroup
+fileSizeLimitExceeded
+files
+fillTargetName
+filterGroupFiles
+filterLowResults
+filterNotesPlaceholder
+fullTextSearch
+fullscreenDisplay
+geminiError
+generalSettings
+generalSettingsSubtitle
+generatePDFPreviewButton
+getUserListFailed
+globalNoSpecificGroup
+globalTenantControl
+goToAdmin
+groupCreated
+groupDeleted
+groupUpdated
+groups
+headerHyperparams
+headerModelSelection
+headerRetrieval
+hidePreview
+historyMessages
+historyTitle
+hybridSearchDesc
+hybridVectorWeight
+hybridVectorWeightDesc
+hybridWeight
+hydeDesc
+idxCancel
+idxDesc
+idxEmbeddingModel
+idxFiles
+idxMethod
+idxModalTitle
+idxStart
+imagesAndVision
+importComplete
+importFolder
+importFolderTip
+importFolderTitle
+importToCurrentGroup
+importedFromLocalFolder
+indexingChunkingConfig
+indexingConfigDesc
+indexingConfigTitle
+info
+installPlugin
+installedPlugin
+kbCleared
+kbManagement
+kbManagementDesc
+kbSettingsSaved
+kbSettingsSubtitle
+langEn
+langJa
+langZh
+languageSettings
+lblEmbedding
+lblMaxTokens
+lblRerank
+lblRerankRef
+lblTargetGroup
+lblTemperature
+lblTopK
+loadFailed
+loadHistoryFailed
+loadLimitsFailed
+loadMore
+loadVisionModelFailed
+loading
+loadingHistoriesFailed
+loadingPDF
+loadingUserData
+loginButton
+loginDesc
+loginError
+loginRequired
+loginTitle
+loginToUpload
+logout
+matchScore
+max
+maxBatchSize
+maxChunkSize
+maxInput
+maxOverlapSize
+maxResponseTokens
+maxValueMsg
+min
+mmAddBtn
+mmCancel
+mmEdit
+mmEmpty
+mmErrorBaseUrlRequired
+mmErrorModelIdRequired
+mmErrorNameRequired
+mmErrorNotAuthenticated
+mmFormApiKey
+mmFormApiKeyPlaceholder
+mmFormBaseUrl
+mmFormModelId
+mmFormName
+mmFormType
+mmSave
+mmTitle
+model
+modelConfiguration
+modelDisabled
+modelEnabled
+modelLimitsInfo
+modelManagement
+modelManagementSubtitle
+modifySettings
+name
+nameHelp
+namePlaceholder
+navAgent
+navCatalog
+navChat
+navKnowledge
+navKnowledgeGroups
+navNotebook
+navPlugin
+navTenants
+needLogin
+newChat
+newGroup
+newNote
+newPassword
+newPasswordMinLength
+newTenant
+next
+nextStep
+noContentToPreview
+noDescriptionProvided
+noFiles
+noFilesDesc
+noFilesFound
+noGroups
+noGroupsFound
+noHistory
+noHistoryDesc
+noKnowledgeGroups
+noNotesFound
+noRerankModel
+noTextExtracted
+noVisionModels
+none
+noneUncategorized
+noteCreatedFailed
+noteCreatedSuccess
+noteTitlePlaceholder
+notebookDesc
+notebooks
+notebooksDesc
+onlyAdminCanModify
+openInNewWindow
+openPDFInNewTabFailed
+operational
+optimizationTips
+orgManagement
+overlapRatioLimit
+page
+password
+passwordChangeFailed
+passwordChangeSuccess
+passwordMinLength
+passwordMismatch
+passwordPlaceholder
+pdfConversionError
+pdfConversionFailed
+pdfLoadError
+pdfLoadFailed
+pdfPreview
+pdfPreviewReady
+pendingFiles
+personalNotebook
+placeholderEmpty
+placeholderNewGroup
+placeholderText
+placeholderWithFiles
+pleaseSelect
+pleaseSelectKnowledgeGroupFirst
+pleaseWait
+pluginBy
+pluginCommunity
+pluginConfig
+pluginDesc
+pluginOfficial
+pluginTitle
+position
+precise
+preciseMode
+preciseModeDesc
+preciseModeFeatures
+preparingPDFConversion
+preview
+previewHeader
+previewNotSupported
+previous
+processingMode
+pureText
+pureVector
+queryExpansionDesc
+readFailed
+readingFailed
+recommendationMsg
+recommendationReason
+reconfigureDesc
+reconfigureFile
+reconfigureTitle
+regeneratePDF
+releaseToIngest
+requestRegenerationFailed
+rerankModel
+rerankSimilarityThreshold
+rerankingDesc
+resetZoom
+retrievalSearchSettings
+retry
+roleRegularUser
+roleTenantAdmin
+save
+saveChanges
+saveNote
+saveVisionModelFailed
+saving
+screenshotPreview
+searchAgent
+searchGroupsPlaceholder
+searchPlaceholder
+searchPlugin
+searchResults
+secureIngestion
+secureProcessing
+selectCategory
+selectEmbedding
+selectEmbeddingFirst
+selectEmbeddingModel
+selectFolderTip
+selectKnowledgeGroup
+selectKnowledgeGroups
+selectLLM
+selectLLMModel
+selectOrganization
+selectPageNumber
+selectVisionModel
+selectedFilesCount
+selectedGroupsCount
+settings
+shortDescription
+showPreview
+showingRange
+sidebarDesc
+sidebarTitle
+similarityThreshold
+sourcePreview
+startByCreatingNote
+startProcessing
+startWritingPlaceholder
+statusIndexingDesc
+statusReadyDesc
+statusRunning
+statusStopped
+strict
+subFolderPlaceholder
+submitFailed
+success
+successNoteCreated
+successNoteDeleted
+successNoteUpdated
+successUploadFile
+supportedFormatsInfo
+switchLanguage
+systemConfiguration
+systemHealth
+systemUsers
+tabSettings
+targetRole
+temperature
+tenantsSubtitle
+textarea
+tipChunkTooLarge
+tipMaxValues
+tipOverlapSmall
+tipPreciseCost
+title
+topK
+totalChunks
+totalTenants
+typeEmbedding
+typeLLM
+typeRerank
+typeVision
+uncategorized
+uncategorizedFiles
+unknownError
+unknownGroup
+unsupportedFileType
+updateFailedRetry
+updatePlugin
+updateUserFailed
+updatedAtPrefix
+uploadErrors
+uploadFailed
+uploadWarning
+uploading
+user
+userAddedToOrganization
+userCreatedSuccess
+userDeletedSuccessfully
+userDemotedFromAdmin
+userList
+userManagement
+userManagementSubtitle
+userPromotedToAdmin
+username
+usernamePlaceholder
+vectorSimilarityThreshold
+viewHistory
+visionModelHelp
+visionModelSettings
+visualVision
+warning
+welcomeMessage
+x-api-key
+x-tenant-id
+x-user-language
+yesterday
+zoomIn
+zoomOut

+ 184 - 0
clean_translations.js

@@ -0,0 +1,184 @@
+
+const fs = require('fs');
+const path = require('path');
+
+const filePath = process.argv[2];
+
+if (!filePath) {
+    console.error('Please provide a file path');
+    process.exit(1);
+}
+
+const content = fs.readFileSync(filePath, 'utf8');
+
+// These are missing keys that we want to ensure exist in each language block
+const missingKeysData = {
+    kbSettingsSaved: { zh: "检索与对话配置已保存", en: "Knowledge base settings saved", ja: "設定を保存しました" },
+    failedToSaveSettings: { zh: "保存设置失败", en: "Failed to save settings", ja: "設定の保存に失敗しました" },
+    actionFailed: { zh: "操作失败", en: "Action failed", ja: "操作に失敗しました" },
+    userAddedToOrganization: { zh: "用户已添加到组织", en: "User added to organization", ja: "ユーザーが組織に追加されました" },
+    featureUpdated: { zh: "功能已更新", en: "Feature updated", ja: "機能が更新されました" },
+    roleTenantAdmin: { zh: "租户管理员", en: "Tenant Administrator", ja: "テナント管理者" },
+    roleRegularUser: { zh: "普通用户", en: "Regular User", ja: "一般ユーザー" },
+    creatingRegularUser: { zh: "正在创建普通用户", en: "Creating regular user", ja: "一般ユーザーを作成中" },
+    editUserRole: { zh: "修改用户角色", en: "Edit user role", ja: "ユーザーロールを編集" },
+    targetRole: { zh: "目标角色", en: "Target Role", ja: "対象のロール" },
+    editCategory: { zh: "编辑分类", en: "Edit category", ja: "カテゴリを編集" },
+    totalTenants: { zh: "总租户数", en: "Total Tenants", ja: "総テナント数" },
+    systemUsers: { zh: "系统用户", en: "System Users", ja: "システムユーザー" },
+    systemHealth: { zh: "系统健康", en: "System Health", ja: "システムヘルス" },
+    operational: { zh: "运行正常", en: "Operational", ja: "正常稼働中" },
+    orgManagement: { zh: "组织管理", en: "Organization Management", ja: "組織管理" },
+    globalTenantControl: { zh: "全局租户控制", en: "Global Tenant Control", ja: "グローバルテナントコントロール" },
+    newTenant: { zh: "新租户", en: "New Tenant", ja: "新規テナント" },
+    domainOptional: { zh: "域名 (可选)", en: "Domain (Optional)", ja: "ドメイン (任意)" },
+    saveChanges: { zh: "保存修改", en: "Save changes", ja: "変更を保存" },
+    modelConfiguration: { zh: "模型配置", en: "Model Configuration", ja: "モデル設定" },
+    defaultLLMModel: { zh: "默认推理模型", en: "Default LLM Model", ja: "デフォルト推論モデル" },
+    selectLLM: { zh: "选择 LLM", en: "Select LLM", ja: "LLMを選択" },
+    selectEmbedding: { zh: "选择 Embedding", en: "Select Embedding", ja: "埋め込みを選択" },
+    rerankModel: { zh: "Rerank 模型", en: "Rerank Model", ja: "リランクモデル" },
+    none: { zh: "无", en: "None", ja: "なし" },
+    indexingChunkingConfig: { zh: "索引与切片配置", en: "Indexing & Chunking Config", ja: "インデックスとチャンク設定" },
+    chatHyperparameters: { zh: "聊天超参数", en: "Chat Hyperparameters", ja: "チャットハイパーパラメータ" },
+    temperature: { zh: "随机性 (Temperature)", en: "Temperature", ja: "温度" },
+    precise: { zh: "精确", en: "Precise", ja: "精密" },
+    creative: { zh: "创意", en: "Creative", ja: "クリエイティブ" },
+    maxResponseTokens: { zh: "最大响应标识 (Max Tokens)", en: "Max Response Tokens", ja: "最大応答トークン数" },
+    retrievalSearchSettings: { zh: "检索与搜索设置", en: "Retrieval & Search Settings", ja: "検索設定" },
+    topK: { zh: "召回数量 (Top K)", en: "Top K", ja: "Top K" },
+    similarityThreshold: { zh: "相似度阈值", en: "Similarity Threshold", ja: "類似度しきい値" },
+    enableHybridSearch: { zh: "启用混合检索", en: "Enable Hybrid Search", ja: "ハイブリッド検索を有効にする" },
+    hybridSearchDesc: { zh: "同时使用向量和全文检索以提高召回率", en: "Use both vector and full-text search to improve recall", ja: "ベクトル検索と全文検索を併用して検索精度を向上させます" },
+    hybridWeight: { zh: "混合权重 (0.0=全文, 1.0=向量)", en: "Hybrid Weight (0.0=Fulltext, 1.0=Vector)", ja: "ハイブリッド重み (0.0=全文, 1.0=ベクトル)" },
+    pureText: { zh: "纯文本", en: "Pure Text", ja: "純粋なテキスト" },
+    pureVector: { zh: "纯向量", en: "Pure Vector", ja: "純粋なベクトル" },
+    enableQueryExpansion: { zh: "启用查询扩展", en: "Enable Query Expansion", ja: "クエリ拡張を有効にする" },
+    queryExpansionDesc: { zh: "生成多个查询变体以提高覆盖率", en: "Generate multiple query variations for better coverage", ja: "複数のクエリバリアントを生成してカバレッジを向上させます" },
+    enableHyDE: { zh: "启用 HyDE", en: "Enable HyDE", ja: "HyDEを有効にする" },
+    hydeDesc: { zh: "生成假设回答以改善语义搜索", en: "Generate hypothetical answers to improve semantic search", ja: "仮想的な回答を生成してセマンティック検索を改善します" },
+    enableReranking: { zh: "启用重排序 (Rerank)", en: "Enable Reranking", ja: "リランクを有効にする" },
+    rerankingDesc: { zh: "使用 Rerank 模型对结果进行二次排序", en: "Use Rerank model to re-sort results", ja: "リランクモデルを使用して結果を再ソートします" },
+    broad: { zh: "宽泛", en: "Broad", ja: "広範" },
+    strict: { zh: "严格", en: "Strict", ja: "厳格" },
+    maxInput: { zh: "最大输入", en: "Max Input", ja: "最大入力" },
+    dimensions: { zh: "维度", en: "Dimensions", ja: "次元" },
+    defaultBadge: { zh: "默认", en: "Default", ja: "デフォルト" },
+    dims: { zh: "维度: $1", en: "Dims: $1", ja: "次元: $1" },
+    ctx: { zh: "上下文: $1", en: "Ctx: $1", ja: "コンテキスト: $1" },
+    baseApi: { zh: "Base API: $1", en: "Base API: $1", ja: "Base API: $1" },
+    configured: { zh: "已配置", en: "Configured", ja: "設定済み" },
+    groupUpdated: { zh: "分组已更新", en: "Group updated", ja: "グループが更新されました" },
+    groupDeleted: { zh: "分组已删除", en: "Group deleted", ja: "グループが削除されました" },
+    groupCreated: { zh: "分组已创建", en: "Group created", ja: "グループが作成されました" },
+    navCatalog: { zh: "目录", en: "Catalog", ja: "カタログ" },
+    allDocuments: { zh: "所有文档", en: "All Documents", ja: "すべてのドキュメント" },
+    categories: { zh: "分类", en: "Categories", ja: "カテゴリ" },
+    uncategorizedFiles: { zh: "未分类文件", en: "Uncategorized Files", ja: "未分類ファイル" },
+    category: { zh: "分类", en: "Category", ja: "カテゴリ" },
+    statusReadyDesc: { zh: "已索引可查询", en: "Indexed and searchable", ja: "インデックス済みで検索可能" },
+    statusIndexingDesc: { zh: "正在建立词向量索引", en: "Building vector index", ja: "ベクトルインデックスを作成中" },
+    selectCategory: { zh: "选择分类", en: "Select Category", ja: "カテゴリを選択" },
+    noneUncategorized: { zh: "无未分类文件", en: "No uncategorized files", ja: "未分類ファイルなし" },
+    previous: { zh: "上一页", en: "Previous", ja: "前へ" },
+    next: { zh: "下一页", en: "Next", ja: "次へ" },
+    createCategory: { zh: "创建分类", en: "Create Category", ja: "カテゴリを作成" },
+    categoryDesc: { zh: "描述您的知识分类", en: "Describe your knowledge category", ja: "ナレッジカテゴリを説明します" },
+    categoryName: { zh: "分类名称", en: "Category Name", ja: "カテゴリ名" },
+    createCategoryBtn: { zh: "立即创建", en: "Create Now", ja: "今すぐ作成" },
+    newGroup: { zh: "新建分组", en: "New Group", ja: "新規グループ" },
+    noKnowledgeGroups: { zh: "暂无知识库分组", en: "No knowledge groups yet", ja: "ナレッジグループがまだありません" },
+    createGroupDesc: { zh: "开始创建您的第一个知识库分组并上传相关文档。", en: "Start by creating your first knowledge group and uploading documents.", ja: "最初のナレッジグループを作成してドキュメントをアップロードしてください。" },
+    noDescriptionProvided: { zh: "未提供描述", en: "No description provided", ja: "説明なし" },
+    browseManageFiles: { zh: "浏览并管理该分组下的文件和笔记。", en: "Browse and manage files and notes in this group.", ja: "このグループ内のファイルとメモを閲覧・管理します。" },
+    filterGroupFiles: { zh: "根据名称搜索分组内文件...", en: "Search files in group by name...", ja: "名前でグループ内のファイルを検索..." },
+    generalSettingsSubtitle: { zh: "管理您的应用程序首选项。", en: "Manage your application preferences.", ja: "アプリケーションの設定を管理します。" },
+    userManagementSubtitle: { zh: "管理访问权限和帐户。", en: "Manage access and accounts.", ja: "アクセス権限とアカウントを管理します。" },
+    modelManagementSubtitle: { zh: "配置全局 AI 模型。", en: "Configure global AI models.", ja: "グローバルなAIモデルを設定します。" },
+    kbSettingsSubtitle: { zh: "索引和聊天参数的技术配置。", en: "Technical configuration for indexing and chat parameters.", ja: "インデックス作成とチャットパラメータの技術設定。" },
+    tenantsSubtitle: { zh: "全局系统概览。", en: "Global system overview.", ja: "グローバルシステムの概要。" },
+    allNotes: { zh: "所有笔记", en: "All Notes", ja: "すべてのノート" },
+    filterNotesPlaceholder: { zh: "筛选笔记...", en: "Filter notes...", ja: "ノートをフィルタリング..." },
+    noteTitlePlaceholder: { zh: "标题...", en: "Title...", ja: "タイトル..." },
+    startWritingPlaceholder: { zh: "开始写作...", en: "Start writing...", ja: "書き始める..." },
+    previewHeader: { zh: "预览", en: "Preview", ja: "プレビュー" },
+    noContentToPreview: { zh: "没有可预览的内容", en: "No content to preview", ja: "プレビューするコンテンツがありません" },
+    hidePreview: { zh: "隐藏预览", en: "Hide Preview", ja: "プレビューを非表示" },
+    showPreview: { zh: "显示预览", en: "Show Preview", ja: "プレビューを表示" },
+    directoryLabel: { zh: "目录", en: "Directory", ja: "ディレクトリ" },
+    uncategorized: { zh: "未分类", en: "Uncategorized", ja: "未分類" },
+    enterNamePlaceholder: { zh: "输入名称...", en: "Enter name...", ja: "名前を入力..." },
+    subFolderPlaceholder: { zh: "子文件夹...", en: "Sub-folder...", ja: "サブフォルダ..." },
+    categoryCreated: { zh: "分类已创建", en: "Category created", ja: "カテゴリが作成されました" },
+    failedToCreateCategory: { zh: "创建分类失败", en: "Failed to create category", ja: "カテゴリの作成に失敗しました" },
+    failedToDeleteCategory: { zh: "删除分类失败", en: "Failed to delete category", ja: "カテゴリの削除に失敗しました" },
+    confirmDeleteCategory: { zh: "您确定要删除此分类吗?", en: "Are you sure you want to delete this category?", ja: "このカテゴリを削除してもよろしいですか?" }
+};
+
+const lines = content.split('\n');
+let currentLang = null;
+let resultLines = [];
+let keysSeen = new Set();
+
+const langStartRegex = /^\s+(\w+): \{/;
+const keyRegex = /^\s+([a-zA-Z0-9_-]+):/;
+
+for (let i = 0; i < lines.length; i++) {
+    const line = lines[i];
+
+    const langMatch = line.match(langStartRegex);
+    if (langMatch) {
+        // If we were in a language block, append missing keys before finishing it
+        if (currentLang) {
+            addMissingKeys(currentLang, resultLines, keysSeen);
+        }
+        currentLang = langMatch[1];
+        keysSeen = new Set();
+        resultLines.push(line);
+        continue;
+    }
+
+    if (currentLang) {
+        const keyMatch = line.match(keyRegex);
+        if (keyMatch) {
+            const key = keyMatch[1];
+            if (keysSeen.has(key)) {
+                // Duplicate key, skip it
+                continue;
+            }
+            keysSeen.add(key);
+        }
+
+        // If the line ends the block
+        if (line.trim() === '},') {
+            addMissingKeys(currentLang, resultLines, keysSeen);
+            currentLang = null;
+            resultLines.push(line);
+            continue;
+        }
+
+        // Also handle the very last block which might not have a comma
+        if (line.trim() === '}' && i > lines.length - 5) {
+            addMissingKeys(currentLang, resultLines, keysSeen);
+            currentLang = null;
+            resultLines.push(line);
+            continue;
+        }
+    }
+
+    resultLines.push(line);
+}
+
+function addMissingKeys(lang, targetLines, seen) {
+    for (const [key, translations] of Object.entries(missingKeysData)) {
+        if (!seen.has(key)) {
+            const val = translations[lang] || translations['en'] || key;
+            const escapedVal = JSON.stringify(val);
+            targetLines.push(`    ${key}: ${escapedVal},`);
+            seen.add(key);
+        }
+    }
+}
+
+fs.writeFileSync(filePath, resultLines.join('\n'), 'utf8');
+console.log('Translations file cleaned and updated successfully!');

+ 87 - 0
clean_translations.py

@@ -0,0 +1,87 @@
+
+import sys
+import re
+
+def clean_translations(file_path):
+    with open(file_path, 'r', encoding='utf-8') as f:
+        content = f.read()
+
+    # Split into blocks
+    blocks = re.split(r'(\s+\w+: \{)', content)
+    # Header is blocks[0]
+    # Then blocks[1] is "  zh: {", blocks[2] is content of zh
+    # blocks[3] is "  en: {", blocks[4] is content of en
+    # blocks[5] is "  ja: {", blocks[6] is content of ja
+    
+    header = blocks[0]
+    processed_blocks = []
+    
+    # Missing keys to ensure (with basic English values)
+    missing_keys = [
+        "kbSettingsSaved", "failedToSaveSettings", "actionFailed", "userAddedToOrganization",
+        "featureUpdated", "roleTenantAdmin", "roleRegularUser", "creatingRegularUser",
+        "editUserRole", "targetRole", "editCategory", "totalTenants", "systemUsers",
+        "systemHealth", "operational", "orgManagement", "globalTenantControl",
+        "newTenant", "domainOptional", "saveChanges", "modelConfiguration",
+        "defaultLLMModel", "selectLLM", "selectEmbedding", "rerankModel", "none",
+        "indexingChunkingConfig", "chatHyperparameters", "temperature", "precise",
+        "creative", "maxResponseTokens", "retrievalSearchSettings", "topK",
+        "similarityThreshold", "enableHybridSearch", "hybridSearchDesc", "hybridWeight",
+        "pureText", "pureVector", "enableQueryExpansion", "queryExpansionDesc",
+        "enableHyDE", "hydeDesc", "enableReranking", "rerankingDesc", "broad",
+        "strict", "maxInput", "dimensions", "defaultBadge", "dims", "ctx",
+        "baseApi", "configured", "groupUpdated", "groupDeleted", "groupCreated",
+        "navCatalog", "allDocuments", "categories", "uncategorizedFiles", "category",
+        "statusReadyDesc", "statusIndexingDesc", "selectCategory", "noneUncategorized",
+        "previous", "next", "createCategory", "categoryDesc", "categoryName",
+        "createCategoryBtn", "newGroup", "noKnowledgeGroups", "createGroupDesc",
+        "noDescriptionProvided", "browseManageFiles", "filterGroupFiles"
+    ]
+
+    for i in range(1, len(blocks), 2):
+        block_header = blocks[i]
+        block_content = blocks[i+1]
+        
+        # Parse keys and values
+        lines = block_content.split('\n')
+        keys_seen = set()
+        new_lines = []
+        
+        # Regex to match "key: value," or "key: `value`,"
+        # Support multiline strings too? Let's be careful.
+        # Most are single line: "    key: \"value\","
+        
+        for line in lines:
+            match = re.search(r'^\s+([a-zA-Z0-9_-]+):', line)
+            if match:
+                key = match.group(1)
+                if key in keys_seen:
+                    continue # Skip duplicate
+                keys_seen.add(key)
+            new_lines.append(line)
+        
+        # Add missing keys if they are not in keys_seen
+        # Remove trailing "  }," or "}," to append
+        if new_lines and re.search(r'^\s+},?$', new_lines[-1]):
+            last_line = new_lines.pop()
+        elif new_lines and re.search(r'^\s+},?$', new_lines[-2]): # Check if last is empty
+            last_line = new_lines.pop(-2)
+        else:
+            last_line = "  },"
+
+        for key in missing_keys:
+            if key not in keys_seen:
+                # Add a descriptive placeholder or common translation
+                val = f'"{key}"' # Default to key name
+                new_lines.append(f'    {key}: {val},')
+        
+        new_lines.append(last_line)
+        processed_blocks.append(block_header + '\n'.join(new_lines))
+
+    new_content = header + ''.join(processed_blocks)
+    
+    with open(file_path, 'w', encoding='utf-8') as f:
+        f.write(new_content)
+
+if __name__ == "__main__":
+    clean_translations(sys.argv[1])

+ 37 - 0
extract_keys.js

@@ -0,0 +1,37 @@
+
+const fs = require('fs');
+const path = require('path');
+
+function getFiles(dir, fileList = []) {
+    const files = fs.readdirSync(dir);
+    for (const file of files) {
+        const name = path.join(dir, file);
+        if (fs.statSync(name).isDirectory()) {
+            if (file !== 'node_modules' && file !== '.git' && file !== 'dist') {
+                getFiles(name, fileList);
+            }
+        } else {
+            if (name.endsWith('.tsx') || name.endsWith('.ts')) {
+                fileList.push(name);
+            }
+        }
+    }
+    return fileList;
+}
+
+const webDir = path.join('d:', 'workspace', 'AuraK', 'web');
+const files = getFiles(webDir);
+const keys = new Set();
+const tRegex = /t\(\s*['"]([a-zA-Z0-9_-]+)['"]/g;
+
+for (const file of files) {
+    const content = fs.readFileSync(file, 'utf8');
+    let match;
+    while ((match = tRegex.exec(content)) !== null) {
+        keys.add(match[1]);
+    }
+}
+
+const sortedKeys = Array.from(keys).sort();
+fs.writeFileSync(path.join('d:', 'workspace', 'AuraK', 'all_used_keys.txt'), sortedKeys.join('\n'));
+console.log(`Extracted ${sortedKeys.length} unique keys to all_used_keys.txt`);

+ 89 - 0
final_cleanup.js

@@ -0,0 +1,89 @@
+
+const fs = require('fs');
+const path = require('path');
+
+const translationsPath = path.join('d:', 'workspace', 'AuraK', 'web', 'utils', 'translations.ts');
+
+const betterTranslations = {
+    navAgent: { zh: "智能体", en: "Agent", ja: "エージェント" },
+    navNotebook: { zh: "笔记本", en: "Notebook", ja: "ノートブック" },
+    navPlugin: { zh: "插件", en: "Plugins", ja: "プラグイン" },
+    navTenants: { zh: "租户管理", en: "Tenants", ja: "テナント管理" },
+    noNotesFound: { zh: "未找到笔记", en: "No notes found", ja: "ノートが見つかりません" },
+    notebookDesc: { zh: "笔记本功能可以帮助您整理和归纳知识。", en: "Notebooks help you organize and summarize knowledge.", ja: "ノートブックは知識の整理と要約に役立ちます。" },
+    personalNotebook: { zh: "个人笔记本", en: "Personal Notebook", ja: "個人用ノートブック" },
+    pluginBy: { zh: "作者", en: "By", ja: "作者" },
+    pluginCommunity: { zh: "社区插件", en: "Community Plugins", ja: "コミュニティプラグイン" },
+    pluginConfig: { zh: "插件配置", en: "Plugin Config", ja: "プラグイン設定" },
+    pluginDesc: { zh: "扩展系统功能。", en: "Extend system capabilities.", ja: "システム機能を拡張します。" },
+    pluginOfficial: { zh: "官方插件", en: "Official Plugins", ja: "公式プラグイン" },
+    pluginTitle: { zh: "插件", en: "Plugins", ja: "プラグイン" },
+    searchAgent: { zh: "搜索智能体", en: "Search Agents", ja: "エージェントを検索" },
+    searchPlugin: { zh: "搜索插件", en: "Search Plugins", ja: "プラグインを検索" },
+    statusRunning: { zh: "运行中", en: "Running", ja: "実行中" },
+    statusStopped: { zh: "已停止", en: "Stopped", ja: "停止中" },
+    success: { zh: "成功", en: "Success", ja: "成功" },
+    updatedAtPrefix: { zh: "最后更新于", en: "Last updated at", ja: "最終更新日:" },
+    visualVision: { zh: "视觉分析", en: "Visual Analysis", ja: "視覚分析" },
+    warning: { zh: "警告", en: "Warning", ja: "警告" },
+    "x-api-key": { zh: "API 密钥", en: "API Key", ja: "APIキー" },
+    "x-tenant-id": { zh: "租户 ID", en: "Tenant ID", ja: "テナントID" },
+    "x-user-language": { zh: "用户语言", en: "User Language", ja: "ユーザー言語" },
+    unknown: { zh: "未知", en: "Unknown", ja: "不明" }
+};
+
+let content = fs.readFileSync(translationsPath, 'utf8');
+
+function isValidIdentifier(id) {
+    return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(id);
+}
+
+// Simple parser to extract blocks
+const langBlocks = content.split(/(\w+): \{/);
+let header = langBlocks[0];
+let newContent = header;
+
+for (let i = 1; i < langBlocks.length; i += 2) {
+    const lang = langBlocks[i];
+    let block = langBlocks[i + 1];
+
+    // Find the end of this block
+    let endIdx = block.lastIndexOf('},');
+    if (endIdx === -1) endIdx = block.lastIndexOf('}'); // last block
+
+    let footer = block.substring(endIdx);
+    let itemsStr = block.substring(0, endIdx);
+
+    let items = itemsStr.split('\n');
+    let seenKeys = new Set();
+    let resultItems = [];
+
+    for (let line of items) {
+        let match = line.match(/^(\s+)(['"]?[a-zA-Z0-9_-]+['"]?):(.*)/);
+        if (match) {
+            let indent = match[1];
+            let keyStr = match[2];
+            let rest = match[3];
+
+            let actualKey = keyStr.replace(/['"]/g, '');
+            if (seenKeys.has(actualKey)) continue;
+            seenKeys.add(actualKey);
+
+            let val = rest.trim().replace(/,$/, '');
+            // If it's a placeholder (same as key) or empty, use better translation if available
+            if ((val === `"${actualKey}"` || val === `'${actualKey}'`) && betterTranslations[actualKey]) {
+                val = JSON.stringify(betterTranslations[actualKey][lang] || betterTranslations[actualKey].en || actualKey);
+            }
+
+            const quotedKey = isValidIdentifier(actualKey) ? actualKey : `"${actualKey}"`;
+            resultItems.push(`${indent}${quotedKey}: ${val},`);
+        } else if (line.trim().startsWith('//') || line.trim() === '') {
+            resultItems.push(line);
+        }
+    }
+
+    newContent += `${lang}: {` + resultItems.join('\n') + footer;
+}
+
+fs.writeFileSync(translationsPath, newContent, 'utf8');
+console.log('Final cleanup and translation improvement complete!');

+ 26 - 0
final_fix_braces.js

@@ -0,0 +1,26 @@
+
+const fs = require('fs');
+const path = require('path');
+
+const translationsPath = path.join('d:', 'workspace', 'AuraK', 'web', 'utils', 'translations.ts');
+let content = fs.readFileSync(translationsPath, 'utf8');
+
+// The file should end with ja object closing and then main object closing.
+// Current last line: }; // end of translations
+// We want:
+//   },
+// };
+
+// Strip potential trailing whitespace or comments that might mess up endsWith
+const lines = content.trimEnd().split('\n');
+const lastLine = lines[lines.length - 1];
+
+if (lastLine.includes('};')) {
+    console.log('Found closing brace at the end. Fixing...');
+    lines[lines.length - 1] = '  },';
+    lines.push('};');
+    fs.writeFileSync(translationsPath, lines.join('\n') + '\n', 'utf8');
+    console.log('Fixed end of file.');
+} else {
+    console.log('Closing brace not found as expected. Last line:', lastLine);
+}

+ 21 - 0
fix_empty_translations.js

@@ -0,0 +1,21 @@
+
+const fs = require('fs');
+const path = require('path');
+
+const translationsPath = path.join('d:', 'workspace', 'AuraK', 'web', 'utils', 'translations.ts');
+let content = fs.readFileSync(translationsPath, 'utf8');
+
+// Fix the specific syntax error first: key: , -> key: "key",
+const lines = content.split('\n');
+const fixedLines = lines.map(line => {
+    const match = line.match(/^(\s+)(["']?[a-zA-Z0-9_-]+["']?):\s*,/);
+    if (match) {
+        const indent = match[1];
+        const key = match[2].replace(/['"]/g, '');
+        return `${indent}${match[2]}: "${key}",`;
+    }
+    return line;
+});
+
+fs.writeFileSync(translationsPath, fixedLines.join('\n'), 'utf8');
+console.log('Fixed empty values in translations.ts!');

BIN
lint_output.txt


+ 1551 - 0
log_dups.txt

@@ -0,0 +1,1551 @@
+zh duplicate: aiCommandsError line 602
+zh duplicate: appTitle line 603
+zh duplicate: loginTitle line 604
+zh duplicate: loginDesc line 605
+zh duplicate: loginButton line 606
+zh duplicate: loginError line 607
+zh duplicate: unknownError line 608
+zh duplicate: usernamePlaceholder line 609
+zh duplicate: passwordPlaceholder line 610
+zh duplicate: registerButton line 611
+zh duplicate: confirm line 612
+zh duplicate: cancel line 613
+zh duplicate: confirmTitle line 614
+zh duplicate: confirmDeleteGroup line 615
+zh duplicate: systemConfiguration line 617
+zh duplicate: noFiles line 618
+zh duplicate: noFilesDesc line 619
+zh duplicate: addFile line 620
+zh duplicate: clearAll line 621
+zh duplicate: uploading line 622
+zh duplicate: editNotebookTitle line 623
+zh duplicate: noteCreatedSuccess line 624
+zh duplicate: noteCreatedFailed line 625
+zh duplicate: errorRenderFlowchart line 626
+zh duplicate: errorLoadData line 627
+zh duplicate: confirmUnsupportedFile line 628
+zh duplicate: errorReadFile line 629
+zh duplicate: successUploadFile line 630
+zh duplicate: errorUploadFile line 631
+zh duplicate: fileAddedToGroup line 632
+zh duplicate: failedToAddToGroup line 633
+zh duplicate: fileRemovedFromGroup line 634
+zh duplicate: failedToRemoveFromGroup line 635
+zh duplicate: errorProcessFile line 636
+zh duplicate: errorTitleContentRequired line 637
+zh duplicate: successNoteUpdated line 638
+zh duplicate: successNoteCreated line 639
+zh duplicate: errorSaveFailed line 640
+zh duplicate: confirmDeleteNote line 641
+zh duplicate: successNoteDeleted line 642
+zh duplicate: confirmRemoveFileFromGroup line 643
+zh duplicate: togglePreviewOpen line 644
+zh duplicate: togglePreviewClose line 645
+zh duplicate: noteTitlePlaceholder line 646
+zh duplicate: noteContentPlaceholder line 647
+zh duplicate: markdownPreviewArea line 648
+zh duplicate: back line 649
+zh duplicate: chatWithGroup line 650
+zh duplicate: chatWithFile line 651
+zh duplicate: filesCountLabel line 652
+zh duplicate: notesCountLabel line 653
+zh duplicate: indexIntoKB line 654
+zh duplicate: noFilesOrNotes line 655
+zh duplicate: sidebarTitle line 657
+zh duplicate: backToWorkspace line 658
+zh duplicate: goToAdmin line 659
+zh duplicate: sidebarDesc line 660
+zh duplicate: tabFiles line 661
+zh duplicate: files line 662
+zh duplicate: notes line 663
+zh duplicate: tabSettings line 664
+zh duplicate: langZh line 665
+zh duplicate: langEn line 666
+zh duplicate: langJa line 667
+zh duplicate: navGlobal line 668
+zh duplicate: navTenants line 669
+zh duplicate: navSystemModels line 670
+zh duplicate: navTenantManagement line 671
+zh duplicate: navUsersTeam line 672
+zh duplicate: navTenantSettings line 673
+zh duplicate: adminConsole line 674
+zh duplicate: globalDashboard line 675
+zh duplicate: statusIndexing line 676
+zh duplicate: statusReady line 677
+zh duplicate: ragSettings line 680
+zh duplicate: enableRerank line 681
+zh duplicate: enableRerankDesc line 682
+zh duplicate: selectRerankModel line 683
+zh duplicate: selectModelPlaceholder line 684
+zh duplicate: headerModelSelection line 686
+zh duplicate: headerHyperparams line 687
+zh duplicate: headerIndexing line 688
+zh duplicate: headerRetrieval line 689
+zh duplicate: btnManageModels line 690
+zh duplicate: lblLLM line 692
+zh duplicate: lblEmbedding line 693
+zh duplicate: lblRerankRef line 694
+zh duplicate: lblTemperature line 695
+zh duplicate: lblMaxTokens line 696
+zh duplicate: lblChunkSize line 697
+zh duplicate: lblChunkOverlap line 698
+zh duplicate: lblTopK line 699
+zh duplicate: lblRerank line 700
+zh duplicate: idxModalTitle line 702
+zh duplicate: idxDesc line 703
+zh duplicate: idxFiles line 704
+zh duplicate: idxMethod line 705
+zh duplicate: idxEmbeddingModel line 706
+zh duplicate: idxStart line 707
+zh duplicate: idxCancel line 708
+zh duplicate: idxAuto line 709
+zh duplicate: idxCustom line 710
+zh duplicate: mmTitle line 712
+zh duplicate: mmAddBtn line 713
+zh duplicate: mmEdit line 714
+zh duplicate: mmDelete line 715
+zh duplicate: mmEmpty line 716
+zh duplicate: mmFormName line 717
+zh duplicate: mmFormProvider line 718
+zh duplicate: mmFormModelId line 719
+zh duplicate: mmFormBaseUrl line 720
+zh duplicate: mmFormType line 721
+zh duplicate: mmFormVision line 722
+zh duplicate: mmFormDimensions line 723
+zh duplicate: mmFormDimensionsHelp line 724
+zh duplicate: mmSave line 725
+zh duplicate: mmCancel line 726
+zh duplicate: mmErrorNotAuthenticated line 727
+zh duplicate: mmErrorTitle line 728
+zh duplicate: modelEnabled line 729
+zh duplicate: modelDisabled line 730
+zh duplicate: confirmChangeEmbeddingModel line 731
+zh duplicate: embeddingModelWarning line 732
+zh duplicate: sourcePreview line 733
+zh duplicate: matchScore line 734
+zh duplicate: copyContent line 735
+zh duplicate: copySuccess line 736
+zh duplicate: selectLLMModel line 739
+zh duplicate: selectEmbeddingModel line 740
+zh duplicate: defaultForUploads line 741
+zh duplicate: noRerankModel line 742
+zh duplicate: vectorSimilarityThreshold line 743
+zh duplicate: rerankSimilarityThreshold line 744
+zh duplicate: filterLowResults line 745
+zh duplicate: fullTextSearch line 746
+zh duplicate: hybridVectorWeight line 747
+zh duplicate: hybridVectorWeightDesc line 748
+zh duplicate: lblQueryExpansion line 749
+zh duplicate: lblHyDE line 750
+zh duplicate: lblQueryExpansionDesc line 751
+zh duplicate: lblHyDEDesc line 752
+zh duplicate: apiKeyValidationFailed line 754
+zh duplicate: keepOriginalKey line 755
+zh duplicate: leaveEmptyNoChange line 756
+zh duplicate: mmFormApiKey line 757
+zh duplicate: mmFormApiKeyPlaceholder line 758
+zh duplicate: reconfigureFile line 761
+zh duplicate: modifySettings line 762
+zh duplicate: filesCount line 763
+zh duplicate: allFilesIndexed line 764
+zh duplicate: noEmbeddingModels line 765
+zh duplicate: reconfigure line 766
+zh duplicate: refresh line 767
+zh duplicate: settings line 768
+zh duplicate: needLogin line 769
+zh duplicate: citationSources line 770
+zh duplicate: chunkNumber line 771
+zh duplicate: getUserListFailed line 772
+zh duplicate: usernamePasswordRequired line 773
+zh duplicate: passwordMinLength line 774
+zh duplicate: userCreatedSuccess line 775
+zh duplicate: createUserFailed line 776
+zh duplicate: userPromotedToAdmin line 777
+zh duplicate: userDemotedFromAdmin line 778
+zh duplicate: updateUserFailed line 779
+zh duplicate: confirmDeleteUser line 780
+zh duplicate: deleteUser line 781
+zh duplicate: deleteUserFailed line 782
+zh duplicate: userDeletedSuccessfully line 783
+zh duplicate: makeUserAdmin line 784
+zh duplicate: makeUserRegular line 785
+zh duplicate: loading line 786
+zh duplicate: noUsers line 787
+zh duplicate: aiAssistant line 790
+zh duplicate: polishContent line 791
+zh duplicate: expandContent line 792
+zh duplicate: summarizeContent line 793
+zh duplicate: translateToEnglish line 794
+zh duplicate: fixGrammar line 795
+zh duplicate: aiCommandInstructPolish line 796
+zh duplicate: aiCommandInstructExpand line 797
+zh duplicate: aiCommandInstructSummarize line 798
+zh duplicate: aiCommandInstructTranslateToEn line 799
+zh duplicate: aiCommandInstructFixGrammar line 800
+zh duplicate: aiCommandsPreset line 801
+zh duplicate: aiCommandsCustom line 802
+zh duplicate: aiCommandsCustomPlaceholder line 803
+zh duplicate: aiCommandsReferenceContext line 804
+zh duplicate: aiCommandsStartGeneration line 805
+zh duplicate: aiCommandsResult line 806
+zh duplicate: aiCommandsGenerating line 807
+zh duplicate: aiCommandsApplyResult line 808
+zh duplicate: aiCommandsGoBack line 809
+zh duplicate: aiCommandsReset line 810
+zh duplicate: aiCommandsModalPreset line 811
+zh duplicate: aiCommandsModalCustom line 812
+zh duplicate: aiCommandsModalCustomPlaceholder line 813
+zh duplicate: aiCommandsModalBasedOnSelection line 814
+zh duplicate: aiCommandsModalResult line 815
+zh duplicate: aiCommandsModalApply line 816
+zh duplicate: fillAllFields line 819
+zh duplicate: passwordMismatch line 820
+zh duplicate: newPasswordMinLength line 821
+zh duplicate: changePasswordFailed line 822
+zh duplicate: changePasswordTitle line 823
+zh duplicate: changing line 824
+zh duplicate: searchResults line 825
+zh duplicate: visionModelSettings line 828
+zh duplicate: defaultVisionModel line 829
+zh duplicate: loadVisionModelFailed line 830
+zh duplicate: loadFailed line 831
+zh duplicate: saveVisionModelFailed line 832
+zh duplicate: noVisionModels line 833
+zh duplicate: selectVisionModel line 834
+zh duplicate: visionModelHelp line 835
+zh duplicate: mmErrorNameRequired line 836
+zh duplicate: mmErrorModelIdRequired line 837
+zh duplicate: mmErrorBaseUrlRequired line 838
+zh duplicate: mmRequiredAsterisk line 839
+zh duplicate: typeLLM line 841
+zh duplicate: typeEmbedding line 842
+zh duplicate: typeRerank line 843
+zh duplicate: typeVision line 844
+zh duplicate: welcome line 846
+zh duplicate: placeholderWithFiles line 847
+zh duplicate: placeholderEmpty line 848
+zh duplicate: analyzing line 849
+zh duplicate: errorGeneric line 850
+zh duplicate: errorLabel line 851
+zh duplicate: errorNoModel line 852
+zh duplicate: aiDisclaimer line 853
+zh duplicate: confirmClear line 854
+zh duplicate: removeFile line 855
+zh duplicate: apiError line 856
+zh duplicate: geminiError line 857
+zh duplicate: processedButNoText line 858
+zh duplicate: unitByte line 859
+zh duplicate: readingFailed line 860
+zh duplicate: copy line 862
+zh duplicate: copied line 863
+zh duplicate: logout line 866
+zh duplicate: changePassword line 867
+zh duplicate: userManagement line 868
+zh duplicate: userList line 869
+zh duplicate: addUser line 870
+zh duplicate: username line 871
+zh duplicate: password line 872
+zh duplicate: confirmPassword line 873
+zh duplicate: currentPassword line 874
+zh duplicate: newPassword line 875
+zh duplicate: createUser line 876
+zh duplicate: admin line 877
+zh duplicate: user line 878
+zh duplicate: adminUser line 879
+zh duplicate: confirmChange line 880
+zh duplicate: changeUserPassword line 881
+zh duplicate: enterNewPassword line 882
+zh duplicate: createdAt line 883
+zh duplicate: newChat line 884
+zh duplicate: selectKnowledgeGroups line 887
+zh duplicate: searchGroupsPlaceholder line 888
+zh duplicate: done line 889
+zh duplicate: all line 890
+zh duplicate: noGroupsFound line 891
+zh duplicate: noGroups line 892
+zh duplicate: autoRefresh line 895
+zh duplicate: refreshInterval line 896
+zh duplicate: kbManagement line 899
+zh duplicate: kbManagementDesc line 900
+zh duplicate: searchPlaceholder line 901
+zh duplicate: allGroups line 902
+zh duplicate: allStatus line 903
+zh duplicate: statusReadyFragment line 904
+zh duplicate: statusFailedFragment line 905
+zh duplicate: statusIndexingFragment line 906
+zh duplicate: uploadFile line 907
+zh duplicate: fileName line 908
+zh duplicate: size line 909
+zh duplicate: status line 910
+zh duplicate: groups line 911
+zh duplicate: actions line 912
+zh duplicate: groupsActions line 913
+zh duplicate: noFilesFound line 914
+zh duplicate: showingRange line 915
+zh duplicate: confirmDeleteFile line 916
+zh duplicate: fileDeleted line 917
+zh duplicate: deleteFailed line 918
+zh duplicate: confirmClearKB line 919
+zh duplicate: kbCleared line 920
+zh duplicate: clearFailed line 921
+zh duplicate: loginRequired line 922
+zh duplicate: uploadErrors line 923
+zh duplicate: uploadWarning line 924
+zh duplicate: uploadFailed line 925
+zh duplicate: preview line 926
+zh duplicate: addGroup line 927
+zh duplicate: delete line 928
+zh duplicate: retry line 929
+zh duplicate: retrying line 930
+zh duplicate: retrySuccess line 931
+zh duplicate: retryFailed line 932
+zh duplicate: chunkInfo line 933
+zh duplicate: totalChunks line 934
+zh duplicate: chunkIndex line 935
+zh duplicate: contentLength line 936
+zh duplicate: position line 937
+zh duplicate: reconfigureTitle line 940
+zh duplicate: reconfigureDesc line 941
+zh duplicate: indexingConfigTitle line 942
+zh duplicate: indexingConfigDesc line 943
+zh duplicate: pendingFiles line 944
+zh duplicate: processingMode line 945
+zh duplicate: analyzingFile line 946
+zh duplicate: recommendationReason line 947
+zh duplicate: fastMode line 948
+zh duplicate: fastModeDesc line 949
+zh duplicate: preciseMode line 950
+zh duplicate: preciseModeDesc line 951
+zh duplicate: fastModeFeatures line 952
+zh duplicate: fastFeature1 line 953
+zh duplicate: fastFeature2 line 954
+zh duplicate: fastFeature3 line 955
+zh duplicate: fastFeature4 line 956
+zh duplicate: fastFeature5 line 957
+zh duplicate: preciseModeFeatures line 958
+zh duplicate: preciseFeature1 line 959
+zh duplicate: preciseFeature2 line 960
+zh duplicate: preciseFeature3 line 961
+zh duplicate: preciseFeature4 line 962
+zh duplicate: preciseFeature5 line 963
+zh duplicate: preciseFeature6 line 964
+zh duplicate: embeddingModel line 965
+zh duplicate: pleaseSelect line 966
+zh duplicate: pleaseSelectKnowledgeGroupFirst line 967
+zh duplicate: selectUnassignGroupWarning line 968
+zh duplicate: chunkConfig line 969
+zh duplicate: chunkSize line 970
+zh duplicate: min line 971
+zh duplicate: max line 972
+zh duplicate: chunkOverlap line 973
+zh duplicate: modelLimitsInfo line 974
+zh duplicate: model line 975
+zh duplicate: maxChunkSize line 976
+zh duplicate: maxOverlapSize line 977
+zh duplicate: maxBatchSize line 978
+zh duplicate: envLimitWeaker line 979
+zh duplicate: optimizationTips line 980
+zh duplicate: tipChunkTooLarge line 981
+zh duplicate: tipOverlapSmall line 982
+zh duplicate: tipMaxValues line 983
+zh duplicate: tipPreciseCost line 984
+zh duplicate: selectEmbeddingFirst line 985
+zh duplicate: confirmPreciseCost line 986
+zh duplicate: startProcessing line 987
+zh duplicate: notebooks line 990
+zh duplicate: notebooksDesc line 991
+zh duplicate: createNotebook line 992
+zh duplicate: chatWithNotebook line 993
+zh duplicate: editNotebook line 994
+zh duplicate: deleteNotebook line 995
+zh duplicate: noDescription line 996
+zh duplicate: noNotebooks line 999
+zh duplicate: createFailed line 1000
+zh duplicate: confirmDeleteNotebook line 1001
+zh duplicate: createNotebookTitle line 1008
+zh duplicate: createFailedRetry line 1009
+zh duplicate: updateFailedRetry line 1010
+zh duplicate: name line 1011
+zh duplicate: nameHelp line 1012
+zh duplicate: namePlaceholder line 1013
+zh duplicate: shortDescription line 1014
+zh duplicate: descPlaceholder line 1015
+zh duplicate: creating line 1019
+zh duplicate: createNow line 1020
+zh duplicate: saving line 1021
+zh duplicate: save line 1022
+zh duplicate: chatTitle line 1025
+zh duplicate: chatDesc line 1026
+zh duplicate: viewHistory line 1027
+zh duplicate: saveSettingsFailed line 1028
+zh duplicate: loginToUpload line 1029
+zh duplicate: fileSizeLimitExceeded line 1030
+zh duplicate: unsupportedFileType line 1031
+zh duplicate: readFailed line 1032
+zh duplicate: loadHistoryFailed line 1033
+zh duplicate: loadingUserData line 1034
+zh duplicate: errorMessage line 1035
+zh duplicate: welcomeMessage line 1036
+zh duplicate: selectKnowledgeGroup line 1037
+zh duplicate: allKnowledgeGroups line 1038
+zh duplicate: unknownGroup line 1039
+zh duplicate: selectedGroupsCount line 1040
+zh duplicate: generalSettings line 1043
+zh duplicate: modelManagement line 1044
+zh duplicate: languageSettings line 1045
+zh duplicate: passwordChangeSuccess line 1046
+zh duplicate: passwordChangeFailed line 1047
+zh duplicate: create line 1048
+zh duplicate: validationFailedMsg line 1049
+zh duplicate: navChat line 1053
+zh duplicate: navCoach line 1054
+zh duplicate: navKnowledge line 1055
+zh duplicate: navKnowledgeGroups line 1056
+zh duplicate: navNotebook line 1057
+zh duplicate: notebookDesc line 1058
+zh duplicate: newNote line 1059
+zh duplicate: editNote line 1060
+zh duplicate: noNotesFound line 1061
+zh duplicate: startByCreatingNote line 1062
+zh duplicate: navCrawler line 1063
+zh duplicate: expandMenu line 1064
+zh duplicate: switchLanguage line 1065
+zh duplicate: importFolder line 1067
+zh duplicate: createPDFNote line 1070
+zh duplicate: screenshotPreview line 1071
+zh duplicate: associateKnowledgeGroup line 1072
+zh duplicate: globalNoSpecificGroup line 1073
+zh duplicate: title line 1074
+zh duplicate: enterNoteTitle line 1075
+zh duplicate: contentOCR line 1076
+zh duplicate: extractingText line 1077
+zh duplicate: analyzingImage line 1078
+zh duplicate: noTextExtracted line 1079
+zh duplicate: saveNote line 1080
+zh duplicate: page line 1083
+zh duplicate: placeholderText line 1084
+zh duplicate: createNewNotebook line 1087
+zh duplicate: nameField line 1088
+zh duplicate: required line 1089
+zh duplicate: exampleResearch line 1090
+zh duplicate: shortDescriptionField line 1091
+zh duplicate: describePurpose line 1092
+zh duplicate: creationFailed line 1095
+zh duplicate: preparingPDFConversion line 1098
+zh duplicate: pleaseWait line 1099
+zh duplicate: convertingPDF line 1100
+zh duplicate: pdfConversionFailed line 1101
+zh duplicate: pdfConversionError line 1102
+zh duplicate: pdfLoadFailed line 1103
+zh duplicate: pdfLoadError line 1104
+zh duplicate: downloadingPDF line 1105
+zh duplicate: loadingPDF line 1106
+zh duplicate: zoomOut line 1107
+zh duplicate: zoomIn line 1108
+zh duplicate: resetZoom line 1109
+zh duplicate: selectPageNumber line 1110
+zh duplicate: enterPageNumber line 1111
+zh duplicate: exitSelectionMode line 1112
+zh duplicate: clickToSelectAndNote line 1113
+zh duplicate: regeneratePDF line 1114
+zh duplicate: downloadPDF line 1115
+zh duplicate: openInNewWindow line 1116
+zh duplicate: exitFullscreen line 1117
+zh duplicate: fullscreenDisplay line 1118
+zh duplicate: pdfPreview line 1119
+zh duplicate: converting line 1120
+zh duplicate: generatePDFPreview line 1121
+zh duplicate: previewNotSupported line 1122
+zh duplicate: confirmRegeneratePDF line 1125
+zh duplicate: pdfPreviewReady line 1128
+zh duplicate: convertingInProgress line 1129
+zh duplicate: conversionFailed line 1130
+zh duplicate: generatePDFPreviewButton line 1131
+zh duplicate: checkPDFStatusFailed line 1134
+zh duplicate: requestRegenerationFailed line 1135
+zh duplicate: downloadPDFFailed line 1136
+zh duplicate: openPDFInNewTabFailed line 1137
+zh duplicate: invalidFile line 1140
+zh duplicate: incompleteFileInfo line 1141
+zh duplicate: unsupportedFileFormat line 1142
+zh duplicate: willUseFastMode line 1143
+zh duplicate: formatNoPrecise line 1144
+zh duplicate: smallFileFastOk line 1145
+zh duplicate: mixedContentPreciseRecommended line 1146
+zh duplicate: willIncurApiCost line 1147
+zh duplicate: largeFilePreciseRecommended line 1148
+zh duplicate: longProcessingTime line 1149
+zh duplicate: highApiCost line 1150
+zh duplicate: considerFileSplitting line 1151
+zh duplicate: dragDropUploadTitle line 1154
+zh duplicate: dragDropUploadDesc line 1155
+zh duplicate: supportedFormats line 1156
+zh duplicate: browseFiles line 1157
+zh duplicate: recommendationMsg line 1160
+zh duplicate: autoAdjustChunk line 1161
+zh duplicate: autoAdjustOverlap line 1162
+zh duplicate: autoAdjustOverlapMin line 1163
+zh duplicate: loadLimitsFailed line 1164
+zh duplicate: maxValueMsg line 1165
+zh duplicate: overlapRatioLimit line 1166
+zh duplicate: onlyAdminCanModify line 1167
+zh duplicate: dragToSelect line 1168
+zh duplicate: fillTargetName line 1171
+zh duplicate: submitFailed line 1172
+zh duplicate: importFolderTitle line 1173
+zh duplicate: importFolderTip line 1174
+zh duplicate: lblTargetGroup line 1175
+zh duplicate: placeholderNewGroup line 1176
+zh duplicate: importToCurrentGroup line 1177
+zh duplicate: nextStep line 1178
+zh duplicate: selectedFilesCount line 1182
+zh duplicate: clickToSelectFolder line 1183
+zh duplicate: selectFolderTip line 1184
+zh duplicate: importComplete line 1185
+zh duplicate: importedFromLocalFolder line 1186
+zh duplicate: historyTitle line 1189
+zh duplicate: confirmDeleteHistory line 1190
+zh duplicate: deleteHistorySuccess line 1191
+zh duplicate: deleteHistoryFailed line 1192
+zh duplicate: yesterday line 1193
+zh duplicate: daysAgo line 1194
+zh duplicate: historyMessages line 1195
+zh duplicate: noHistory line 1196
+zh duplicate: noHistoryDesc line 1197
+zh duplicate: loadMore line 1198
+zh duplicate: loadingHistoriesFailed line 1199
+zh duplicate: supportedFormatsInfo line 1200
+zh duplicate: aiCommandsError line 1203
+zh duplicate: appTitle line 1204
+zh duplicate: loginTitle line 1205
+zh duplicate: loginDesc line 1206
+zh duplicate: loginButton line 1207
+zh duplicate: loginError line 1208
+zh duplicate: unknownError line 1209
+zh duplicate: usernamePlaceholder line 1210
+zh duplicate: passwordPlaceholder line 1211
+zh duplicate: registerButton line 1212
+zh duplicate: langZh line 1213
+zh duplicate: langEn line 1214
+zh duplicate: langJa line 1215
+zh duplicate: confirm line 1216
+zh duplicate: cancel line 1217
+zh duplicate: confirmTitle line 1218
+zh duplicate: confirmDeleteGroup line 1219
+zh duplicate: sidebarTitle line 1221
+zh duplicate: backToWorkspace line 1222
+zh duplicate: goToAdmin line 1223
+zh duplicate: sidebarDesc line 1224
+zh duplicate: tabFiles line 1225
+zh duplicate: files line 1226
+zh duplicate: notes line 1227
+zh duplicate: tabSettings line 1228
+zh duplicate: systemConfiguration line 1229
+zh duplicate: noFiles line 1230
+zh duplicate: noFilesDesc line 1231
+zh duplicate: addFile line 1232
+zh duplicate: clearAll line 1233
+zh duplicate: uploading line 1234
+zh duplicate: statusIndexing line 1235
+zh duplicate: statusReady line 1236
+zh duplicate: ragSettings line 1239
+zh duplicate: enableRerank line 1240
+zh duplicate: enableRerankDesc line 1241
+zh duplicate: selectRerankModel line 1242
+zh duplicate: selectModelPlaceholder line 1243
+zh duplicate: headerModelSelection line 1245
+zh duplicate: headerHyperparams line 1246
+zh duplicate: headerIndexing line 1247
+zh duplicate: headerRetrieval line 1248
+zh duplicate: btnManageModels line 1249
+zh duplicate: lblLLM line 1251
+zh duplicate: lblEmbedding line 1252
+zh duplicate: lblRerankRef line 1253
+zh duplicate: lblTemperature line 1254
+zh duplicate: lblMaxTokens line 1255
+zh duplicate: lblChunkSize line 1256
+zh duplicate: lblChunkOverlap line 1257
+zh duplicate: lblTopK line 1258
+zh duplicate: lblRerank line 1259
+zh duplicate: idxModalTitle line 1261
+zh duplicate: idxDesc line 1262
+zh duplicate: idxFiles line 1263
+zh duplicate: idxMethod line 1264
+zh duplicate: idxEmbeddingModel line 1265
+zh duplicate: idxStart line 1266
+zh duplicate: idxCancel line 1267
+zh duplicate: idxAuto line 1268
+zh duplicate: idxCustom line 1269
+zh duplicate: mmTitle line 1271
+zh duplicate: mmAddBtn line 1272
+zh duplicate: mmEdit line 1273
+zh duplicate: mmDelete line 1274
+zh duplicate: mmEmpty line 1275
+zh duplicate: mmFormName line 1276
+zh duplicate: mmFormProvider line 1277
+zh duplicate: mmFormModelId line 1278
+zh duplicate: mmFormBaseUrl line 1279
+zh duplicate: mmFormType line 1280
+zh duplicate: mmFormVision line 1281
+zh duplicate: mmFormDimensions line 1282
+zh duplicate: mmFormDimensionsHelp line 1283
+zh duplicate: mmSave line 1284
+zh duplicate: mmCancel line 1285
+zh duplicate: mmErrorNotAuthenticated line 1286
+zh duplicate: mmErrorTitle line 1287
+zh duplicate: modelEnabled line 1288
+zh duplicate: modelDisabled line 1289
+zh duplicate: confirmChangeEmbeddingModel line 1290
+zh duplicate: embeddingModelWarning line 1291
+zh duplicate: sourcePreview line 1292
+zh duplicate: matchScore line 1293
+zh duplicate: copyContent line 1294
+zh duplicate: copySuccess line 1295
+zh duplicate: selectLLMModel line 1298
+zh duplicate: selectEmbeddingModel line 1299
+zh duplicate: defaultForUploads line 1300
+zh duplicate: noRerankModel line 1301
+zh duplicate: vectorSimilarityThreshold line 1302
+zh duplicate: rerankSimilarityThreshold line 1303
+zh duplicate: filterLowResults line 1304
+zh duplicate: noteCreatedSuccess line 1305
+zh duplicate: noteCreatedFailed line 1306
+zh duplicate: fullTextSearch line 1307
+zh duplicate: hybridVectorWeight line 1308
+zh duplicate: hybridVectorWeightDesc line 1309
+zh duplicate: lblQueryExpansion line 1310
+zh duplicate: lblHyDE line 1311
+zh duplicate: lblQueryExpansionDesc line 1312
+zh duplicate: lblHyDEDesc line 1313
+zh duplicate: apiKeyValidationFailed line 1315
+zh duplicate: keepOriginalKey line 1316
+zh duplicate: leaveEmptyNoChange line 1317
+zh duplicate: mmFormApiKey line 1318
+zh duplicate: mmFormApiKeyPlaceholder line 1319
+zh duplicate: reconfigureFile line 1322
+zh duplicate: modifySettings line 1323
+zh duplicate: filesCount line 1324
+zh duplicate: allFilesIndexed line 1325
+zh duplicate: noEmbeddingModels line 1326
+zh duplicate: reconfigure line 1327
+zh duplicate: refresh line 1328
+zh duplicate: settings line 1329
+zh duplicate: needLogin line 1330
+zh duplicate: citationSources line 1331
+zh duplicate: chunkNumber line 1332
+zh duplicate: getUserListFailed line 1333
+zh duplicate: usernamePasswordRequired line 1334
+zh duplicate: passwordMinLength line 1335
+zh duplicate: userCreatedSuccess line 1336
+zh duplicate: createUserFailed line 1337
+zh duplicate: userPromotedToAdmin line 1338
+zh duplicate: userDemotedFromAdmin line 1339
+zh duplicate: updateUserFailed line 1340
+zh duplicate: confirmDeleteUser line 1341
+zh duplicate: deleteUser line 1342
+zh duplicate: deleteUserFailed line 1343
+zh duplicate: userDeletedSuccessfully line 1344
+zh duplicate: makeUserAdmin line 1345
+zh duplicate: makeUserRegular line 1346
+zh duplicate: loading line 1347
+zh duplicate: noUsers line 1348
+zh duplicate: aiAssistant line 1351
+zh duplicate: polishContent line 1352
+zh duplicate: expandContent line 1353
+zh duplicate: summarizeContent line 1354
+zh duplicate: translateToEnglish line 1355
+zh duplicate: fixGrammar line 1356
+zh duplicate: aiCommandInstructPolish line 1357
+zh duplicate: aiCommandInstructExpand line 1358
+zh duplicate: aiCommandInstructSummarize line 1359
+zh duplicate: aiCommandInstructTranslateToEn line 1360
+zh duplicate: aiCommandInstructFixGrammar line 1361
+zh duplicate: aiCommandsPreset line 1362
+zh duplicate: aiCommandsCustom line 1363
+zh duplicate: aiCommandsCustomPlaceholder line 1364
+zh duplicate: aiCommandsReferenceContext line 1365
+zh duplicate: aiCommandsStartGeneration line 1366
+zh duplicate: aiCommandsResult line 1367
+zh duplicate: aiCommandsGenerating line 1368
+zh duplicate: aiCommandsApplyResult line 1369
+zh duplicate: aiCommandsGoBack line 1370
+zh duplicate: aiCommandsReset line 1371
+zh duplicate: aiCommandsModalPreset line 1372
+zh duplicate: aiCommandsModalCustom line 1373
+zh duplicate: aiCommandsModalCustomPlaceholder line 1374
+zh duplicate: aiCommandsModalBasedOnSelection line 1375
+zh duplicate: aiCommandsModalResult line 1376
+zh duplicate: aiCommandsModalApply line 1377
+zh duplicate: fillAllFields line 1380
+zh duplicate: passwordMismatch line 1381
+zh duplicate: newPasswordMinLength line 1382
+zh duplicate: changePasswordFailed line 1383
+zh duplicate: changePasswordTitle line 1384
+zh duplicate: changing line 1385
+zh duplicate: searchResults line 1386
+zh duplicate: visionModelSettings line 1389
+zh duplicate: defaultVisionModel line 1390
+zh duplicate: loadVisionModelFailed line 1391
+zh duplicate: loadFailed line 1392
+zh duplicate: saveVisionModelFailed line 1393
+zh duplicate: noVisionModels line 1394
+zh duplicate: selectVisionModel line 1395
+zh duplicate: visionModelHelp line 1396
+zh duplicate: mmErrorNameRequired line 1397
+zh duplicate: mmErrorModelIdRequired line 1398
+zh duplicate: mmErrorBaseUrlRequired line 1399
+zh duplicate: mmRequiredAsterisk line 1400
+zh duplicate: typeLLM line 1402
+zh duplicate: typeEmbedding line 1403
+zh duplicate: typeRerank line 1404
+zh duplicate: typeVision line 1405
+zh duplicate: welcome line 1407
+zh duplicate: placeholderWithFiles line 1408
+zh duplicate: placeholderEmpty line 1409
+zh duplicate: analyzing line 1410
+zh duplicate: errorGeneric line 1411
+zh duplicate: errorLabel line 1412
+zh duplicate: errorNoModel line 1413
+zh duplicate: aiDisclaimer line 1414
+zh duplicate: confirmClear line 1415
+zh duplicate: removeFile line 1416
+zh duplicate: apiError line 1417
+zh duplicate: geminiError line 1418
+zh duplicate: processedButNoText line 1419
+zh duplicate: unitByte line 1420
+zh duplicate: readingFailed line 1421
+zh duplicate: copy line 1423
+zh duplicate: copied line 1424
+zh duplicate: logout line 1427
+zh duplicate: changePassword line 1428
+zh duplicate: userManagement line 1429
+zh duplicate: userList line 1430
+zh duplicate: addUser line 1431
+zh duplicate: username line 1432
+zh duplicate: password line 1433
+zh duplicate: confirmPassword line 1434
+zh duplicate: currentPassword line 1435
+zh duplicate: newPassword line 1436
+zh duplicate: createUser line 1437
+zh duplicate: admin line 1438
+zh duplicate: user line 1439
+zh duplicate: adminUser line 1440
+zh duplicate: confirmChange line 1441
+zh duplicate: changeUserPassword line 1442
+zh duplicate: enterNewPassword line 1443
+zh duplicate: createdAt line 1444
+zh duplicate: newChat line 1445
+zh duplicate: kbManagement line 1448
+zh duplicate: kbManagementDesc line 1449
+zh duplicate: searchPlaceholder line 1450
+zh duplicate: allGroups line 1451
+zh duplicate: allStatus line 1452
+zh duplicate: statusReadyFragment line 1453
+zh duplicate: statusFailedFragment line 1454
+zh duplicate: statusIndexingFragment line 1455
+zh duplicate: uploadFile line 1456
+zh duplicate: fileName line 1457
+zh duplicate: size line 1458
+zh duplicate: status line 1459
+zh duplicate: groups line 1460
+zh duplicate: actions line 1461
+zh duplicate: groupsActions line 1462
+zh duplicate: noFilesFound line 1463
+zh duplicate: showingRange line 1464
+zh duplicate: confirmDeleteFile line 1465
+zh duplicate: fileDeleted line 1466
+zh duplicate: deleteFailed line 1467
+zh duplicate: fileAddedToGroup line 1468
+zh duplicate: failedToAddToGroup line 1469
+zh duplicate: fileRemovedFromGroup line 1470
+zh duplicate: failedToRemoveFromGroup line 1471
+zh duplicate: confirmClearKB line 1472
+zh duplicate: kbCleared line 1473
+zh duplicate: clearFailed line 1474
+zh duplicate: loginRequired line 1475
+zh duplicate: uploadErrors line 1476
+zh duplicate: uploadWarning line 1477
+zh duplicate: uploadFailed line 1478
+zh duplicate: preview line 1479
+zh duplicate: addGroup line 1480
+zh duplicate: delete line 1481
+zh duplicate: retry line 1482
+zh duplicate: retrying line 1483
+zh duplicate: retrySuccess line 1484
+zh duplicate: retryFailed line 1485
+zh duplicate: chunkInfo line 1486
+zh duplicate: totalChunks line 1487
+zh duplicate: chunkIndex line 1488
+zh duplicate: contentLength line 1489
+zh duplicate: position line 1490
+zh duplicate: reconfigureTitle line 1493
+zh duplicate: reconfigureDesc line 1494
+zh duplicate: indexingConfigTitle line 1495
+zh duplicate: indexingConfigDesc line 1496
+zh duplicate: pendingFiles line 1497
+zh duplicate: processingMode line 1498
+zh duplicate: analyzingFile line 1499
+zh duplicate: recommendationReason line 1500
+zh duplicate: fastMode line 1501
+zh duplicate: fastModeDesc line 1502
+zh duplicate: preciseMode line 1503
+zh duplicate: preciseModeDesc line 1504
+zh duplicate: fastModeFeatures line 1505
+zh duplicate: fastFeature1 line 1506
+zh duplicate: fastFeature2 line 1507
+zh duplicate: fastFeature3 line 1508
+zh duplicate: fastFeature4 line 1509
+zh duplicate: fastFeature5 line 1510
+zh duplicate: preciseModeFeatures line 1511
+zh duplicate: preciseFeature1 line 1512
+zh duplicate: preciseFeature2 line 1513
+zh duplicate: preciseFeature3 line 1514
+zh duplicate: preciseFeature4 line 1515
+zh duplicate: preciseFeature5 line 1516
+zh duplicate: preciseFeature6 line 1517
+zh duplicate: embeddingModel line 1518
+zh duplicate: pleaseSelect line 1519
+zh duplicate: pleaseSelectKnowledgeGroupFirst line 1520
+zh duplicate: selectUnassignGroupWarning line 1521
+zh duplicate: chunkConfig line 1522
+zh duplicate: chunkSize line 1523
+zh duplicate: min line 1524
+zh duplicate: max line 1525
+zh duplicate: chunkOverlap line 1526
+zh duplicate: modelLimitsInfo line 1527
+zh duplicate: model line 1528
+zh duplicate: maxChunkSize line 1529
+zh duplicate: maxOverlapSize line 1530
+zh duplicate: maxBatchSize line 1531
+zh duplicate: envLimitWeaker line 1532
+zh duplicate: optimizationTips line 1533
+zh duplicate: tipChunkTooLarge line 1534
+zh duplicate: tipOverlapSmall line 1535
+zh duplicate: tipMaxValues line 1536
+zh duplicate: tipPreciseCost line 1537
+zh duplicate: selectEmbeddingFirst line 1538
+zh duplicate: confirmPreciseCost line 1539
+zh duplicate: startProcessing line 1540
+zh duplicate: notebooks line 1543
+zh duplicate: notebooksDesc line 1544
+zh duplicate: createNotebook line 1545
+zh duplicate: chatWithNotebook line 1546
+zh duplicate: editNotebook line 1547
+zh duplicate: deleteNotebook line 1548
+zh duplicate: noDescription line 1549
+zh duplicate: hasIntro line 1550
+zh duplicate: noIntro line 1551
+zh duplicate: noNotebooks line 1552
+zh duplicate: createFailed line 1553
+zh duplicate: confirmDeleteNotebook line 1554
+zh duplicate: errorFileTooLarge line 1557
+zh duplicate: noFilesYet line 1558
+zh duplicate: createNotebookTitle line 1561
+zh duplicate: editNotebookTitle line 1562
+zh duplicate: createFailedRetry line 1563
+zh duplicate: updateFailedRetry line 1564
+zh duplicate: name line 1565
+zh duplicate: nameHelp line 1566
+zh duplicate: namePlaceholder line 1567
+zh duplicate: shortDescription line 1568
+zh duplicate: descPlaceholder line 1569
+zh duplicate: detailedIntro line 1570
+zh duplicate: introPlaceholder line 1571
+zh duplicate: introHelp line 1572
+zh duplicate: creating line 1573
+zh duplicate: createNow line 1574
+zh duplicate: saving line 1575
+zh duplicate: save line 1576
+zh duplicate: chatTitle line 1579
+zh duplicate: chatDesc line 1580
+zh duplicate: viewHistory line 1581
+zh duplicate: saveSettingsFailed line 1582
+zh duplicate: loginToUpload line 1583
+zh duplicate: fileSizeLimitExceeded line 1584
+zh duplicate: unsupportedFileType line 1585
+zh duplicate: readFailed line 1586
+zh duplicate: loadHistoryFailed line 1587
+zh duplicate: loadingUserData line 1588
+zh duplicate: errorMessage line 1589
+zh duplicate: welcomeMessage line 1590
+zh duplicate: selectKnowledgeGroup line 1591
+zh duplicate: allKnowledgeGroups line 1592
+zh duplicate: unknownGroup line 1593
+zh duplicate: selectedGroupsCount line 1594
+zh duplicate: generalSettings line 1597
+zh duplicate: modelManagement line 1598
+zh duplicate: languageSettings line 1599
+zh duplicate: passwordChangeSuccess line 1600
+zh duplicate: passwordChangeFailed line 1601
+zh duplicate: create line 1602
+zh duplicate: validationFailedMsg line 1603
+zh duplicate: navChat line 1607
+zh duplicate: navCoach line 1608
+zh duplicate: navKnowledge line 1609
+zh duplicate: navKnowledgeGroups line 1610
+zh duplicate: navCrawler line 1611
+zh duplicate: expandMenu line 1612
+zh duplicate: switchLanguage line 1613
+zh duplicate: selectKnowledgeGroups line 1616
+zh duplicate: searchGroupsPlaceholder line 1617
+zh duplicate: done line 1618
+zh duplicate: all line 1619
+zh duplicate: noGroupsFound line 1620
+zh duplicate: noGroups line 1621
+zh duplicate: autoRefresh line 1624
+zh duplicate: refreshInterval line 1625
+zh duplicate: errorRenderFlowchart line 1628
+zh duplicate: errorLoadData line 1629
+zh duplicate: confirmUnsupportedFile line 1630
+zh duplicate: errorReadFile line 1631
+zh duplicate: successUploadFile line 1632
+zh duplicate: errorUploadFile line 1633
+zh duplicate: errorProcessFile line 1634
+zh duplicate: errorTitleContentRequired line 1635
+zh duplicate: successNoteUpdated line 1636
+zh duplicate: successNoteCreated line 1637
+zh duplicate: errorSaveFailed line 1638
+zh duplicate: confirmDeleteNote line 1639
+zh duplicate: successNoteDeleted line 1640
+zh duplicate: confirmRemoveFileFromGroup line 1641
+zh duplicate: editNote line 1642
+zh duplicate: newNote line 1643
+zh duplicate: togglePreviewOpen line 1644
+zh duplicate: togglePreviewClose line 1645
+zh duplicate: noteTitlePlaceholder line 1646
+zh duplicate: noteContentPlaceholder line 1647
+zh duplicate: markdownPreviewArea line 1648
+zh duplicate: back line 1649
+zh duplicate: chatWithGroup line 1650
+zh duplicate: chatWithFile line 1651
+zh duplicate: filesCountLabel line 1652
+zh duplicate: notesCountLabel line 1653
+zh duplicate: indexIntoKB line 1654
+zh duplicate: noFilesOrNotes line 1655
+zh duplicate: importFolder line 1656
+zh duplicate: createPDFNote line 1659
+zh duplicate: screenshotPreview line 1660
+zh duplicate: associateKnowledgeGroup line 1661
+zh duplicate: globalNoSpecificGroup line 1662
+zh duplicate: title line 1663
+zh duplicate: enterNoteTitle line 1664
+zh duplicate: contentOCR line 1665
+zh duplicate: extractingText line 1666
+zh duplicate: analyzingImage line 1667
+zh duplicate: noTextExtracted line 1668
+zh duplicate: saveNote line 1669
+zh duplicate: page line 1672
+zh duplicate: placeholderText line 1673
+zh duplicate: createNewNotebook line 1676
+zh duplicate: nameField line 1677
+zh duplicate: required line 1678
+zh duplicate: exampleResearch line 1679
+zh duplicate: shortDescriptionField line 1680
+zh duplicate: describePurpose line 1681
+zh duplicate: detailedIntroField line 1682
+zh duplicate: provideBackgroundInfo line 1683
+zh duplicate: creationFailed line 1684
+zh duplicate: preparingPDFConversion line 1687
+zh duplicate: pleaseWait line 1688
+zh duplicate: convertingPDF line 1689
+zh duplicate: pdfConversionFailed line 1690
+zh duplicate: pdfConversionError line 1691
+zh duplicate: pdfLoadFailed line 1692
+zh duplicate: pdfLoadError line 1693
+zh duplicate: downloadingPDF line 1694
+zh duplicate: loadingPDF line 1695
+zh duplicate: zoomOut line 1696
+zh duplicate: zoomIn line 1697
+zh duplicate: resetZoom line 1698
+zh duplicate: selectPageNumber line 1699
+zh duplicate: enterPageNumber line 1700
+zh duplicate: exitSelectionMode line 1701
+zh duplicate: clickToSelectAndNote line 1702
+zh duplicate: regeneratePDF line 1703
+zh duplicate: downloadPDF line 1704
+zh duplicate: openInNewWindow line 1705
+zh duplicate: exitFullscreen line 1706
+zh duplicate: fullscreenDisplay line 1707
+zh duplicate: pdfPreview line 1708
+zh duplicate: converting line 1709
+zh duplicate: generatePDFPreview line 1710
+zh duplicate: previewNotSupported line 1711
+zh duplicate: confirmRegeneratePDF line 1714
+zh duplicate: pdfPreviewReady line 1717
+zh duplicate: convertingInProgress line 1718
+zh duplicate: conversionFailed line 1719
+zh duplicate: generatePDFPreviewButton line 1720
+zh duplicate: checkPDFStatusFailed line 1723
+zh duplicate: requestRegenerationFailed line 1724
+zh duplicate: downloadPDFFailed line 1725
+zh duplicate: openPDFInNewTabFailed line 1726
+zh duplicate: invalidFile line 1729
+zh duplicate: incompleteFileInfo line 1730
+zh duplicate: unsupportedFileFormat line 1731
+zh duplicate: willUseFastMode line 1732
+zh duplicate: formatNoPrecise line 1733
+zh duplicate: smallFileFastOk line 1734
+zh duplicate: mixedContentPreciseRecommended line 1735
+zh duplicate: willIncurApiCost line 1736
+zh duplicate: largeFilePreciseRecommended line 1737
+zh duplicate: longProcessingTime line 1738
+zh duplicate: highApiCost line 1739
+zh duplicate: considerFileSplitting line 1740
+zh duplicate: dragDropUploadTitle line 1743
+zh duplicate: dragDropUploadDesc line 1744
+zh duplicate: supportedFormats line 1745
+zh duplicate: browseFiles line 1746
+zh duplicate: recommendationMsg line 1749
+zh duplicate: autoAdjustChunk line 1750
+zh duplicate: autoAdjustOverlap line 1751
+zh duplicate: autoAdjustOverlapMin line 1752
+zh duplicate: loadLimitsFailed line 1753
+zh duplicate: maxValueMsg line 1754
+zh duplicate: overlapRatioLimit line 1755
+zh duplicate: onlyAdminCanModify line 1756
+zh duplicate: dragToSelect line 1757
+zh duplicate: fillTargetName line 1760
+zh duplicate: submitFailed line 1761
+zh duplicate: importFolderTitle line 1762
+zh duplicate: importFolderTip line 1763
+zh duplicate: lblTargetGroup line 1764
+zh duplicate: placeholderNewGroup line 1765
+zh duplicate: importToCurrentGroup line 1766
+zh duplicate: nextStep line 1767
+zh duplicate: lblImportSource line 1768
+zh duplicate: serverPath line 1769
+zh duplicate: localFolder line 1770
+zh duplicate: selectedFilesCount line 1771
+zh duplicate: clickToSelectFolder line 1772
+zh duplicate: selectFolderTip line 1773
+zh duplicate: importComplete line 1774
+zh duplicate: importedFromLocalFolder line 1775
+zh duplicate: historyTitle line 1778
+zh duplicate: confirmDeleteHistory line 1779
+zh duplicate: deleteHistorySuccess line 1780
+zh duplicate: deleteHistoryFailed line 1781
+zh duplicate: yesterday line 1782
+zh duplicate: daysAgo line 1783
+zh duplicate: historyMessages line 1784
+zh duplicate: noHistory line 1785
+zh duplicate: noHistoryDesc line 1786
+zh duplicate: loadMore line 1787
+zh duplicate: loadingHistoriesFailed line 1788
+zh duplicate: supportedFormatsInfo line 1789
+en duplicate: aiCommandsError line 1203
+en duplicate: appTitle line 1204
+en duplicate: loginTitle line 1205
+en duplicate: loginDesc line 1206
+en duplicate: loginButton line 1207
+en duplicate: loginError line 1208
+en duplicate: unknownError line 1209
+en duplicate: usernamePlaceholder line 1210
+en duplicate: passwordPlaceholder line 1211
+en duplicate: registerButton line 1212
+en duplicate: langZh line 1213
+en duplicate: langEn line 1214
+en duplicate: langJa line 1215
+en duplicate: confirm line 1216
+en duplicate: cancel line 1217
+en duplicate: confirmTitle line 1218
+en duplicate: confirmDeleteGroup line 1219
+en duplicate: sidebarTitle line 1221
+en duplicate: backToWorkspace line 1222
+en duplicate: goToAdmin line 1223
+en duplicate: sidebarDesc line 1224
+en duplicate: tabFiles line 1225
+en duplicate: files line 1226
+en duplicate: notes line 1227
+en duplicate: tabSettings line 1228
+en duplicate: systemConfiguration line 1229
+en duplicate: noFiles line 1230
+en duplicate: noFilesDesc line 1231
+en duplicate: addFile line 1232
+en duplicate: clearAll line 1233
+en duplicate: uploading line 1234
+en duplicate: statusIndexing line 1235
+en duplicate: statusReady line 1236
+en duplicate: ragSettings line 1239
+en duplicate: enableRerank line 1240
+en duplicate: enableRerankDesc line 1241
+en duplicate: selectRerankModel line 1242
+en duplicate: selectModelPlaceholder line 1243
+en duplicate: headerModelSelection line 1245
+en duplicate: headerHyperparams line 1246
+en duplicate: headerIndexing line 1247
+en duplicate: headerRetrieval line 1248
+en duplicate: btnManageModels line 1249
+en duplicate: lblLLM line 1251
+en duplicate: lblEmbedding line 1252
+en duplicate: lblRerankRef line 1253
+en duplicate: lblTemperature line 1254
+en duplicate: lblMaxTokens line 1255
+en duplicate: lblChunkSize line 1256
+en duplicate: lblChunkOverlap line 1257
+en duplicate: lblTopK line 1258
+en duplicate: lblRerank line 1259
+en duplicate: idxModalTitle line 1261
+en duplicate: idxDesc line 1262
+en duplicate: idxFiles line 1263
+en duplicate: idxMethod line 1264
+en duplicate: idxEmbeddingModel line 1265
+en duplicate: idxStart line 1266
+en duplicate: idxCancel line 1267
+en duplicate: idxAuto line 1268
+en duplicate: idxCustom line 1269
+en duplicate: mmTitle line 1271
+en duplicate: mmAddBtn line 1272
+en duplicate: mmEdit line 1273
+en duplicate: mmDelete line 1274
+en duplicate: mmEmpty line 1275
+en duplicate: mmFormName line 1276
+en duplicate: mmFormProvider line 1277
+en duplicate: mmFormModelId line 1278
+en duplicate: mmFormBaseUrl line 1279
+en duplicate: mmFormType line 1280
+en duplicate: mmFormVision line 1281
+en duplicate: mmFormDimensions line 1282
+en duplicate: mmFormDimensionsHelp line 1283
+en duplicate: mmSave line 1284
+en duplicate: mmCancel line 1285
+en duplicate: mmErrorNotAuthenticated line 1286
+en duplicate: mmErrorTitle line 1287
+en duplicate: modelEnabled line 1288
+en duplicate: modelDisabled line 1289
+en duplicate: confirmChangeEmbeddingModel line 1290
+en duplicate: embeddingModelWarning line 1291
+en duplicate: sourcePreview line 1292
+en duplicate: matchScore line 1293
+en duplicate: copyContent line 1294
+en duplicate: copySuccess line 1295
+en duplicate: selectLLMModel line 1298
+en duplicate: selectEmbeddingModel line 1299
+en duplicate: defaultForUploads line 1300
+en duplicate: noRerankModel line 1301
+en duplicate: vectorSimilarityThreshold line 1302
+en duplicate: rerankSimilarityThreshold line 1303
+en duplicate: filterLowResults line 1304
+en duplicate: noteCreatedSuccess line 1305
+en duplicate: noteCreatedFailed line 1306
+en duplicate: fullTextSearch line 1307
+en duplicate: hybridVectorWeight line 1308
+en duplicate: hybridVectorWeightDesc line 1309
+en duplicate: lblQueryExpansion line 1310
+en duplicate: lblHyDE line 1311
+en duplicate: lblQueryExpansionDesc line 1312
+en duplicate: lblHyDEDesc line 1313
+en duplicate: apiKeyValidationFailed line 1315
+en duplicate: keepOriginalKey line 1316
+en duplicate: leaveEmptyNoChange line 1317
+en duplicate: mmFormApiKey line 1318
+en duplicate: mmFormApiKeyPlaceholder line 1319
+en duplicate: reconfigureFile line 1322
+en duplicate: modifySettings line 1323
+en duplicate: filesCount line 1324
+en duplicate: allFilesIndexed line 1325
+en duplicate: noEmbeddingModels line 1326
+en duplicate: reconfigure line 1327
+en duplicate: refresh line 1328
+en duplicate: settings line 1329
+en duplicate: needLogin line 1330
+en duplicate: citationSources line 1331
+en duplicate: chunkNumber line 1332
+en duplicate: getUserListFailed line 1333
+en duplicate: usernamePasswordRequired line 1334
+en duplicate: passwordMinLength line 1335
+en duplicate: userCreatedSuccess line 1336
+en duplicate: createUserFailed line 1337
+en duplicate: userPromotedToAdmin line 1338
+en duplicate: userDemotedFromAdmin line 1339
+en duplicate: updateUserFailed line 1340
+en duplicate: confirmDeleteUser line 1341
+en duplicate: deleteUser line 1342
+en duplicate: deleteUserFailed line 1343
+en duplicate: userDeletedSuccessfully line 1344
+en duplicate: makeUserAdmin line 1345
+en duplicate: makeUserRegular line 1346
+en duplicate: loading line 1347
+en duplicate: noUsers line 1348
+en duplicate: aiAssistant line 1351
+en duplicate: polishContent line 1352
+en duplicate: expandContent line 1353
+en duplicate: summarizeContent line 1354
+en duplicate: translateToEnglish line 1355
+en duplicate: fixGrammar line 1356
+en duplicate: aiCommandInstructPolish line 1357
+en duplicate: aiCommandInstructExpand line 1358
+en duplicate: aiCommandInstructSummarize line 1359
+en duplicate: aiCommandInstructTranslateToEn line 1360
+en duplicate: aiCommandInstructFixGrammar line 1361
+en duplicate: aiCommandsPreset line 1362
+en duplicate: aiCommandsCustom line 1363
+en duplicate: aiCommandsCustomPlaceholder line 1364
+en duplicate: aiCommandsReferenceContext line 1365
+en duplicate: aiCommandsStartGeneration line 1366
+en duplicate: aiCommandsResult line 1367
+en duplicate: aiCommandsGenerating line 1368
+en duplicate: aiCommandsApplyResult line 1369
+en duplicate: aiCommandsGoBack line 1370
+en duplicate: aiCommandsReset line 1371
+en duplicate: aiCommandsModalPreset line 1372
+en duplicate: aiCommandsModalCustom line 1373
+en duplicate: aiCommandsModalCustomPlaceholder line 1374
+en duplicate: aiCommandsModalBasedOnSelection line 1375
+en duplicate: aiCommandsModalResult line 1376
+en duplicate: aiCommandsModalApply line 1377
+en duplicate: fillAllFields line 1380
+en duplicate: passwordMismatch line 1381
+en duplicate: newPasswordMinLength line 1382
+en duplicate: changePasswordFailed line 1383
+en duplicate: changePasswordTitle line 1384
+en duplicate: changing line 1385
+en duplicate: searchResults line 1386
+en duplicate: visionModelSettings line 1389
+en duplicate: defaultVisionModel line 1390
+en duplicate: loadVisionModelFailed line 1391
+en duplicate: loadFailed line 1392
+en duplicate: saveVisionModelFailed line 1393
+en duplicate: noVisionModels line 1394
+en duplicate: selectVisionModel line 1395
+en duplicate: visionModelHelp line 1396
+en duplicate: mmErrorNameRequired line 1397
+en duplicate: mmErrorModelIdRequired line 1398
+en duplicate: mmErrorBaseUrlRequired line 1399
+en duplicate: mmRequiredAsterisk line 1400
+en duplicate: typeLLM line 1402
+en duplicate: typeEmbedding line 1403
+en duplicate: typeRerank line 1404
+en duplicate: typeVision line 1405
+en duplicate: welcome line 1407
+en duplicate: placeholderWithFiles line 1408
+en duplicate: placeholderEmpty line 1409
+en duplicate: analyzing line 1410
+en duplicate: errorGeneric line 1411
+en duplicate: errorLabel line 1412
+en duplicate: errorNoModel line 1413
+en duplicate: aiDisclaimer line 1414
+en duplicate: confirmClear line 1415
+en duplicate: removeFile line 1416
+en duplicate: apiError line 1417
+en duplicate: geminiError line 1418
+en duplicate: processedButNoText line 1419
+en duplicate: unitByte line 1420
+en duplicate: readingFailed line 1421
+en duplicate: copy line 1423
+en duplicate: copied line 1424
+en duplicate: logout line 1427
+en duplicate: changePassword line 1428
+en duplicate: userManagement line 1429
+en duplicate: userList line 1430
+en duplicate: addUser line 1431
+en duplicate: username line 1432
+en duplicate: password line 1433
+en duplicate: confirmPassword line 1434
+en duplicate: currentPassword line 1435
+en duplicate: newPassword line 1436
+en duplicate: createUser line 1437
+en duplicate: admin line 1438
+en duplicate: user line 1439
+en duplicate: adminUser line 1440
+en duplicate: confirmChange line 1441
+en duplicate: changeUserPassword line 1442
+en duplicate: enterNewPassword line 1443
+en duplicate: createdAt line 1444
+en duplicate: newChat line 1445
+en duplicate: kbManagement line 1448
+en duplicate: kbManagementDesc line 1449
+en duplicate: searchPlaceholder line 1450
+en duplicate: allGroups line 1451
+en duplicate: allStatus line 1452
+en duplicate: statusReadyFragment line 1453
+en duplicate: statusFailedFragment line 1454
+en duplicate: statusIndexingFragment line 1455
+en duplicate: uploadFile line 1456
+en duplicate: fileName line 1457
+en duplicate: size line 1458
+en duplicate: status line 1459
+en duplicate: groups line 1460
+en duplicate: actions line 1461
+en duplicate: groupsActions line 1462
+en duplicate: noFilesFound line 1463
+en duplicate: showingRange line 1464
+en duplicate: confirmDeleteFile line 1465
+en duplicate: fileDeleted line 1466
+en duplicate: deleteFailed line 1467
+en duplicate: fileAddedToGroup line 1468
+en duplicate: failedToAddToGroup line 1469
+en duplicate: fileRemovedFromGroup line 1470
+en duplicate: failedToRemoveFromGroup line 1471
+en duplicate: confirmClearKB line 1472
+en duplicate: kbCleared line 1473
+en duplicate: clearFailed line 1474
+en duplicate: loginRequired line 1475
+en duplicate: uploadErrors line 1476
+en duplicate: uploadWarning line 1477
+en duplicate: uploadFailed line 1478
+en duplicate: preview line 1479
+en duplicate: addGroup line 1480
+en duplicate: delete line 1481
+en duplicate: retry line 1482
+en duplicate: retrying line 1483
+en duplicate: retrySuccess line 1484
+en duplicate: retryFailed line 1485
+en duplicate: chunkInfo line 1486
+en duplicate: totalChunks line 1487
+en duplicate: chunkIndex line 1488
+en duplicate: contentLength line 1489
+en duplicate: position line 1490
+en duplicate: reconfigureTitle line 1493
+en duplicate: reconfigureDesc line 1494
+en duplicate: indexingConfigTitle line 1495
+en duplicate: indexingConfigDesc line 1496
+en duplicate: pendingFiles line 1497
+en duplicate: processingMode line 1498
+en duplicate: analyzingFile line 1499
+en duplicate: recommendationReason line 1500
+en duplicate: fastMode line 1501
+en duplicate: fastModeDesc line 1502
+en duplicate: preciseMode line 1503
+en duplicate: preciseModeDesc line 1504
+en duplicate: fastModeFeatures line 1505
+en duplicate: fastFeature1 line 1506
+en duplicate: fastFeature2 line 1507
+en duplicate: fastFeature3 line 1508
+en duplicate: fastFeature4 line 1509
+en duplicate: fastFeature5 line 1510
+en duplicate: preciseModeFeatures line 1511
+en duplicate: preciseFeature1 line 1512
+en duplicate: preciseFeature2 line 1513
+en duplicate: preciseFeature3 line 1514
+en duplicate: preciseFeature4 line 1515
+en duplicate: preciseFeature5 line 1516
+en duplicate: preciseFeature6 line 1517
+en duplicate: embeddingModel line 1518
+en duplicate: pleaseSelect line 1519
+en duplicate: pleaseSelectKnowledgeGroupFirst line 1520
+en duplicate: selectUnassignGroupWarning line 1521
+en duplicate: chunkConfig line 1522
+en duplicate: chunkSize line 1523
+en duplicate: min line 1524
+en duplicate: max line 1525
+en duplicate: chunkOverlap line 1526
+en duplicate: modelLimitsInfo line 1527
+en duplicate: model line 1528
+en duplicate: maxChunkSize line 1529
+en duplicate: maxOverlapSize line 1530
+en duplicate: maxBatchSize line 1531
+en duplicate: envLimitWeaker line 1532
+en duplicate: optimizationTips line 1533
+en duplicate: tipChunkTooLarge line 1534
+en duplicate: tipOverlapSmall line 1535
+en duplicate: tipMaxValues line 1536
+en duplicate: tipPreciseCost line 1537
+en duplicate: selectEmbeddingFirst line 1538
+en duplicate: confirmPreciseCost line 1539
+en duplicate: startProcessing line 1540
+en duplicate: notebooks line 1543
+en duplicate: notebooksDesc line 1544
+en duplicate: createNotebook line 1545
+en duplicate: chatWithNotebook line 1546
+en duplicate: editNotebook line 1547
+en duplicate: deleteNotebook line 1548
+en duplicate: noDescription line 1549
+en duplicate: hasIntro line 1550
+en duplicate: noIntro line 1551
+en duplicate: noNotebooks line 1552
+en duplicate: createFailed line 1553
+en duplicate: confirmDeleteNotebook line 1554
+en duplicate: errorFileTooLarge line 1557
+en duplicate: noFilesYet line 1558
+en duplicate: createNotebookTitle line 1561
+en duplicate: editNotebookTitle line 1562
+en duplicate: createFailedRetry line 1563
+en duplicate: updateFailedRetry line 1564
+en duplicate: name line 1565
+en duplicate: nameHelp line 1566
+en duplicate: namePlaceholder line 1567
+en duplicate: shortDescription line 1568
+en duplicate: descPlaceholder line 1569
+en duplicate: detailedIntro line 1570
+en duplicate: introPlaceholder line 1571
+en duplicate: introHelp line 1572
+en duplicate: creating line 1573
+en duplicate: createNow line 1574
+en duplicate: saving line 1575
+en duplicate: save line 1576
+en duplicate: chatTitle line 1579
+en duplicate: chatDesc line 1580
+en duplicate: viewHistory line 1581
+en duplicate: saveSettingsFailed line 1582
+en duplicate: loginToUpload line 1583
+en duplicate: fileSizeLimitExceeded line 1584
+en duplicate: unsupportedFileType line 1585
+en duplicate: readFailed line 1586
+en duplicate: loadHistoryFailed line 1587
+en duplicate: loadingUserData line 1588
+en duplicate: errorMessage line 1589
+en duplicate: welcomeMessage line 1590
+en duplicate: selectKnowledgeGroup line 1591
+en duplicate: allKnowledgeGroups line 1592
+en duplicate: unknownGroup line 1593
+en duplicate: selectedGroupsCount line 1594
+en duplicate: generalSettings line 1597
+en duplicate: modelManagement line 1598
+en duplicate: languageSettings line 1599
+en duplicate: passwordChangeSuccess line 1600
+en duplicate: passwordChangeFailed line 1601
+en duplicate: create line 1602
+en duplicate: validationFailedMsg line 1603
+en duplicate: navChat line 1607
+en duplicate: navCoach line 1608
+en duplicate: navKnowledge line 1609
+en duplicate: navKnowledgeGroups line 1610
+en duplicate: navCrawler line 1611
+en duplicate: expandMenu line 1612
+en duplicate: switchLanguage line 1613
+en duplicate: selectKnowledgeGroups line 1616
+en duplicate: searchGroupsPlaceholder line 1617
+en duplicate: done line 1618
+en duplicate: all line 1619
+en duplicate: noGroupsFound line 1620
+en duplicate: noGroups line 1621
+en duplicate: autoRefresh line 1624
+en duplicate: refreshInterval line 1625
+en duplicate: errorRenderFlowchart line 1628
+en duplicate: errorLoadData line 1629
+en duplicate: confirmUnsupportedFile line 1630
+en duplicate: errorReadFile line 1631
+en duplicate: successUploadFile line 1632
+en duplicate: errorUploadFile line 1633
+en duplicate: errorProcessFile line 1634
+en duplicate: errorTitleContentRequired line 1635
+en duplicate: successNoteUpdated line 1636
+en duplicate: successNoteCreated line 1637
+en duplicate: errorSaveFailed line 1638
+en duplicate: confirmDeleteNote line 1639
+en duplicate: successNoteDeleted line 1640
+en duplicate: confirmRemoveFileFromGroup line 1641
+en duplicate: editNote line 1642
+en duplicate: newNote line 1643
+en duplicate: togglePreviewOpen line 1644
+en duplicate: togglePreviewClose line 1645
+en duplicate: noteTitlePlaceholder line 1646
+en duplicate: noteContentPlaceholder line 1647
+en duplicate: markdownPreviewArea line 1648
+en duplicate: back line 1649
+en duplicate: chatWithGroup line 1650
+en duplicate: chatWithFile line 1651
+en duplicate: filesCountLabel line 1652
+en duplicate: notesCountLabel line 1653
+en duplicate: indexIntoKB line 1654
+en duplicate: noFilesOrNotes line 1655
+en duplicate: importFolder line 1656
+en duplicate: createPDFNote line 1659
+en duplicate: screenshotPreview line 1660
+en duplicate: associateKnowledgeGroup line 1661
+en duplicate: globalNoSpecificGroup line 1662
+en duplicate: title line 1663
+en duplicate: enterNoteTitle line 1664
+en duplicate: contentOCR line 1665
+en duplicate: extractingText line 1666
+en duplicate: analyzingImage line 1667
+en duplicate: noTextExtracted line 1668
+en duplicate: saveNote line 1669
+en duplicate: page line 1672
+en duplicate: placeholderText line 1673
+en duplicate: createNewNotebook line 1676
+en duplicate: nameField line 1677
+en duplicate: required line 1678
+en duplicate: exampleResearch line 1679
+en duplicate: shortDescriptionField line 1680
+en duplicate: describePurpose line 1681
+en duplicate: detailedIntroField line 1682
+en duplicate: provideBackgroundInfo line 1683
+en duplicate: creationFailed line 1684
+en duplicate: preparingPDFConversion line 1687
+en duplicate: pleaseWait line 1688
+en duplicate: convertingPDF line 1689
+en duplicate: pdfConversionFailed line 1690
+en duplicate: pdfConversionError line 1691
+en duplicate: pdfLoadFailed line 1692
+en duplicate: pdfLoadError line 1693
+en duplicate: downloadingPDF line 1694
+en duplicate: loadingPDF line 1695
+en duplicate: zoomOut line 1696
+en duplicate: zoomIn line 1697
+en duplicate: resetZoom line 1698
+en duplicate: selectPageNumber line 1699
+en duplicate: enterPageNumber line 1700
+en duplicate: exitSelectionMode line 1701
+en duplicate: clickToSelectAndNote line 1702
+en duplicate: regeneratePDF line 1703
+en duplicate: downloadPDF line 1704
+en duplicate: openInNewWindow line 1705
+en duplicate: exitFullscreen line 1706
+en duplicate: fullscreenDisplay line 1707
+en duplicate: pdfPreview line 1708
+en duplicate: converting line 1709
+en duplicate: generatePDFPreview line 1710
+en duplicate: previewNotSupported line 1711
+en duplicate: confirmRegeneratePDF line 1714
+en duplicate: pdfPreviewReady line 1717
+en duplicate: convertingInProgress line 1718
+en duplicate: conversionFailed line 1719
+en duplicate: generatePDFPreviewButton line 1720
+en duplicate: checkPDFStatusFailed line 1723
+en duplicate: requestRegenerationFailed line 1724
+en duplicate: downloadPDFFailed line 1725
+en duplicate: openPDFInNewTabFailed line 1726
+en duplicate: invalidFile line 1729
+en duplicate: incompleteFileInfo line 1730
+en duplicate: unsupportedFileFormat line 1731
+en duplicate: willUseFastMode line 1732
+en duplicate: formatNoPrecise line 1733
+en duplicate: smallFileFastOk line 1734
+en duplicate: mixedContentPreciseRecommended line 1735
+en duplicate: willIncurApiCost line 1736
+en duplicate: largeFilePreciseRecommended line 1737
+en duplicate: longProcessingTime line 1738
+en duplicate: highApiCost line 1739
+en duplicate: considerFileSplitting line 1740
+en duplicate: dragDropUploadTitle line 1743
+en duplicate: dragDropUploadDesc line 1744
+en duplicate: supportedFormats line 1745
+en duplicate: browseFiles line 1746
+en duplicate: recommendationMsg line 1749
+en duplicate: autoAdjustChunk line 1750
+en duplicate: autoAdjustOverlap line 1751
+en duplicate: autoAdjustOverlapMin line 1752
+en duplicate: loadLimitsFailed line 1753
+en duplicate: maxValueMsg line 1754
+en duplicate: overlapRatioLimit line 1755
+en duplicate: onlyAdminCanModify line 1756
+en duplicate: dragToSelect line 1757
+en duplicate: fillTargetName line 1760
+en duplicate: submitFailed line 1761
+en duplicate: importFolderTitle line 1762
+en duplicate: importFolderTip line 1763
+en duplicate: lblTargetGroup line 1764
+en duplicate: placeholderNewGroup line 1765
+en duplicate: importToCurrentGroup line 1766
+en duplicate: nextStep line 1767
+en duplicate: lblImportSource line 1768
+en duplicate: serverPath line 1769
+en duplicate: localFolder line 1770
+en duplicate: selectedFilesCount line 1771
+en duplicate: clickToSelectFolder line 1772
+en duplicate: selectFolderTip line 1773
+en duplicate: importComplete line 1774
+en duplicate: importedFromLocalFolder line 1775
+en duplicate: historyTitle line 1778
+en duplicate: confirmDeleteHistory line 1779
+en duplicate: deleteHistorySuccess line 1780
+en duplicate: deleteHistoryFailed line 1781
+en duplicate: yesterday line 1782
+en duplicate: daysAgo line 1783
+en duplicate: historyMessages line 1784
+en duplicate: noHistory line 1785
+en duplicate: noHistoryDesc line 1786
+en duplicate: loadMore line 1787
+en duplicate: loadingHistoriesFailed line 1788
+en duplicate: supportedFormatsInfo line 1789

+ 42 - 6
package-lock.json

@@ -528,6 +528,7 @@
       "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@babel/code-frame": "^7.27.1",
         "@babel/generator": "^7.28.5",
@@ -2440,6 +2441,7 @@
       "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.5.tgz",
       "integrity": "sha512-m+EhnHhaCnVPJt4HRmhElBN3ZBvQGfXL/hm80UV3EHNUPNUCHi6q6de7dqrw/l4oTvmX0nC08Fm2ta1U59o1bQ==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@cfworker/json-schema": "^4.0.2",
         "ansi-styles": "^5.0.0",
@@ -2964,6 +2966,7 @@
       "resolved": "https://registry.npmmirror.com/@nestjs/common/-/common-11.1.9.tgz",
       "integrity": "sha512-zDntUTReRbAThIfSp3dQZ9kKqI+LjgLp5YZN5c1bgNRDuoeLySAoZg46Bg1a+uV8TMgIRziHocglKGNzr6l+bQ==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "file-type": "21.1.0",
         "iterare": "1.2.1",
@@ -3023,6 +3026,7 @@
       "integrity": "sha512-a00B0BM4X+9z+t3UxJqIZlemIwCQdYoPKrMcM+ky4z3pkqqG1eTWexjs+YXpGObnLnjtMPVKWlcZHp3adDYvUw==",
       "hasInstallScript": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@nuxt/opencollective": "0.4.1",
         "fast-safe-stringify": "2.1.1",
@@ -3106,6 +3110,7 @@
       "resolved": "https://registry.npmmirror.com/@nestjs/platform-express/-/platform-express-11.1.9.tgz",
       "integrity": "sha512-GVd3+0lO0mJq2m1kl9hDDnVrX3Nd4oH3oDfklz0pZEVEVS0KVSp63ufHq2Lu9cyPdSBuelJr9iPm2QQ1yX+Kmw==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "cors": "2.8.5",
         "express": "5.1.0",
@@ -3373,6 +3378,7 @@
       "resolved": "https://registry.npmmirror.com/@opentelemetry/api/-/api-1.9.0.tgz",
       "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
       "license": "Apache-2.0",
+      "peer": true,
       "engines": {
         "node": ">=8.0.0"
       }
@@ -4271,6 +4277,7 @@
       "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@types/estree": "*",
         "@types/json-schema": "*"
@@ -4453,6 +4460,7 @@
       "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.0.0.tgz",
       "integrity": "sha512-rl78HwuZlaDIUSeUKkmogkhebA+8K1Hy7tddZuJ3D0xV8pZSfsYGTsliGUol1JPzu9EKnTxPC4L1fiWouStRew==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "undici-types": "~7.16.0"
       }
@@ -4526,6 +4534,7 @@
       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz",
       "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "csstype": "^3.2.2"
       }
@@ -4684,6 +4693,7 @@
       "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@typescript-eslint/scope-manager": "8.49.0",
         "@typescript-eslint/types": "8.49.0",
@@ -5129,6 +5139,7 @@
       "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz",
       "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
       "license": "MIT",
+      "peer": true,
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -5187,6 +5198,7 @@
       "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.3",
         "fast-uri": "^3.0.1",
@@ -5652,6 +5664,7 @@
       "integrity": "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg==",
       "hasInstallScript": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "bindings": "^1.5.0",
         "prebuild-install": "^7.1.1"
@@ -5787,6 +5800,7 @@
         }
       ],
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "baseline-browser-mapping": "^2.9.0",
         "caniuse-lite": "^1.0.30001759",
@@ -6118,6 +6132,7 @@
       "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "readdirp": "^4.0.1"
       },
@@ -6171,13 +6186,15 @@
       "version": "0.5.1",
       "resolved": "https://registry.npmmirror.com/class-transformer/-/class-transformer-0.5.1.tgz",
       "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
-      "license": "MIT"
+      "license": "MIT",
+      "peer": true
     },
     "node_modules/class-validator": {
       "version": "0.14.3",
       "resolved": "https://registry.npmmirror.com/class-validator/-/class-validator-0.14.3.tgz",
       "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@types/validator": "^13.15.3",
         "libphonenumber-js": "^1.11.1",
@@ -6651,6 +6668,7 @@
       "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz",
       "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
       "license": "MIT",
+      "peer": true,
       "engines": {
         "node": ">=0.10"
       }
@@ -7072,6 +7090,7 @@
       "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
       "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
       "license": "ISC",
+      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -7688,6 +7707,7 @@
       "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.8.0",
         "@eslint-community/regexpp": "^4.12.1",
@@ -7748,6 +7768,7 @@
       "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "bin": {
         "eslint-config-prettier": "bin/cli.js"
       },
@@ -8078,6 +8099,7 @@
       "resolved": "https://registry.npmmirror.com/express/-/express-5.1.0.tgz",
       "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "accepts": "^2.0.0",
         "body-parser": "^2.2.0",
@@ -8455,6 +8477,7 @@
       "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.1",
         "fast-json-stable-stringify": "^2.0.0",
@@ -9768,6 +9791,7 @@
       "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@jest/core": "30.2.0",
         "@jest/types": "30.2.0",
@@ -12942,6 +12966,7 @@
       "resolved": "https://registry.npmmirror.com/passport/-/passport-0.7.0.tgz",
       "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "passport-strategy": "1.x.x",
         "pause": "0.0.1",
@@ -13209,6 +13234,7 @@
         }
       ],
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "nanoid": "^3.3.11",
         "picocolors": "^1.1.1",
@@ -13281,6 +13307,7 @@
       "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "bin": {
         "prettier": "bin/prettier.cjs"
       },
@@ -13485,6 +13512,7 @@
       "resolved": "https://registry.npmmirror.com/react/-/react-19.2.1.tgz",
       "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==",
       "license": "MIT",
+      "peer": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -13494,6 +13522,7 @@
       "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.1.tgz",
       "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "scheduler": "^0.27.0"
       },
@@ -13648,7 +13677,8 @@
       "version": "0.2.2",
       "resolved": "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
       "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
-      "license": "Apache-2.0"
+      "license": "Apache-2.0",
+      "peer": true
     },
     "node_modules/refractor": {
       "version": "5.0.0",
@@ -13946,6 +13976,7 @@
       "resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz",
       "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
       "license": "Apache-2.0",
+      "peer": true,
       "dependencies": {
         "tslib": "^2.1.0"
       }
@@ -14713,7 +14744,8 @@
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz",
       "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==",
-      "license": "MIT"
+      "license": "MIT",
+      "peer": true
     },
     "node_modules/tapable": {
       "version": "2.3.0",
@@ -14762,6 +14794,7 @@
       "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
       "devOptional": true,
       "license": "BSD-2-Clause",
+      "peer": true,
       "dependencies": {
         "@jridgewell/source-map": "^0.3.3",
         "acorn": "^8.15.0",
@@ -15273,7 +15306,6 @@
       "resolved": "https://registry.npmmirror.com/typeorm/-/typeorm-0.3.26.tgz",
       "integrity": "sha512-o2RrBNn3lczx1qv4j+JliVMmtkPSqEGpG0UuZkt9tCfWkoXKu8MZnjvp2GjWPll1SehwemQw6xrbVRhmOglj8Q==",
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "@sqltools/formatter": "^1.2.5",
         "ansis": "^3.17.0",
@@ -15376,7 +15408,6 @@
       "resolved": "https://registry.npmmirror.com/ansis/-/ansis-3.17.0.tgz",
       "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==",
       "license": "ISC",
-      "peer": true,
       "engines": {
         "node": ">=14"
       }
@@ -15390,7 +15421,6 @@
         "https://github.com/sponsors/ctavan"
       ],
       "license": "MIT",
-      "peer": true,
       "bin": {
         "uuid": "dist/esm/bin/uuid"
       }
@@ -15401,6 +15431,7 @@
       "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
       "devOptional": true,
       "license": "Apache-2.0",
+      "peer": true,
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -16029,6 +16060,7 @@
       "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@types/eslint-scope": "^3.7.7",
         "@types/estree": "^1.0.8",
@@ -16389,6 +16421,7 @@
       "resolved": "https://registry.npmmirror.com/zod/-/zod-4.1.13.tgz",
       "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
       "license": "MIT",
+      "peer": true,
       "funding": {
         "url": "https://github.com/sponsors/colinhacks"
       }
@@ -16479,6 +16512,7 @@
       "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==",
       "devOptional": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "undici-types": "~6.21.0"
       }
@@ -16510,6 +16544,7 @@
       "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
       "devOptional": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@cspotcode/source-map-support": "^0.8.0",
         "@tsconfig/node10": "^1.0.7",
@@ -16723,6 +16758,7 @@
       "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "undici-types": "~6.21.0"
       }

+ 12 - 0
server/check_schema.js

@@ -0,0 +1,12 @@
+const sqlite3 = require('better-sqlite3');
+const db = new sqlite3('./data/metadata.db');
+
+const tableInfo = db.prepare("PRAGMA table_info(model_configs)").all();
+console.log("Table info for model_configs:");
+console.log(JSON.stringify(tableInfo, null, 2));
+
+const sample = db.prepare("SELECT * FROM model_configs LIMIT 5").all();
+console.log("Sample data:");
+console.log(JSON.stringify(sample, null, 2));
+
+db.close();

+ 47 - 0
server/debug_es.js

@@ -0,0 +1,47 @@
+const { Client } = require('@elastic/elasticsearch');
+
+async function run() {
+    const client = new Client({
+        node: 'http://127.0.0.1:9200',
+    });
+
+    try {
+        const indexName = 'knowledge_base';
+
+        console.log(`\n--- Total Documents ---`);
+        const count = await client.count({ index: indexName });
+        console.log(count);
+
+        console.log(`\n--- Document Distribution by tenantId ---`);
+        const distribution = await client.search({
+            index: indexName,
+            size: 0,
+            aggs: {
+                by_tenant: {
+                    terms: { field: 'tenantId', size: 100, missing: 'N/A' }
+                }
+            }
+        });
+        console.log(JSON.stringify(distribution.aggregations.by_tenant.buckets, null, 2));
+
+        console.log(`\n--- Sample Documents (last 5) ---`);
+        const samples = await client.search({
+            index: indexName,
+            size: 5,
+            sort: [{ createdAt: 'desc' }],
+        });
+        console.log(JSON.stringify(samples.hits.hits.map(h => ({
+            id: h._id,
+            tenantId: h._source.tenantId,
+            fileName: h._source.fileName,
+            vectorLength: h._source.vector?.length,
+            vectorPreview: h._source.vector?.slice(0, 5),
+            contentPreview: h._source.content?.substring(0, 50)
+        })), null, 2));
+
+    } catch (error) {
+        console.error('Error:', error.meta?.body || error.message);
+    }
+}
+
+run();

BIN
server/es_results.txt


+ 0 - 0
server/metadata.db


BIN
server/schema_output.txt


+ 8 - 1
server/src/import-task/import-task.controller.ts

@@ -1,4 +1,4 @@
-import { Controller, Post, Get, Body, Request, UseGuards } from '@nestjs/common';
+import { Controller, Post, Get, Delete, Param, Body, Request, UseGuards } from '@nestjs/common';
 import { ImportTaskService } from './import-task.service';
 import { CombinedAuthGuard } from '../auth/combined-auth.guard';
 import { RolesGuard } from '../auth/roles.guard';
@@ -22,7 +22,9 @@ export class ImportTaskController {
             chunkSize: body.chunkSize,
             chunkOverlap: body.chunkOverlap,
             mode: body.mode,
+            useHierarchy: body.useHierarchy ?? false,
             userId: req.user.id,
+            tenantId: req.user.tenantId,
         });
     }
 
@@ -30,4 +32,9 @@ export class ImportTaskController {
     async findAll(@Request() req) {
         return this.taskService.findAll(req.user.id);
     }
+
+    @Delete(':id')
+    async delete(@Param('id') id: string, @Request() req) {
+        return this.taskService.delete(id, req.user.id);
+    }
 }

+ 4 - 0
server/src/import-task/import-task.entity.ts

@@ -41,6 +41,10 @@ export class ImportTask {
     @Column({ nullable: true, default: 'fast' })
     mode: string;
 
+    /** When true, sub-directories become sub-categories mirroring the folder hierarchy */
+    @Column({ default: false })
+    useHierarchy: boolean;
+
     @CreateDateColumn()
     createdAt: Date;
 

+ 171 - 89
server/src/import-task/import-task.service.ts

@@ -42,12 +42,19 @@ export class ImportTaskService {
         });
     }
 
+    async delete(taskId: string, userId: string): Promise<void> {
+        const task = await this.taskRepository.findOne({ where: { id: taskId, userId } });
+        if (!task) {
+            throw new Error(`Task ${taskId} not found or you don't have permission to delete it.`);
+        }
+        await this.taskRepository.remove(task);
+    }
+
     @Cron(CronExpression.EVERY_MINUTE)
     async handleScheduledTasks() {
         this.logger.debug('Checking for scheduled import tasks...');
         const now = new Date();
 
-        // Find pending tasks that are due
         const tasks = await this.taskRepository.find({
             where: {
                 status: 'PENDING',
@@ -57,7 +64,6 @@ export class ImportTaskService {
 
         for (const task of tasks) {
             this.logger.log(`Starting scheduled task ${task.id}`);
-            // Execute without awaiting to allow parallel execution of multiple tasks
             this.executeTask(task.id).catch(err =>
                 this.logger.error(`Scheduled execution failed to start for task ${task.id}`, err)
             );
@@ -78,111 +84,106 @@ export class ImportTaskService {
         }
 
         await this.updateStatus(taskId, 'PROCESSING', 'Starting import...');
-        this.logger.debug(`Task ${taskId} status updated to PROCESSING.`);
 
         try {
             if (!fs.existsSync(task.sourcePath)) {
                 throw new Error(`Directory not found: ${task.sourcePath}`);
             }
 
-            // 1. Prepare Target Group
-            this.logger.debug(`Task ${taskId}: Preparing target group.`);
-            let groupId = task.targetGroupId;
-            if (!groupId && task.targetGroupName) {
-                // Create new group
-                const group = await this.groupService.create(task.userId, task.tenantId || 'default', {
-                    name: task.targetGroupName,
-                    description: `Imported from ${task.sourcePath}`,
-                    color: '#0078D4', // Default blue
-                });
-                groupId = group.id;
-                await this.appendLog(taskId, `Created new group: ${task.targetGroupName}`);
-                this.logger.debug(`Task ${taskId}: Created new group ${group.id} - ${task.targetGroupName}`);
-            } else if (!groupId) {
-                throw new Error('No target group specified');
-            }
-            this.logger.debug(`Task ${taskId}: Target group ID is ${groupId}.`);
-
-            // 2. Scan Files
-            await this.appendLog(taskId, `Scanning directory: ${task.sourcePath}`);
-            this.logger.debug(`Task ${taskId}: Scanning directory ${task.sourcePath}.`);
-            const filesToImport = this.scanDir(task.sourcePath);
-            await this.appendLog(taskId, `Found ${filesToImport.length} markdown/text files.`);
-            this.logger.debug(`Task ${taskId}: Found ${filesToImport.length} files to import.`);
-
-            // 3. Import Loop
             const uploadPath = this.configService.get<string>('UPLOAD_FILE_PATH', './uploads');
             const importTargetDir = path.join(uploadPath, 'imported', taskId);
 
             if (!fs.existsSync(importTargetDir)) {
                 fs.mkdirSync(importTargetDir, { recursive: true });
             }
-            this.logger.debug(`Task ${taskId}: Created import target directory ${importTargetDir}.`);
 
             let successCount = 0;
             let failCount = 0;
 
-            for (let i = 0; i < filesToImport.length; i++) {
-                const filePath = filesToImport[i];
-                try {
-                    const filename = path.basename(filePath);
-                    // Directory as prefix logic
-                    const dirName = path.basename(path.dirname(filePath));
-                    // Avoid prefix if direct child of source path? No, keep it simple: always prefix with parent folder name
-                    // unless it's the source folder itself? 
-                    // Current plan: "ParentDir - Filename"
-                    // Let's refine: If file is D:/a/b/c.md, and source is D:/a, then dir is b. Title: "b - c.md"
-
-                    let title = filename;
+            if (task.useHierarchy) {
+                // ---- Hierarchy mode: create sub-groups matching folder structure ----
+                await this.appendLog(taskId, `Scanning directory with hierarchy: ${task.sourcePath}`);
+
+                // Determine root group
+                let rootGroupId = task.targetGroupId;
+                if (!rootGroupId) {
+                    const rootName = task.targetGroupName || path.basename(task.sourcePath);
+                    const rootGroup = await this.groupService.create(task.userId, task.tenantId || 'default', {
+                        name: rootName,
+                        description: `Imported from ${task.sourcePath}`,
+                        color: '#0078D4',
+                    });
+                    rootGroupId = rootGroup.id;
+                    await this.appendLog(taskId, `Created root group: ${rootName}`);
+                }
+
+                // Map from relative dir path -> groupId
+                const dirToGroupId = new Map<string, string>();
+                dirToGroupId.set('.', rootGroupId);
+
+                // Collect all files first
+                const allFiles = this.scanDir(task.sourcePath);
+                await this.appendLog(taskId, `Found ${allFiles.length} files.`);
+
+                for (let i = 0; i < allFiles.length; i++) {
+                    const filePath = allFiles[i];
                     const relativeDir = path.relative(task.sourcePath, path.dirname(filePath));
-                    if (relativeDir && relativeDir !== '.') {
-                        const prefix = relativeDir.replace(/[\\\/]/g, ' - ');
-                        title = `${prefix} - ${filename}`;
+                    const normalizedDir = relativeDir || '.';
+
+                    // Ensure group exists for this directory
+                    const groupId = await this.ensureHierarchyGroup(
+                        task.userId,
+                        task.tenantId || 'default',
+                        normalizedDir,
+                        dirToGroupId,
+                        task.sourcePath,
+                        taskId,
+                    );
+
+                    try {
+                        const kb = await this.importSingleFile(filePath, task, importTargetDir, i, allFiles.length);
+                        await this.groupService.addFilesToGroup(kb.id, [groupId], task.userId, task.tenantId || 'default');
+                        successCount++;
+                        if (successCount % 10 === 0) {
+                            await this.appendLog(taskId, `Imported ${successCount} files...`);
+                        }
+                    } catch (e) {
+                        failCount++;
+                        await this.appendLog(taskId, `Failed to import ${path.basename(filePath)}: ${e.message}`);
                     }
+                }
+            } else {
+                // ---- Single-group mode (original behavior) ----
+                let groupId = task.targetGroupId;
+                if (!groupId && task.targetGroupName) {
+                    const group = await this.groupService.create(task.userId, task.tenantId || 'default', {
+                        name: task.targetGroupName,
+                        description: `Imported from ${task.sourcePath}`,
+                        color: '#0078D4',
+                    });
+                    groupId = group.id;
+                    await this.appendLog(taskId, `Created new group: ${task.targetGroupName}`);
+                } else if (!groupId) {
+                    throw new Error('No target group specified');
+                }
 
-                    const storedFilename = `imported-${Date.now()}-${Math.random().toString(36).substr(2, 5)}-${filename}`;
-                    const targetPath = path.join(importTargetDir, storedFilename);
-
-                    // Copy file to safe location
-                    fs.copyFileSync(filePath, targetPath);
-
-                    const stats = fs.statSync(targetPath);
-                    const fileInfo = {
-                        filename: storedFilename,
-                        originalname: title,
-                        path: targetPath, // Use the safe copy path
-                        mimetype: 'text/markdown', // Assume markdown/text for now
-                        size: stats.size,
-                    };
-
-                    const indexingConfig = {
-                        chunkSize: task.chunkSize || 500,
-                        chunkOverlap: task.chunkOverlap || 50,
-                        embeddingModelId: task.embeddingModelId,
-                        mode: (task.mode || 'fast') as 'fast' | 'precise',
-                    };
-
-                    // Ingest sequentially
-                    this.logger.log(`Processing file ${i + 1}/${filesToImport.length}: ${fileInfo.originalname}`);
-                    const kb = await this.kbService.createAndIndex(fileInfo, task.userId, task.tenantId || 'default', {
-                        ...indexingConfig,
-                        waitForCompletion: true // Ensure sequential processing
-                    } as any);
-                    this.logger.log(`File ${i + 1}/${filesToImport.length} processing completed`);
-
-                    // Link to Group
-                    await this.groupService.addFilesToGroup(kb.id, [groupId], task.userId, task.tenantId || 'default');
-                    this.logger.debug(`Task ${taskId}: Linked KB ${kb.id} to group ${groupId}.`);
-
-                    successCount++;
-                    // Optional: log every single file? Might be too verbose for large courses.
-                    if (successCount % 10 === 0) {
-                        await this.appendLog(taskId, `Imported ${successCount} files...`);
+                await this.appendLog(taskId, `Scanning directory: ${task.sourcePath}`);
+                const filesToImport = this.scanDir(task.sourcePath);
+                await this.appendLog(taskId, `Found ${filesToImport.length} files.`);
+
+                for (let i = 0; i < filesToImport.length; i++) {
+                    const filePath = filesToImport[i];
+                    try {
+                        const kb = await this.importSingleFile(filePath, task, importTargetDir, i, filesToImport.length);
+                        await this.groupService.addFilesToGroup(kb.id, [groupId], task.userId, task.tenantId || 'default');
+                        successCount++;
+                        if (successCount % 10 === 0) {
+                            await this.appendLog(taskId, `Imported ${successCount} files...`);
+                        }
+                    } catch (e) {
+                        failCount++;
+                        await this.appendLog(taskId, `Failed to import ${path.basename(filePath)}: ${e.message}`);
                     }
-
-                } catch (e) {
-                    failCount++;
-                    await this.appendLog(taskId, `Failed to import ${path.basename(filePath)}: ${e.message}`);
                 }
             }
 
@@ -193,6 +194,88 @@ export class ImportTaskService {
         }
     }
 
+    /**
+     * Ensure a KnowledgeGroup exists for each segment of the relative directory path.
+     * Returns the groupId for the leaf directory.
+     */
+    private async ensureHierarchyGroup(
+        userId: string,
+        tenantId: string,
+        relativeDir: string,
+        dirToGroupId: Map<string, string>,
+        _sourcePath: string,
+        taskId: string,
+    ): Promise<string> {
+        if (dirToGroupId.has(relativeDir)) {
+            return dirToGroupId.get(relativeDir)!;
+        }
+
+        const segments = relativeDir.split(path.sep);
+        let currentPath = '';
+        let parentGroupId = dirToGroupId.get('.') ?? dirToGroupId.values().next().value;
+
+        for (const segment of segments) {
+            currentPath = currentPath ? path.join(currentPath, segment) : segment;
+            if (dirToGroupId.has(currentPath)) {
+                parentGroupId = dirToGroupId.get(currentPath)!;
+                continue;
+            }
+
+            // Create a group for this directory segment
+            const group = await this.groupService.findOrCreate(
+                userId,
+                tenantId,
+                segment,
+                parentGroupId,
+                `Sub-folder: ${currentPath}`,
+            );
+            dirToGroupId.set(currentPath, group.id);
+            await this.appendLog(taskId, `Created sub-group: ${segment}`);
+            parentGroupId = group.id;
+        }
+
+        return parentGroupId;
+    }
+
+    /** Copy file to safe location and index it */
+    private async importSingleFile(
+        filePath: string,
+        task: ImportTask,
+        importTargetDir: string,
+        index: number,
+        total: number,
+    ) {
+        const filename = path.basename(filePath);
+        const storedFilename = `imported-${Date.now()}-${Math.random().toString(36).substr(2, 5)}-${filename}`;
+        const targetPath = path.join(importTargetDir, storedFilename);
+
+        fs.copyFileSync(filePath, targetPath);
+        const stats = fs.statSync(targetPath);
+
+        const fileInfo = {
+            filename: storedFilename,
+            originalname: filename,
+            path: targetPath,
+            mimetype: 'text/markdown',
+            size: stats.size,
+        };
+
+        const indexingConfig = {
+            chunkSize: task.chunkSize || 500,
+            chunkOverlap: task.chunkOverlap || 50,
+            embeddingModelId: task.embeddingModelId,
+            mode: (task.mode || 'fast') as 'fast' | 'precise',
+        };
+
+        this.logger.log(`Processing file ${index + 1}/${total}: ${filename}`);
+        const kb = await this.kbService.createAndIndex(fileInfo, task.userId, task.tenantId || 'default', {
+            ...indexingConfig,
+            waitForCompletion: true,
+        } as any);
+        this.logger.log(`File ${index + 1}/${total} processing completed: ${filename}`);
+        return kb;
+    }
+
     private scanDir(directory: string): string[] {
         let results: string[] = [];
         const items = fs.readdirSync(directory);
@@ -202,8 +285,7 @@ export class ImportTaskService {
             if (stat.isDirectory()) {
                 results = results.concat(this.scanDir(fullPath));
             } else {
-                // Filter for text files and PDFs
-                if (item.match(/\.(md|txt|html|json|pdf)$/i)) {
+                if (item.match(/\.(md|txt|html|json|pdf|docx|xlsx|pptx|csv)$/i)) {
                     results.push(fullPath);
                 }
             }

+ 3 - 0
server/src/knowledge-base/knowledge-base.service.ts

@@ -918,6 +918,7 @@ export class KnowledgeBaseService {
                       originalName: kb.originalName,
                       mimetype: kb.mimetype,
                       userId: userId,
+                      tenantId, // Added tenantId
                       chunkIndex: chunk.index,
                       startPosition: chunk.startPosition,
                       endPosition: chunk.endPosition,
@@ -968,6 +969,7 @@ export class KnowledgeBaseService {
                   originalName: kb.originalName,
                   mimetype: kb.mimetype,
                   userId: userId,
+                  tenantId, // Added tenantId
                   chunkIndex: chunk.index,
                   startPosition: chunk.startPosition,
                   endPosition: chunk.endPosition,
@@ -1004,6 +1006,7 @@ export class KnowledgeBaseService {
                       originalName: kb.originalName,
                       mimetype: kb.mimetype,
                       userId: userId,
+                      tenantId, // Added tenantId
                       chunkIndex: chunk.index,
                       startPosition: chunk.startPosition,
                       endPosition: chunk.endPosition,

+ 2 - 7
server/src/knowledge-group/knowledge-group.controller.ts

@@ -22,20 +22,18 @@ export class KnowledgeGroupController {
 
   @Get()
   async findAll(@Request() req) {
-    // All users can see all groups for their tenant
+    // All users can see all groups for their tenant (returns tree structure)
     return await this.groupService.findAll(req.user.id, req.user.tenantId);
   }
 
   @Get(':id')
   async findOne(@Param('id') id: string, @Request() req) {
-    // Access group within tenant
     return await this.groupService.findOne(id, req.user.id, req.user.tenantId);
   }
 
   @Post()
   @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
   async create(@Body() createGroupDto: CreateGroupDto, @Request() req) {
-    // Only admin can create groups (implicitly scoped to their tenant)
     return await this.groupService.create(req.user.id, req.user.tenantId, createGroupDto);
   }
 
@@ -46,21 +44,18 @@ export class KnowledgeGroupController {
     @Body() updateGroupDto: UpdateGroupDto,
     @Request() req,
   ) {
-    // Only admin can update any group within tenant
     return await this.groupService.update(id, req.user.id, req.user.tenantId, updateGroupDto);
   }
 
   @Delete(':id')
   @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
   async remove(@Param('id') id: string, @Request() req) {
-    // Only admin can delete groups
     await this.groupService.remove(id, req.user.id, req.user.tenantId);
-    return { message: '分组删除成功' };
+    return { message: 'Group deleted successfully' };
   }
 
   @Get(':id/files')
   async getGroupFiles(@Param('id') id: string, @Request() req) {
-    // Any user can see files in any group within tenant
     const files = await this.groupService.getGroupFiles(id, req.user.id, req.user.tenantId);
     return { files };
   }

+ 12 - 0
server/src/knowledge-group/knowledge-group.entity.ts

@@ -7,6 +7,7 @@ import {
   ManyToMany,
   JoinTable,
   ManyToOne,
+  OneToMany,
   JoinColumn,
 } from 'typeorm';
 import { KnowledgeBase } from '../knowledge-base/knowledge-base.entity';
@@ -35,6 +36,17 @@ export class KnowledgeGroup {
   @JoinColumn({ name: 'tenant_id' })
   tenant: Tenant;
 
+  // Hierarchical parent-child relationship
+  @Column({ name: 'parent_id', nullable: true, type: 'text' })
+  parentId: string | null;
+
+  @ManyToOne(() => KnowledgeGroup, (group) => group.children, { nullable: true, onDelete: 'SET NULL' })
+  @JoinColumn({ name: 'parent_id' })
+  parent: KnowledgeGroup;
+
+  @OneToMany(() => KnowledgeGroup, (group) => group.parent)
+  children: KnowledgeGroup[];
+
   @CreateDateColumn({ name: 'created_at' })
   createdAt: Date;
 

+ 72 - 26
server/src/knowledge-group/knowledge-group.service.ts

@@ -1,7 +1,7 @@
 import { Injectable, NotFoundException, Inject, forwardRef } from '@nestjs/common';
 import { I18nService } from '../i18n/i18n.service';
 import { InjectRepository } from '@nestjs/typeorm';
-import { Repository } from 'typeorm';
+import { Repository, In } from 'typeorm';
 import { KnowledgeGroup } from './knowledge-group.entity';
 import { KnowledgeBase } from '../knowledge-base/knowledge-base.entity';
 import { KnowledgeBaseService } from '../knowledge-base/knowledge-base.service';
@@ -10,12 +10,14 @@ export interface CreateGroupDto {
   name: string;
   description?: string;
   color?: string;
+  parentId?: string | null;
 }
 
 export interface UpdateGroupDto {
   name?: string;
   description?: string;
   color?: string;
+  parentId?: string | null;
 }
 
 export interface GroupWithFileCount {
@@ -24,6 +26,8 @@ export interface GroupWithFileCount {
   description?: string;
   color: string;
   fileCount: number;
+  parentId?: string | null;
+  children?: GroupWithFileCount[];
   createdAt: Date;
 }
 
@@ -40,24 +44,45 @@ export class KnowledgeGroupService {
   ) { }
 
   async findAll(userId: string, tenantId: string): Promise<GroupWithFileCount[]> {
-    // Return all groups for the tenant
+    // Return all groups for the tenant with file counts
     const groups = await this.groupRepository
       .createQueryBuilder('group')
       .leftJoin('group.knowledgeBases', 'kb')
       .where('group.tenantId = :tenantId', { tenantId })
       .addSelect('COUNT(kb.id)', 'fileCount')
       .groupBy('group.id')
-      .orderBy('group.createdAt', 'DESC')
+      .orderBy('group.createdAt', 'ASC')
       .getRawAndEntities();
 
-    return groups.entities.map((group, index) => ({
+    const flatList: GroupWithFileCount[] = groups.entities.map((group, index) => ({
       id: group.id,
       name: group.name,
       description: group.description,
       color: group.color,
+      parentId: group.parentId ?? null,
       fileCount: parseInt(groups.raw[index].fileCount) || 0,
       createdAt: group.createdAt,
+      children: [],
     }));
+
+    // Build tree structure
+    return this.buildTree(flatList);
+  }
+
+  /** Build a nested tree from a flat list */
+  private buildTree(items: GroupWithFileCount[]): GroupWithFileCount[] {
+    const map = new Map<string, GroupWithFileCount>();
+    items.forEach(item => map.set(item.id, { ...item, children: [] }));
+
+    const roots: GroupWithFileCount[] = [];
+    map.forEach(item => {
+      if (item.parentId && map.has(item.parentId)) {
+        map.get(item.parentId)!.children!.push(item);
+      } else {
+        roots.push(item);
+      }
+    });
+    return roots;
   }
 
   async findOne(id: string, userId: string, tenantId: string): Promise<KnowledgeGroup> {
@@ -77,6 +102,7 @@ export class KnowledgeGroupService {
   async create(userId: string, tenantId: string, createGroupDto: CreateGroupDto): Promise<KnowledgeGroup> {
     const group = this.groupRepository.create({
       ...createGroupDto,
+      parentId: createGroupDto.parentId ?? null,
       tenantId,
     });
 
@@ -98,7 +124,6 @@ export class KnowledgeGroupService {
   }
 
   async remove(id: string, userId: string, tenantId: string): Promise<void> {
-    // Remove group within the tenant
     const group = await this.groupRepository.findOne({
       where: { id, tenantId },
     });
@@ -107,7 +132,21 @@ export class KnowledgeGroupService {
       throw new NotFoundException(this.i18nService.getMessage('groupNotFound'));
     }
 
-    // Find all files associated with this group (without user restriction)
+    // Recursively delete this group and all its descendants
+    await this.removeGroupRecursive(id, userId, tenantId);
+  }
+
+  /** Recursively delete a group, all its children, and all associated files */
+  private async removeGroupRecursive(id: string, userId: string, tenantId: string): Promise<void> {
+    // 1. Find all direct children of this group
+    const children = await this.groupRepository.find({ where: { parentId: id, tenantId } });
+
+    // 2. Recurse into each child first (depth-first)
+    for (const child of children) {
+      await this.removeGroupRecursive(child.id, userId, tenantId);
+    }
+
+    // 3. Delete all files belonging to this group
     const files = await this.knowledgeBaseRepository
       .createQueryBuilder('kb')
       .innerJoin('kb.groups', 'group')
@@ -115,15 +154,12 @@ export class KnowledgeGroupService {
       .select('kb.id')
       .getMany();
 
-    // Delete each file
     for (const file of files) {
       try {
-        // We need to get the file's owner to delete it properly
         const fullFile = await this.knowledgeBaseRepository.findOne({
           where: { id: file.id },
-          select: ['id', 'userId', 'tenantId']  // Get the owner of the file
+          select: ['id', 'userId', 'tenantId'],
         });
-
         if (fullFile) {
           await this.knowledgeBaseService.deleteFile(fullFile.id, fullFile.userId, fullFile.tenantId as string);
         }
@@ -132,20 +168,11 @@ export class KnowledgeGroupService {
       }
     }
 
-    // Delete notes in this group - call the findAll method with groupId parameter only
-    // We'll fetch notes for the group without userId restriction
-    // For this, we'll call the note service differently
-
-    // Actually, we need to think about this carefully
-    // Notes belong to users, so we can't remove notes from other users' groups
-    // We'll just remove the group from the association
-    // Or fetch notes by groupId only (need to modify note service)
-
-    // Since note service is user-restricted, let's only handle file removal
-    // and leave note management to users individually
-
-    // Delete the group itself
-    await this.groupRepository.remove(group);
+    // 4. Delete the group itself
+    const group = await this.groupRepository.findOne({ where: { id } });
+    if (group) {
+      await this.groupRepository.remove(group);
+    }
   }
 
   async getGroupFiles(groupId: string, userId: string, tenantId: string): Promise<KnowledgeBase[]> {
@@ -172,10 +199,10 @@ export class KnowledgeGroupService {
     }
 
     // Load all groups by ID without user restriction
-    const groups = await this.groupRepository.findByIds(groupIds);
+    const groups = await this.groupRepository.findBy({ id: In(groupIds) });
     const validGroups = groups.filter(g => g.tenantId === tenantId);
 
-    if (validGroups.length !== groupIds.length) {
+    if (groupIds.length > 0 && validGroups.length !== groupIds.length) {
       throw new NotFoundException(this.i18nService.getMessage('someGroupsNotFound'));
     }
 
@@ -212,4 +239,23 @@ export class KnowledgeGroupService {
 
     return result.map(row => row.id);
   }
+
+  /**
+   * Find or create a group by name and parentId within a tenant.
+   * Used by import tasks to build folder hierarchy.
+   */
+  async findOrCreate(
+    userId: string,
+    tenantId: string,
+    name: string,
+    parentId: string | null,
+    description?: string,
+  ): Promise<KnowledgeGroup> {
+    const existing = await this.groupRepository.findOne({
+      where: { name, tenantId, parentId: parentId ?? undefined },
+    });
+    if (existing) return existing;
+
+    return this.create(userId, tenantId, { name, description, parentId });
+  }
 }

+ 18 - 0
server/src/migrations/1772340000000-AddParentIdToKnowledgeGroups.ts

@@ -0,0 +1,18 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddParentIdToKnowledgeGroups1772340000000 implements MigrationInterface {
+    name = 'AddParentIdToKnowledgeGroups1772340000000';
+
+    public async up(queryRunner: QueryRunner): Promise<void> {
+        // Add parent_id column to knowledge_groups table
+        await queryRunner.query(
+            `ALTER TABLE "knowledge_groups" ADD COLUMN "parent_id" text REFERENCES "knowledge_groups"("id") ON DELETE SET NULL`
+        );
+    }
+
+    public async down(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(
+            `ALTER TABLE "knowledge_groups" DROP COLUMN "parent_id"`
+        );
+    }
+}

+ 80 - 0
sync_translations.js

@@ -0,0 +1,80 @@
+
+const fs = require('fs');
+const path = require('path');
+
+const translationsPath = path.join('d:', 'workspace', 'AuraK', 'web', 'utils', 'translations.ts');
+const usedKeysPath = path.join('d:', 'workspace', 'AuraK', 'all_used_keys.txt');
+
+const usedKeys = fs.readFileSync(usedKeysPath, 'utf8').split('\n').filter(Boolean);
+const translationsContent = fs.readFileSync(translationsPath, 'utf8');
+
+const lines = translationsContent.split('\n');
+let currentLang = null;
+let resultLines = [];
+let keysSeen = new Set();
+
+const langStartRegex = /^\s+(\w+): \{/;
+const keyRegex = /^(\s+)([a-zA-Z0-9_-]+):(.*)/;
+
+function isValidIdentifier(id) {
+    return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(id);
+}
+
+for (let i = 0; i < lines.length; i++) {
+    const line = lines[i];
+
+    const langMatch = line.match(langStartRegex);
+    if (langMatch) {
+        if (currentLang) {
+            addMissingUsedKeys(currentLang, resultLines, keysSeen);
+        }
+        currentLang = langMatch[1];
+        keysSeen = new Set();
+        resultLines.push(line);
+        continue;
+    }
+
+    if (currentLang) {
+        const keyMatch = line.match(keyRegex);
+        if (keyMatch) {
+            const indent = keyMatch[1];
+            let key = keyMatch[2];
+            const rest = keyMatch[3];
+
+            // Note: keyMatch[2] might already be quoted if it was fixed in a previous run
+            const actualKey = (key.startsWith('"') && key.endsWith('"')) || (key.startsWith("'") && key.endsWith("'"))
+                ? key.slice(1, -1)
+                : key;
+
+            keysSeen.add(actualKey);
+
+            if (!isValidIdentifier(actualKey)) {
+                resultLines.push(`${indent}"${actualKey}":${rest}`);
+                continue;
+            }
+        }
+
+        if (line.trim() === '},' || (line.trim() === '}' && i > lines.length - 5)) {
+            addMissingUsedKeys(currentLang, resultLines, keysSeen);
+            currentLang = null;
+            resultLines.push(line);
+            continue;
+        }
+    }
+
+    resultLines.push(line);
+}
+
+function addMissingUsedKeys(lang, targetLines, seen) {
+    for (const key of usedKeys) {
+        if (!seen.has(key)) {
+            const val = key;
+            const quotedKey = isValidIdentifier(key) ? key : `"${key}"`;
+            targetLines.push(`    ${quotedKey}: ${JSON.stringify(val)},`);
+            seen.add(key);
+        }
+    }
+}
+
+fs.writeFileSync(translationsPath, resultLines.join('\n'), 'utf8');
+console.log('Final sync (with proper identifier quoting) complete!');

+ 2090 - 0
tmp_duplicates.txt

@@ -0,0 +1,2090 @@
+zh Duplicate key: embeddingModel (line 659)
+zh Duplicate key: chunkSize (line 681)
+zh Duplicate key: chunkOverlap (line 682)
+zh Duplicate key: rerankSimilarityThreshold (line 685)
+zh Duplicate key: importFolder (line 700)
+zh Duplicate key: files (line 705)
+zh Duplicate key: noteTitlePlaceholder (line 775)
+zh Duplicate key: uncategorized (line 782)
+zh Duplicate key: aiCommandsError (line 791)
+zh Duplicate key: appTitle (line 792)
+zh Duplicate key: loginTitle (line 793)
+zh Duplicate key: loginDesc (line 794)
+zh Duplicate key: loginButton (line 795)
+zh Duplicate key: loginError (line 796)
+zh Duplicate key: unknownError (line 797)
+zh Duplicate key: usernamePlaceholder (line 798)
+zh Duplicate key: passwordPlaceholder (line 799)
+zh Duplicate key: registerButton (line 800)
+zh Duplicate key: confirm (line 801)
+zh Duplicate key: cancel (line 802)
+zh Duplicate key: confirmTitle (line 803)
+zh Duplicate key: confirmDeleteGroup (line 804)
+zh Duplicate key: systemConfiguration (line 806)
+zh Duplicate key: noFiles (line 807)
+zh Duplicate key: noFilesDesc (line 808)
+zh Duplicate key: addFile (line 809)
+zh Duplicate key: clearAll (line 810)
+zh Duplicate key: uploading (line 811)
+zh Duplicate key: editNotebookTitle (line 812)
+zh Duplicate key: noteCreatedSuccess (line 813)
+zh Duplicate key: noteCreatedFailed (line 814)
+zh Duplicate key: errorRenderFlowchart (line 815)
+zh Duplicate key: errorLoadData (line 816)
+zh Duplicate key: confirmUnsupportedFile (line 817)
+zh Duplicate key: errorReadFile (line 818)
+zh Duplicate key: successUploadFile (line 819)
+zh Duplicate key: errorUploadFile (line 820)
+zh Duplicate key: fileAddedToGroup (line 821)
+zh Duplicate key: failedToAddToGroup (line 822)
+zh Duplicate key: fileRemovedFromGroup (line 823)
+zh Duplicate key: failedToRemoveFromGroup (line 824)
+zh Duplicate key: errorProcessFile (line 825)
+zh Duplicate key: errorTitleContentRequired (line 826)
+zh Duplicate key: successNoteUpdated (line 827)
+zh Duplicate key: successNoteCreated (line 828)
+zh Duplicate key: errorSaveFailed (line 829)
+zh Duplicate key: confirmDeleteNote (line 830)
+zh Duplicate key: successNoteDeleted (line 831)
+zh Duplicate key: confirmRemoveFileFromGroup (line 832)
+zh Duplicate key: togglePreviewOpen (line 833)
+zh Duplicate key: togglePreviewClose (line 834)
+zh Duplicate key: noteTitlePlaceholder (line 835)
+zh Duplicate key: noteContentPlaceholder (line 836)
+zh Duplicate key: markdownPreviewArea (line 837)
+zh Duplicate key: back (line 838)
+zh Duplicate key: chatWithGroup (line 839)
+zh Duplicate key: chatWithFile (line 840)
+zh Duplicate key: filesCountLabel (line 841)
+zh Duplicate key: notesCountLabel (line 842)
+zh Duplicate key: indexIntoKB (line 843)
+zh Duplicate key: noFilesOrNotes (line 844)
+zh Duplicate key: sidebarTitle (line 846)
+zh Duplicate key: backToWorkspace (line 847)
+zh Duplicate key: goToAdmin (line 848)
+zh Duplicate key: sidebarDesc (line 849)
+zh Duplicate key: tabFiles (line 850)
+zh Duplicate key: files (line 851)
+zh Duplicate key: notes (line 852)
+zh Duplicate key: tabSettings (line 853)
+zh Duplicate key: langZh (line 854)
+zh Duplicate key: langEn (line 855)
+zh Duplicate key: langJa (line 856)
+zh Duplicate key: navGlobal (line 857)
+zh Duplicate key: navTenants (line 858)
+zh Duplicate key: navSystemModels (line 859)
+zh Duplicate key: navTenantManagement (line 860)
+zh Duplicate key: navUsersTeam (line 861)
+zh Duplicate key: navTenantSettings (line 862)
+zh Duplicate key: adminConsole (line 863)
+zh Duplicate key: globalDashboard (line 864)
+zh Duplicate key: statusIndexing (line 865)
+zh Duplicate key: statusReady (line 866)
+zh Duplicate key: ragSettings (line 869)
+zh Duplicate key: enableRerank (line 870)
+zh Duplicate key: enableRerankDesc (line 871)
+zh Duplicate key: selectRerankModel (line 872)
+zh Duplicate key: selectModelPlaceholder (line 873)
+zh Duplicate key: headerModelSelection (line 875)
+zh Duplicate key: headerHyperparams (line 876)
+zh Duplicate key: headerIndexing (line 877)
+zh Duplicate key: headerRetrieval (line 878)
+zh Duplicate key: btnManageModels (line 879)
+zh Duplicate key: lblLLM (line 881)
+zh Duplicate key: lblEmbedding (line 882)
+zh Duplicate key: lblRerankRef (line 883)
+zh Duplicate key: lblTemperature (line 884)
+zh Duplicate key: lblMaxTokens (line 885)
+zh Duplicate key: lblChunkSize (line 886)
+zh Duplicate key: lblChunkOverlap (line 887)
+zh Duplicate key: lblTopK (line 888)
+zh Duplicate key: lblRerank (line 889)
+zh Duplicate key: idxModalTitle (line 891)
+zh Duplicate key: idxDesc (line 892)
+zh Duplicate key: idxFiles (line 893)
+zh Duplicate key: idxMethod (line 894)
+zh Duplicate key: idxEmbeddingModel (line 895)
+zh Duplicate key: idxStart (line 896)
+zh Duplicate key: idxCancel (line 897)
+zh Duplicate key: idxAuto (line 898)
+zh Duplicate key: idxCustom (line 899)
+zh Duplicate key: mmTitle (line 901)
+zh Duplicate key: mmAddBtn (line 902)
+zh Duplicate key: mmEdit (line 903)
+zh Duplicate key: mmDelete (line 904)
+zh Duplicate key: mmEmpty (line 905)
+zh Duplicate key: mmFormName (line 906)
+zh Duplicate key: mmFormProvider (line 907)
+zh Duplicate key: mmFormModelId (line 908)
+zh Duplicate key: mmFormBaseUrl (line 909)
+zh Duplicate key: mmFormType (line 910)
+zh Duplicate key: mmFormVision (line 911)
+zh Duplicate key: mmFormDimensions (line 912)
+zh Duplicate key: mmFormDimensionsHelp (line 913)
+zh Duplicate key: mmSave (line 914)
+zh Duplicate key: mmCancel (line 915)
+zh Duplicate key: mmErrorNotAuthenticated (line 916)
+zh Duplicate key: mmErrorTitle (line 917)
+zh Duplicate key: modelEnabled (line 918)
+zh Duplicate key: modelDisabled (line 919)
+zh Duplicate key: confirmChangeEmbeddingModel (line 920)
+zh Duplicate key: embeddingModelWarning (line 921)
+zh Duplicate key: sourcePreview (line 922)
+zh Duplicate key: matchScore (line 923)
+zh Duplicate key: copyContent (line 924)
+zh Duplicate key: copySuccess (line 925)
+zh Duplicate key: selectLLMModel (line 928)
+zh Duplicate key: selectEmbeddingModel (line 929)
+zh Duplicate key: defaultForUploads (line 930)
+zh Duplicate key: noRerankModel (line 931)
+zh Duplicate key: vectorSimilarityThreshold (line 932)
+zh Duplicate key: rerankSimilarityThreshold (line 933)
+zh Duplicate key: filterLowResults (line 934)
+zh Duplicate key: fullTextSearch (line 935)
+zh Duplicate key: hybridVectorWeight (line 936)
+zh Duplicate key: hybridVectorWeightDesc (line 937)
+zh Duplicate key: lblQueryExpansion (line 938)
+zh Duplicate key: lblHyDE (line 939)
+zh Duplicate key: lblQueryExpansionDesc (line 940)
+zh Duplicate key: lblHyDEDesc (line 941)
+zh Duplicate key: apiKeyValidationFailed (line 943)
+zh Duplicate key: keepOriginalKey (line 944)
+zh Duplicate key: leaveEmptyNoChange (line 945)
+zh Duplicate key: mmFormApiKey (line 946)
+zh Duplicate key: mmFormApiKeyPlaceholder (line 947)
+zh Duplicate key: reconfigureFile (line 950)
+zh Duplicate key: modifySettings (line 951)
+zh Duplicate key: filesCount (line 952)
+zh Duplicate key: allFilesIndexed (line 953)
+zh Duplicate key: noEmbeddingModels (line 954)
+zh Duplicate key: reconfigure (line 955)
+zh Duplicate key: refresh (line 956)
+zh Duplicate key: settings (line 957)
+zh Duplicate key: needLogin (line 958)
+zh Duplicate key: citationSources (line 959)
+zh Duplicate key: chunkNumber (line 960)
+zh Duplicate key: getUserListFailed (line 961)
+zh Duplicate key: usernamePasswordRequired (line 962)
+zh Duplicate key: passwordMinLength (line 963)
+zh Duplicate key: userCreatedSuccess (line 964)
+zh Duplicate key: createUserFailed (line 965)
+zh Duplicate key: userPromotedToAdmin (line 966)
+zh Duplicate key: userDemotedFromAdmin (line 967)
+zh Duplicate key: updateUserFailed (line 968)
+zh Duplicate key: confirmDeleteUser (line 969)
+zh Duplicate key: deleteUser (line 970)
+zh Duplicate key: deleteUserFailed (line 971)
+zh Duplicate key: userDeletedSuccessfully (line 972)
+zh Duplicate key: makeUserAdmin (line 973)
+zh Duplicate key: makeUserRegular (line 974)
+zh Duplicate key: loading (line 975)
+zh Duplicate key: noUsers (line 976)
+zh Duplicate key: aiAssistant (line 979)
+zh Duplicate key: polishContent (line 980)
+zh Duplicate key: expandContent (line 981)
+zh Duplicate key: summarizeContent (line 982)
+zh Duplicate key: translateToEnglish (line 983)
+zh Duplicate key: fixGrammar (line 984)
+zh Duplicate key: aiCommandInstructPolish (line 985)
+zh Duplicate key: aiCommandInstructExpand (line 986)
+zh Duplicate key: aiCommandInstructSummarize (line 987)
+zh Duplicate key: aiCommandInstructTranslateToEn (line 988)
+zh Duplicate key: aiCommandInstructFixGrammar (line 989)
+zh Duplicate key: aiCommandsPreset (line 990)
+zh Duplicate key: aiCommandsCustom (line 991)
+zh Duplicate key: aiCommandsCustomPlaceholder (line 992)
+zh Duplicate key: aiCommandsReferenceContext (line 993)
+zh Duplicate key: aiCommandsStartGeneration (line 994)
+zh Duplicate key: aiCommandsResult (line 995)
+zh Duplicate key: aiCommandsGenerating (line 996)
+zh Duplicate key: aiCommandsApplyResult (line 997)
+zh Duplicate key: aiCommandsGoBack (line 998)
+zh Duplicate key: aiCommandsReset (line 999)
+zh Duplicate key: aiCommandsModalPreset (line 1000)
+zh Duplicate key: aiCommandsModalCustom (line 1001)
+zh Duplicate key: aiCommandsModalCustomPlaceholder (line 1002)
+zh Duplicate key: aiCommandsModalBasedOnSelection (line 1003)
+zh Duplicate key: aiCommandsModalResult (line 1004)
+zh Duplicate key: aiCommandsModalApply (line 1005)
+zh Duplicate key: fillAllFields (line 1008)
+zh Duplicate key: passwordMismatch (line 1009)
+zh Duplicate key: newPasswordMinLength (line 1010)
+zh Duplicate key: changePasswordFailed (line 1011)
+zh Duplicate key: changePasswordTitle (line 1012)
+zh Duplicate key: changing (line 1013)
+zh Duplicate key: searchResults (line 1014)
+zh Duplicate key: visionModelSettings (line 1017)
+zh Duplicate key: defaultVisionModel (line 1018)
+zh Duplicate key: loadVisionModelFailed (line 1019)
+zh Duplicate key: loadFailed (line 1020)
+zh Duplicate key: saveVisionModelFailed (line 1021)
+zh Duplicate key: noVisionModels (line 1022)
+zh Duplicate key: selectVisionModel (line 1023)
+zh Duplicate key: visionModelHelp (line 1024)
+zh Duplicate key: mmErrorNameRequired (line 1025)
+zh Duplicate key: mmErrorModelIdRequired (line 1026)
+zh Duplicate key: mmErrorBaseUrlRequired (line 1027)
+zh Duplicate key: mmRequiredAsterisk (line 1028)
+zh Duplicate key: typeLLM (line 1030)
+zh Duplicate key: typeEmbedding (line 1031)
+zh Duplicate key: typeRerank (line 1032)
+zh Duplicate key: typeVision (line 1033)
+zh Duplicate key: welcome (line 1035)
+zh Duplicate key: placeholderWithFiles (line 1036)
+zh Duplicate key: placeholderEmpty (line 1037)
+zh Duplicate key: analyzing (line 1038)
+zh Duplicate key: errorGeneric (line 1039)
+zh Duplicate key: errorLabel (line 1040)
+zh Duplicate key: errorNoModel (line 1041)
+zh Duplicate key: aiDisclaimer (line 1042)
+zh Duplicate key: confirmClear (line 1043)
+zh Duplicate key: removeFile (line 1044)
+zh Duplicate key: apiError (line 1045)
+zh Duplicate key: geminiError (line 1046)
+zh Duplicate key: processedButNoText (line 1047)
+zh Duplicate key: unitByte (line 1048)
+zh Duplicate key: readingFailed (line 1049)
+zh Duplicate key: copy (line 1051)
+zh Duplicate key: copied (line 1052)
+zh Duplicate key: logout (line 1055)
+zh Duplicate key: changePassword (line 1056)
+zh Duplicate key: userManagement (line 1057)
+zh Duplicate key: userList (line 1058)
+zh Duplicate key: addUser (line 1059)
+zh Duplicate key: username (line 1060)
+zh Duplicate key: password (line 1061)
+zh Duplicate key: confirmPassword (line 1062)
+zh Duplicate key: currentPassword (line 1063)
+zh Duplicate key: newPassword (line 1064)
+zh Duplicate key: createUser (line 1065)
+zh Duplicate key: admin (line 1066)
+zh Duplicate key: user (line 1067)
+zh Duplicate key: adminUser (line 1068)
+zh Duplicate key: confirmChange (line 1069)
+zh Duplicate key: changeUserPassword (line 1070)
+zh Duplicate key: enterNewPassword (line 1071)
+zh Duplicate key: createdAt (line 1072)
+zh Duplicate key: newChat (line 1073)
+zh Duplicate key: selectKnowledgeGroups (line 1076)
+zh Duplicate key: searchGroupsPlaceholder (line 1077)
+zh Duplicate key: done (line 1078)
+zh Duplicate key: all (line 1079)
+zh Duplicate key: noGroupsFound (line 1080)
+zh Duplicate key: noGroups (line 1081)
+zh Duplicate key: autoRefresh (line 1084)
+zh Duplicate key: refreshInterval (line 1085)
+zh Duplicate key: kbManagement (line 1088)
+zh Duplicate key: kbManagementDesc (line 1089)
+zh Duplicate key: searchPlaceholder (line 1090)
+zh Duplicate key: allGroups (line 1091)
+zh Duplicate key: allStatus (line 1092)
+zh Duplicate key: statusReadyFragment (line 1093)
+zh Duplicate key: statusFailedFragment (line 1094)
+zh Duplicate key: statusIndexingFragment (line 1095)
+zh Duplicate key: uploadFile (line 1096)
+zh Duplicate key: fileName (line 1097)
+zh Duplicate key: size (line 1098)
+zh Duplicate key: status (line 1099)
+zh Duplicate key: groups (line 1100)
+zh Duplicate key: actions (line 1101)
+zh Duplicate key: groupsActions (line 1102)
+zh Duplicate key: noFilesFound (line 1103)
+zh Duplicate key: showingRange (line 1104)
+zh Duplicate key: confirmDeleteFile (line 1105)
+zh Duplicate key: fileDeleted (line 1106)
+zh Duplicate key: deleteFailed (line 1107)
+zh Duplicate key: confirmClearKB (line 1108)
+zh Duplicate key: kbCleared (line 1109)
+zh Duplicate key: clearFailed (line 1110)
+zh Duplicate key: actionFailed (line 1111)
+zh Duplicate key: groupCreated (line 1112)
+zh Duplicate key: groupUpdated (line 1113)
+zh Duplicate key: groupDeleted (line 1114)
+zh Duplicate key: groupManagement (line 1115)
+zh Duplicate key: loginRequired (line 1116)
+zh Duplicate key: uploadErrors (line 1117)
+zh Duplicate key: uploadWarning (line 1118)
+zh Duplicate key: uploadFailed (line 1119)
+zh Duplicate key: preview (line 1120)
+zh Duplicate key: addGroup (line 1121)
+zh Duplicate key: delete (line 1122)
+zh Duplicate key: retry (line 1123)
+zh Duplicate key: retrying (line 1124)
+zh Duplicate key: retrySuccess (line 1125)
+zh Duplicate key: retryFailed (line 1126)
+zh Duplicate key: chunkInfo (line 1127)
+zh Duplicate key: totalChunks (line 1128)
+zh Duplicate key: chunkIndex (line 1129)
+zh Duplicate key: contentLength (line 1130)
+zh Duplicate key: position (line 1131)
+zh Duplicate key: reconfigureTitle (line 1134)
+zh Duplicate key: reconfigureDesc (line 1135)
+zh Duplicate key: indexingConfigTitle (line 1136)
+zh Duplicate key: indexingConfigDesc (line 1137)
+zh Duplicate key: pendingFiles (line 1138)
+zh Duplicate key: processingMode (line 1139)
+zh Duplicate key: analyzingFile (line 1140)
+zh Duplicate key: recommendationReason (line 1141)
+zh Duplicate key: fastMode (line 1142)
+zh Duplicate key: fastModeDesc (line 1143)
+zh Duplicate key: preciseMode (line 1144)
+zh Duplicate key: preciseModeDesc (line 1145)
+zh Duplicate key: fastModeFeatures (line 1146)
+zh Duplicate key: fastFeature1 (line 1147)
+zh Duplicate key: fastFeature2 (line 1148)
+zh Duplicate key: fastFeature3 (line 1149)
+zh Duplicate key: fastFeature4 (line 1150)
+zh Duplicate key: fastFeature5 (line 1151)
+zh Duplicate key: preciseModeFeatures (line 1152)
+zh Duplicate key: preciseFeature1 (line 1153)
+zh Duplicate key: preciseFeature2 (line 1154)
+zh Duplicate key: preciseFeature3 (line 1155)
+zh Duplicate key: preciseFeature4 (line 1156)
+zh Duplicate key: preciseFeature5 (line 1157)
+zh Duplicate key: preciseFeature6 (line 1158)
+zh Duplicate key: embeddingModel (line 1159)
+zh Duplicate key: pleaseSelect (line 1160)
+zh Duplicate key: pleaseSelectKnowledgeGroupFirst (line 1161)
+zh Duplicate key: selectUnassignGroupWarning (line 1162)
+zh Duplicate key: chunkConfig (line 1163)
+zh Duplicate key: chunkSize (line 1164)
+zh Duplicate key: min (line 1165)
+zh Duplicate key: max (line 1166)
+zh Duplicate key: chunkOverlap (line 1167)
+zh Duplicate key: modelLimitsInfo (line 1168)
+zh Duplicate key: model (line 1169)
+zh Duplicate key: maxChunkSize (line 1170)
+zh Duplicate key: maxOverlapSize (line 1171)
+zh Duplicate key: maxBatchSize (line 1172)
+zh Duplicate key: envLimitWeaker (line 1173)
+zh Duplicate key: optimizationTips (line 1174)
+zh Duplicate key: tipChunkTooLarge (line 1175)
+zh Duplicate key: tipOverlapSmall (line 1176)
+zh Duplicate key: tipMaxValues (line 1177)
+zh Duplicate key: tipPreciseCost (line 1178)
+zh Duplicate key: selectEmbeddingFirst (line 1179)
+zh Duplicate key: confirmPreciseCost (line 1180)
+zh Duplicate key: startProcessing (line 1181)
+zh Duplicate key: notebooks (line 1184)
+zh Duplicate key: notebooksDesc (line 1185)
+zh Duplicate key: createNotebook (line 1186)
+zh Duplicate key: chatWithNotebook (line 1187)
+zh Duplicate key: editNotebook (line 1188)
+zh Duplicate key: deleteNotebook (line 1189)
+zh Duplicate key: noDescription (line 1190)
+zh Duplicate key: noNotebooks (line 1193)
+zh Duplicate key: createFailed (line 1194)
+zh Duplicate key: confirmDeleteNotebook (line 1195)
+zh Duplicate key: createNotebookTitle (line 1202)
+zh Duplicate key: createFailedRetry (line 1203)
+zh Duplicate key: updateFailedRetry (line 1204)
+zh Duplicate key: name (line 1205)
+zh Duplicate key: nameHelp (line 1206)
+zh Duplicate key: namePlaceholder (line 1207)
+zh Duplicate key: shortDescription (line 1208)
+zh Duplicate key: descPlaceholder (line 1209)
+zh Duplicate key: creating (line 1213)
+zh Duplicate key: createNow (line 1214)
+zh Duplicate key: saving (line 1215)
+zh Duplicate key: save (line 1216)
+zh Duplicate key: chatTitle (line 1219)
+zh Duplicate key: chatDesc (line 1220)
+zh Duplicate key: viewHistory (line 1221)
+zh Duplicate key: saveSettingsFailed (line 1222)
+zh Duplicate key: loginToUpload (line 1223)
+zh Duplicate key: fileSizeLimitExceeded (line 1224)
+zh Duplicate key: unsupportedFileType (line 1225)
+zh Duplicate key: readFailed (line 1226)
+zh Duplicate key: loadHistoryFailed (line 1227)
+zh Duplicate key: loadingUserData (line 1228)
+zh Duplicate key: errorMessage (line 1229)
+zh Duplicate key: welcomeMessage (line 1230)
+zh Duplicate key: selectKnowledgeGroup (line 1231)
+zh Duplicate key: allKnowledgeGroups (line 1232)
+zh Duplicate key: unknownGroup (line 1233)
+zh Duplicate key: selectedGroupsCount (line 1234)
+zh Duplicate key: generalSettings (line 1237)
+zh Duplicate key: modelManagement (line 1238)
+zh Duplicate key: languageSettings (line 1239)
+zh Duplicate key: passwordChangeSuccess (line 1240)
+zh Duplicate key: passwordChangeFailed (line 1241)
+zh Duplicate key: create (line 1242)
+zh Duplicate key: validationFailedMsg (line 1243)
+zh Duplicate key: navChat (line 1247)
+zh Duplicate key: navCoach (line 1248)
+zh Duplicate key: navKnowledge (line 1249)
+zh Duplicate key: navKnowledgeGroups (line 1250)
+zh Duplicate key: navNotebook (line 1251)
+zh Duplicate key: notebookDesc (line 1252)
+zh Duplicate key: newNote (line 1253)
+zh Duplicate key: editNote (line 1254)
+zh Duplicate key: noNotesFound (line 1255)
+zh Duplicate key: startByCreatingNote (line 1256)
+zh Duplicate key: navCrawler (line 1257)
+zh Duplicate key: expandMenu (line 1258)
+zh Duplicate key: switchLanguage (line 1259)
+zh Duplicate key: importFolder (line 1261)
+zh Duplicate key: createPDFNote (line 1264)
+zh Duplicate key: screenshotPreview (line 1265)
+zh Duplicate key: associateKnowledgeGroup (line 1266)
+zh Duplicate key: globalNoSpecificGroup (line 1267)
+zh Duplicate key: title (line 1268)
+zh Duplicate key: enterNoteTitle (line 1269)
+zh Duplicate key: contentOCR (line 1270)
+zh Duplicate key: extractingText (line 1271)
+zh Duplicate key: analyzingImage (line 1272)
+zh Duplicate key: noTextExtracted (line 1273)
+zh Duplicate key: saveNote (line 1274)
+zh Duplicate key: page (line 1277)
+zh Duplicate key: placeholderText (line 1278)
+zh Duplicate key: createNewNotebook (line 1281)
+zh Duplicate key: nameField (line 1282)
+zh Duplicate key: required (line 1283)
+zh Duplicate key: exampleResearch (line 1284)
+zh Duplicate key: shortDescriptionField (line 1285)
+zh Duplicate key: describePurpose (line 1286)
+zh Duplicate key: creationFailed (line 1289)
+zh Duplicate key: preparingPDFConversion (line 1292)
+zh Duplicate key: pleaseWait (line 1293)
+zh Duplicate key: convertingPDF (line 1294)
+zh Duplicate key: pdfConversionFailed (line 1295)
+zh Duplicate key: pdfConversionError (line 1296)
+zh Duplicate key: pdfLoadFailed (line 1297)
+zh Duplicate key: pdfLoadError (line 1298)
+zh Duplicate key: downloadingPDF (line 1299)
+zh Duplicate key: loadingPDF (line 1300)
+zh Duplicate key: zoomOut (line 1301)
+zh Duplicate key: zoomIn (line 1302)
+zh Duplicate key: resetZoom (line 1303)
+zh Duplicate key: selectPageNumber (line 1304)
+zh Duplicate key: enterPageNumber (line 1305)
+zh Duplicate key: exitSelectionMode (line 1306)
+zh Duplicate key: clickToSelectAndNote (line 1307)
+zh Duplicate key: regeneratePDF (line 1308)
+zh Duplicate key: downloadPDF (line 1309)
+zh Duplicate key: openInNewWindow (line 1310)
+zh Duplicate key: exitFullscreen (line 1311)
+zh Duplicate key: fullscreenDisplay (line 1312)
+zh Duplicate key: pdfPreview (line 1313)
+zh Duplicate key: converting (line 1314)
+zh Duplicate key: generatePDFPreview (line 1315)
+zh Duplicate key: previewNotSupported (line 1316)
+zh Duplicate key: confirmRegeneratePDF (line 1319)
+zh Duplicate key: pdfPreviewReady (line 1322)
+zh Duplicate key: convertingInProgress (line 1323)
+zh Duplicate key: conversionFailed (line 1324)
+zh Duplicate key: generatePDFPreviewButton (line 1325)
+zh Duplicate key: checkPDFStatusFailed (line 1328)
+zh Duplicate key: requestRegenerationFailed (line 1329)
+zh Duplicate key: downloadPDFFailed (line 1330)
+zh Duplicate key: openPDFInNewTabFailed (line 1331)
+zh Duplicate key: invalidFile (line 1334)
+zh Duplicate key: incompleteFileInfo (line 1335)
+zh Duplicate key: unsupportedFileFormat (line 1336)
+zh Duplicate key: willUseFastMode (line 1337)
+zh Duplicate key: formatNoPrecise (line 1338)
+zh Duplicate key: smallFileFastOk (line 1339)
+zh Duplicate key: mixedContentPreciseRecommended (line 1340)
+zh Duplicate key: willIncurApiCost (line 1341)
+zh Duplicate key: largeFilePreciseRecommended (line 1342)
+zh Duplicate key: longProcessingTime (line 1343)
+zh Duplicate key: highApiCost (line 1344)
+zh Duplicate key: considerFileSplitting (line 1345)
+zh Duplicate key: dragDropUploadTitle (line 1348)
+zh Duplicate key: dragDropUploadDesc (line 1349)
+zh Duplicate key: supportedFormats (line 1350)
+zh Duplicate key: browseFiles (line 1351)
+zh Duplicate key: recommendationMsg (line 1354)
+zh Duplicate key: autoAdjustChunk (line 1355)
+zh Duplicate key: autoAdjustOverlap (line 1356)
+zh Duplicate key: autoAdjustOverlapMin (line 1357)
+zh Duplicate key: loadLimitsFailed (line 1358)
+zh Duplicate key: maxValueMsg (line 1359)
+zh Duplicate key: overlapRatioLimit (line 1360)
+zh Duplicate key: onlyAdminCanModify (line 1361)
+zh Duplicate key: dragToSelect (line 1362)
+zh Duplicate key: fillTargetName (line 1365)
+zh Duplicate key: submitFailed (line 1366)
+zh Duplicate key: importFolderTitle (line 1367)
+zh Duplicate key: importFolderTip (line 1368)
+zh Duplicate key: lblTargetGroup (line 1369)
+zh Duplicate key: placeholderNewGroup (line 1370)
+zh Duplicate key: importToCurrentGroup (line 1371)
+zh Duplicate key: nextStep (line 1372)
+zh Duplicate key: selectedFilesCount (line 1376)
+zh Duplicate key: clickToSelectFolder (line 1377)
+zh Duplicate key: selectFolderTip (line 1378)
+zh Duplicate key: importComplete (line 1379)
+zh Duplicate key: importedFromLocalFolder (line 1380)
+zh Duplicate key: historyTitle (line 1383)
+zh Duplicate key: confirmDeleteHistory (line 1384)
+zh Duplicate key: deleteHistorySuccess (line 1385)
+zh Duplicate key: deleteHistoryFailed (line 1386)
+zh Duplicate key: yesterday (line 1387)
+zh Duplicate key: daysAgo (line 1388)
+zh Duplicate key: historyMessages (line 1389)
+zh Duplicate key: noHistory (line 1390)
+zh Duplicate key: noHistoryDesc (line 1391)
+zh Duplicate key: loadMore (line 1392)
+zh Duplicate key: loadingHistoriesFailed (line 1393)
+zh Duplicate key: supportedFormatsInfo (line 1394)
+zh Duplicate key: navCatalog (line 1397)
+zh Duplicate key: allDocuments (line 1398)
+zh Duplicate key: uncategorized (line 1399)
+zh Duplicate key: categories (line 1400)
+zh Duplicate key: uncategorizedFiles (line 1401)
+zh Duplicate key: category (line 1402)
+zh Duplicate key: statusReadyDesc (line 1403)
+zh Duplicate key: statusIndexingDesc (line 1404)
+zh Duplicate key: selectCategory (line 1405)
+zh Duplicate key: noneUncategorized (line 1406)
+zh Duplicate key: previous (line 1407)
+zh Duplicate key: next (line 1408)
+zh Duplicate key: editCategory (line 1409)
+zh Duplicate key: createCategory (line 1410)
+zh Duplicate key: categoryDesc (line 1411)
+zh Duplicate key: categoryName (line 1412)
+zh Duplicate key: saveChanges (line 1413)
+zh Duplicate key: createCategoryBtn (line 1414)
+zh Duplicate key: totalTenants (line 1417)
+zh Duplicate key: systemUsers (line 1418)
+zh Duplicate key: systemHealth (line 1419)
+zh Duplicate key: operational (line 1420)
+zh Duplicate key: orgManagement (line 1421)
+zh Duplicate key: globalTenantControl (line 1422)
+zh Duplicate key: newTenant (line 1423)
+zh Duplicate key: tenantName (line 1424)
+zh Duplicate key: domainOptional (line 1425)
+zh Duplicate key: assignInitialAdmin (line 1426)
+zh Duplicate key: selectUserOptional (line 1427)
+zh Duplicate key: promoteToAdminWarning (line 1428)
+zh Duplicate key: editOrganization (line 1429)
+zh Duplicate key: createOrganization (line 1430)
+zh Duplicate key: bindAdmin (line 1431)
+zh Duplicate key: manageMembers (line 1432)
+zh Duplicate key: currentMembers (line 1433)
+zh Duplicate key: addMembers (line 1434)
+zh Duplicate key: systemRestricted (line 1435)
+zh Duplicate key: noUnassignedUsers (line 1436)
+zh Duplicate key: enableNotebook (line 1437)
+zh Duplicate key: disableNotebook (line 1438)
+zh Duplicate key: featureUpdated (line 1439)
+zh Duplicate key: kbSettingsSaved (line 1444)
+zh Duplicate key: failedToSaveSettings (line 1445)
+zh Duplicate key: modelConfiguration (line 1446)
+zh Duplicate key: defaultLLMModel (line 1447)
+zh Duplicate key: selectLLM (line 1448)
+zh Duplicate key: embeddingModel (line 1449)
+zh Duplicate key: selectEmbedding (line 1450)
+zh Duplicate key: rerankModel (line 1451)
+zh Duplicate key: none (line 1452)
+zh Duplicate key: indexingChunkingConfig (line 1453)
+zh Duplicate key: chatHyperparameters (line 1454)
+zh Duplicate key: precise (line 1455)
+zh Duplicate key: creative (line 1456)
+zh Duplicate key: maxResponseTokens (line 1457)
+zh Duplicate key: retrievalSearchSettings (line 1458)
+zh Duplicate key: enableHybridSearch (line 1459)
+zh Duplicate key: hybridSearchDesc (line 1460)
+zh Duplicate key: hybridWeight (line 1461)
+zh Duplicate key: pureText (line 1462)
+zh Duplicate key: pureVector (line 1463)
+zh Duplicate key: enableQueryExpansion (line 1464)
+zh Duplicate key: queryExpansionDesc (line 1465)
+zh Duplicate key: enableHyDE (line 1466)
+zh Duplicate key: hydeDesc (line 1467)
+zh Duplicate key: enableReranking (line 1468)
+zh Duplicate key: rerankingDesc (line 1469)
+zh Duplicate key: temperature (line 1470)
+zh Duplicate key: chunkSize (line 1471)
+zh Duplicate key: chunkOverlap (line 1472)
+zh Duplicate key: topK (line 1473)
+zh Duplicate key: similarityThreshold (line 1474)
+zh Duplicate key: rerankSimilarityThreshold (line 1475)
+zh Duplicate key: broad (line 1476)
+zh Duplicate key: strict (line 1477)
+zh Duplicate key: maxInput (line 1478)
+zh Duplicate key: dimensions (line 1479)
+zh Duplicate key: dims (line 1480)
+zh Duplicate key: ctx (line 1481)
+zh Duplicate key: baseApi (line 1482)
+zh Duplicate key: configured (line 1483)
+zh Duplicate key: defaultBadge (line 1484)
+zh Duplicate key: roleTenantAdmin (line 1485)
+zh Duplicate key: roleRegularUser (line 1486)
+zh Duplicate key: creatingRegularUser (line 1487)
+zh Duplicate key: importFolder (line 1490)
+zh Duplicate key: newGroup (line 1491)
+zh Duplicate key: noKnowledgeGroups (line 1492)
+zh Duplicate key: createGroupDesc (line 1493)
+zh Duplicate key: noDescriptionProvided (line 1494)
+zh Duplicate key: files (line 1495)
+zh Duplicate key: filterGroupFiles (line 1496)
+zh Duplicate key: browseManageFiles (line 1497)
+zh Duplicate key: navAgent (line 1500)
+zh Duplicate key: agentTitle (line 1501)
+zh Duplicate key: agentDesc (line 1502)
+zh Duplicate key: searchAgent (line 1503)
+zh Duplicate key: createAgent (line 1504)
+zh Duplicate key: statusRunning (line 1505)
+zh Duplicate key: statusStopped (line 1506)
+zh Duplicate key: btnChat (line 1507)
+zh Duplicate key: navPlugin (line 1510)
+zh Duplicate key: pluginTitle (line 1511)
+zh Duplicate key: pluginDesc (line 1512)
+zh Duplicate key: searchPlugin (line 1513)
+zh Duplicate key: installPlugin (line 1514)
+zh Duplicate key: installedPlugin (line 1515)
+zh Duplicate key: updatePlugin (line 1516)
+zh Duplicate key: selectOrganization (line 1519)
+zh Duplicate key: defaultTenant (line 1520)
+zh Duplicate key: roleTenantAdmin (line 1521)
+zh Duplicate key: roleRegularUser (line 1522)
+zh Duplicate key: creatingRegularUser (line 1523)
+zh Duplicate key: defaultBadge (line 1526)
+zh Duplicate key: plugin1Name (line 1533)
+zh Duplicate key: plugin1Desc (line 1534)
+zh Duplicate key: plugin2Name (line 1535)
+zh Duplicate key: plugin2Desc (line 1536)
+zh Duplicate key: plugin3Name (line 1537)
+zh Duplicate key: plugin3Desc (line 1538)
+zh Duplicate key: plugin4Name (line 1539)
+zh Duplicate key: plugin4Desc (line 1540)
+zh Duplicate key: plugin5Name (line 1541)
+zh Duplicate key: plugin5Desc (line 1542)
+zh Duplicate key: plugin6Name (line 1543)
+zh Duplicate key: plugin6Desc (line 1544)
+zh Duplicate key: agent1Name (line 1545)
+zh Duplicate key: agent1Desc (line 1546)
+zh Duplicate key: agent1Time (line 1547)
+zh Duplicate key: agent2Name (line 1548)
+zh Duplicate key: agent2Desc (line 1549)
+zh Duplicate key: agent2Time (line 1550)
+zh Duplicate key: agent3Name (line 1551)
+zh Duplicate key: agent3Desc (line 1552)
+zh Duplicate key: agent3Time (line 1553)
+zh Duplicate key: agent4Name (line 1554)
+zh Duplicate key: agent4Desc (line 1555)
+zh Duplicate key: agent4Time (line 1556)
+zh Duplicate key: agent5Name (line 1557)
+zh Duplicate key: agent5Desc (line 1558)
+zh Duplicate key: agent5Time (line 1559)
+zh Duplicate key: agent6Name (line 1560)
+zh Duplicate key: agent6Desc (line 1561)
+zh Duplicate key: agent6Time (line 1562)
+zh Duplicate key: agent7Name (line 1563)
+zh Duplicate key: agent7Desc (line 1564)
+zh Duplicate key: agent7Time (line 1565)
+zh Duplicate key: generalSettingsSubtitle (line 1566)
+zh Duplicate key: userManagementSubtitle (line 1567)
+zh Duplicate key: modelManagementSubtitle (line 1568)
+zh Duplicate key: kbSettingsSubtitle (line 1569)
+zh Duplicate key: tenantsSubtitle (line 1570)
+zh Duplicate key: allNotes (line 1572)
+zh Duplicate key: filterNotesPlaceholder (line 1573)
+zh Duplicate key: noteTitlePlaceholder (line 1574)
+zh Duplicate key: startWritingPlaceholder (line 1575)
+zh Duplicate key: previewHeader (line 1576)
+zh Duplicate key: noContentToPreview (line 1577)
+zh Duplicate key: hidePreview (line 1578)
+zh Duplicate key: showPreview (line 1579)
+zh Duplicate key: directoryLabel (line 1580)
+zh Duplicate key: uncategorized (line 1581)
+zh Duplicate key: enterNamePlaceholder (line 1582)
+zh Duplicate key: subFolderPlaceholder (line 1583)
+zh Duplicate key: categoryCreated (line 1584)
+zh Duplicate key: failedToCreateCategory (line 1585)
+zh Duplicate key: failedToDeleteCategory (line 1586)
+zh Duplicate key: confirmDeleteCategory (line 1587)
+zh Duplicate key: aiCommandsError (line 1590)
+zh Duplicate key: appTitle (line 1591)
+zh Duplicate key: loginTitle (line 1592)
+zh Duplicate key: loginDesc (line 1593)
+zh Duplicate key: loginButton (line 1594)
+zh Duplicate key: loginError (line 1595)
+zh Duplicate key: unknownError (line 1596)
+zh Duplicate key: usernamePlaceholder (line 1597)
+zh Duplicate key: passwordPlaceholder (line 1598)
+zh Duplicate key: registerButton (line 1599)
+zh Duplicate key: langZh (line 1600)
+zh Duplicate key: langEn (line 1601)
+zh Duplicate key: langJa (line 1602)
+zh Duplicate key: confirm (line 1603)
+zh Duplicate key: cancel (line 1604)
+zh Duplicate key: confirmTitle (line 1605)
+zh Duplicate key: confirmDeleteGroup (line 1606)
+zh Duplicate key: sidebarTitle (line 1608)
+zh Duplicate key: backToWorkspace (line 1609)
+zh Duplicate key: goToAdmin (line 1610)
+zh Duplicate key: sidebarDesc (line 1611)
+zh Duplicate key: tabFiles (line 1612)
+zh Duplicate key: files (line 1613)
+zh Duplicate key: notes (line 1614)
+zh Duplicate key: tabSettings (line 1615)
+zh Duplicate key: systemConfiguration (line 1616)
+zh Duplicate key: noFiles (line 1617)
+zh Duplicate key: noFilesDesc (line 1618)
+zh Duplicate key: addFile (line 1619)
+zh Duplicate key: clearAll (line 1620)
+zh Duplicate key: uploading (line 1621)
+zh Duplicate key: statusIndexing (line 1622)
+zh Duplicate key: statusReady (line 1623)
+zh Duplicate key: ragSettings (line 1626)
+zh Duplicate key: enableRerank (line 1627)
+zh Duplicate key: enableRerankDesc (line 1628)
+zh Duplicate key: selectRerankModel (line 1629)
+zh Duplicate key: selectModelPlaceholder (line 1630)
+zh Duplicate key: headerModelSelection (line 1632)
+zh Duplicate key: headerHyperparams (line 1633)
+zh Duplicate key: headerIndexing (line 1634)
+zh Duplicate key: headerRetrieval (line 1635)
+zh Duplicate key: btnManageModels (line 1636)
+zh Duplicate key: lblLLM (line 1638)
+zh Duplicate key: lblEmbedding (line 1639)
+zh Duplicate key: lblRerankRef (line 1640)
+zh Duplicate key: lblTemperature (line 1641)
+zh Duplicate key: lblMaxTokens (line 1642)
+zh Duplicate key: lblChunkSize (line 1643)
+zh Duplicate key: lblChunkOverlap (line 1644)
+zh Duplicate key: lblTopK (line 1645)
+zh Duplicate key: lblRerank (line 1646)
+zh Duplicate key: idxModalTitle (line 1648)
+zh Duplicate key: idxDesc (line 1649)
+zh Duplicate key: idxFiles (line 1650)
+zh Duplicate key: idxMethod (line 1651)
+zh Duplicate key: idxEmbeddingModel (line 1652)
+zh Duplicate key: idxStart (line 1653)
+zh Duplicate key: idxCancel (line 1654)
+zh Duplicate key: idxAuto (line 1655)
+zh Duplicate key: idxCustom (line 1656)
+zh Duplicate key: mmTitle (line 1658)
+zh Duplicate key: mmAddBtn (line 1659)
+zh Duplicate key: mmEdit (line 1660)
+zh Duplicate key: mmDelete (line 1661)
+zh Duplicate key: mmEmpty (line 1662)
+zh Duplicate key: mmFormName (line 1663)
+zh Duplicate key: mmFormProvider (line 1664)
+zh Duplicate key: mmFormModelId (line 1665)
+zh Duplicate key: mmFormBaseUrl (line 1666)
+zh Duplicate key: mmFormType (line 1667)
+zh Duplicate key: mmFormVision (line 1668)
+zh Duplicate key: mmFormDimensions (line 1669)
+zh Duplicate key: mmFormDimensionsHelp (line 1670)
+zh Duplicate key: mmSave (line 1671)
+zh Duplicate key: mmCancel (line 1672)
+zh Duplicate key: mmErrorNotAuthenticated (line 1673)
+zh Duplicate key: mmErrorTitle (line 1674)
+zh Duplicate key: modelEnabled (line 1675)
+zh Duplicate key: modelDisabled (line 1676)
+zh Duplicate key: confirmChangeEmbeddingModel (line 1677)
+zh Duplicate key: embeddingModelWarning (line 1678)
+zh Duplicate key: sourcePreview (line 1679)
+zh Duplicate key: matchScore (line 1680)
+zh Duplicate key: copyContent (line 1681)
+zh Duplicate key: copySuccess (line 1682)
+zh Duplicate key: selectLLMModel (line 1685)
+zh Duplicate key: selectEmbeddingModel (line 1686)
+zh Duplicate key: defaultForUploads (line 1687)
+zh Duplicate key: noRerankModel (line 1688)
+zh Duplicate key: vectorSimilarityThreshold (line 1689)
+zh Duplicate key: rerankSimilarityThreshold (line 1690)
+zh Duplicate key: filterLowResults (line 1691)
+zh Duplicate key: noteCreatedSuccess (line 1692)
+zh Duplicate key: noteCreatedFailed (line 1693)
+zh Duplicate key: fullTextSearch (line 1694)
+zh Duplicate key: hybridVectorWeight (line 1695)
+zh Duplicate key: hybridVectorWeightDesc (line 1696)
+zh Duplicate key: lblQueryExpansion (line 1697)
+zh Duplicate key: lblHyDE (line 1698)
+zh Duplicate key: lblQueryExpansionDesc (line 1699)
+zh Duplicate key: lblHyDEDesc (line 1700)
+zh Duplicate key: apiKeyValidationFailed (line 1702)
+zh Duplicate key: keepOriginalKey (line 1703)
+zh Duplicate key: leaveEmptyNoChange (line 1704)
+zh Duplicate key: mmFormApiKey (line 1705)
+zh Duplicate key: mmFormApiKeyPlaceholder (line 1706)
+zh Duplicate key: reconfigureFile (line 1709)
+zh Duplicate key: modifySettings (line 1710)
+zh Duplicate key: filesCount (line 1711)
+zh Duplicate key: allFilesIndexed (line 1712)
+zh Duplicate key: noEmbeddingModels (line 1713)
+zh Duplicate key: reconfigure (line 1714)
+zh Duplicate key: refresh (line 1715)
+zh Duplicate key: settings (line 1716)
+zh Duplicate key: needLogin (line 1717)
+zh Duplicate key: citationSources (line 1718)
+zh Duplicate key: chunkNumber (line 1719)
+zh Duplicate key: getUserListFailed (line 1720)
+zh Duplicate key: usernamePasswordRequired (line 1721)
+zh Duplicate key: passwordMinLength (line 1722)
+zh Duplicate key: userCreatedSuccess (line 1723)
+zh Duplicate key: createUserFailed (line 1724)
+zh Duplicate key: userPromotedToAdmin (line 1725)
+zh Duplicate key: userDemotedFromAdmin (line 1726)
+zh Duplicate key: updateUserFailed (line 1727)
+zh Duplicate key: confirmDeleteUser (line 1728)
+zh Duplicate key: deleteUser (line 1729)
+zh Duplicate key: deleteUserFailed (line 1730)
+zh Duplicate key: userDeletedSuccessfully (line 1731)
+zh Duplicate key: makeUserAdmin (line 1732)
+zh Duplicate key: makeUserRegular (line 1733)
+zh Duplicate key: loading (line 1734)
+zh Duplicate key: noUsers (line 1735)
+zh Duplicate key: aiAssistant (line 1738)
+zh Duplicate key: polishContent (line 1739)
+zh Duplicate key: expandContent (line 1740)
+zh Duplicate key: summarizeContent (line 1741)
+zh Duplicate key: translateToEnglish (line 1742)
+zh Duplicate key: fixGrammar (line 1743)
+zh Duplicate key: aiCommandInstructPolish (line 1744)
+zh Duplicate key: aiCommandInstructExpand (line 1745)
+zh Duplicate key: aiCommandInstructSummarize (line 1746)
+zh Duplicate key: aiCommandInstructTranslateToEn (line 1747)
+zh Duplicate key: aiCommandInstructFixGrammar (line 1748)
+zh Duplicate key: aiCommandsPreset (line 1749)
+zh Duplicate key: aiCommandsCustom (line 1750)
+zh Duplicate key: aiCommandsCustomPlaceholder (line 1751)
+zh Duplicate key: aiCommandsReferenceContext (line 1752)
+zh Duplicate key: aiCommandsStartGeneration (line 1753)
+zh Duplicate key: aiCommandsResult (line 1754)
+zh Duplicate key: aiCommandsGenerating (line 1755)
+zh Duplicate key: aiCommandsApplyResult (line 1756)
+zh Duplicate key: aiCommandsGoBack (line 1757)
+zh Duplicate key: aiCommandsReset (line 1758)
+zh Duplicate key: aiCommandsModalPreset (line 1759)
+zh Duplicate key: aiCommandsModalCustom (line 1760)
+zh Duplicate key: aiCommandsModalCustomPlaceholder (line 1761)
+zh Duplicate key: aiCommandsModalBasedOnSelection (line 1762)
+zh Duplicate key: aiCommandsModalResult (line 1763)
+zh Duplicate key: aiCommandsModalApply (line 1764)
+zh Duplicate key: fillAllFields (line 1767)
+zh Duplicate key: passwordMismatch (line 1768)
+zh Duplicate key: newPasswordMinLength (line 1769)
+zh Duplicate key: changePasswordFailed (line 1770)
+zh Duplicate key: changePasswordTitle (line 1771)
+zh Duplicate key: changing (line 1772)
+zh Duplicate key: searchResults (line 1773)
+zh Duplicate key: visionModelSettings (line 1776)
+zh Duplicate key: defaultVisionModel (line 1777)
+zh Duplicate key: loadVisionModelFailed (line 1778)
+zh Duplicate key: loadFailed (line 1779)
+zh Duplicate key: saveVisionModelFailed (line 1780)
+zh Duplicate key: noVisionModels (line 1781)
+zh Duplicate key: selectVisionModel (line 1782)
+zh Duplicate key: visionModelHelp (line 1783)
+zh Duplicate key: mmErrorNameRequired (line 1784)
+zh Duplicate key: mmErrorModelIdRequired (line 1785)
+zh Duplicate key: mmErrorBaseUrlRequired (line 1786)
+zh Duplicate key: mmRequiredAsterisk (line 1787)
+zh Duplicate key: typeLLM (line 1789)
+zh Duplicate key: typeEmbedding (line 1790)
+zh Duplicate key: typeRerank (line 1791)
+zh Duplicate key: typeVision (line 1792)
+zh Duplicate key: welcome (line 1794)
+zh Duplicate key: placeholderWithFiles (line 1795)
+zh Duplicate key: placeholderEmpty (line 1796)
+zh Duplicate key: analyzing (line 1797)
+zh Duplicate key: errorGeneric (line 1798)
+zh Duplicate key: errorLabel (line 1799)
+zh Duplicate key: errorNoModel (line 1800)
+zh Duplicate key: aiDisclaimer (line 1801)
+zh Duplicate key: confirmClear (line 1802)
+zh Duplicate key: removeFile (line 1803)
+zh Duplicate key: apiError (line 1804)
+zh Duplicate key: geminiError (line 1805)
+zh Duplicate key: processedButNoText (line 1806)
+zh Duplicate key: unitByte (line 1807)
+zh Duplicate key: readingFailed (line 1808)
+zh Duplicate key: copy (line 1810)
+zh Duplicate key: copied (line 1811)
+zh Duplicate key: logout (line 1814)
+zh Duplicate key: changePassword (line 1815)
+zh Duplicate key: userManagement (line 1816)
+zh Duplicate key: userList (line 1817)
+zh Duplicate key: addUser (line 1818)
+zh Duplicate key: username (line 1819)
+zh Duplicate key: password (line 1820)
+zh Duplicate key: confirmPassword (line 1821)
+zh Duplicate key: currentPassword (line 1822)
+zh Duplicate key: newPassword (line 1823)
+zh Duplicate key: createUser (line 1824)
+zh Duplicate key: admin (line 1825)
+zh Duplicate key: user (line 1826)
+zh Duplicate key: adminUser (line 1827)
+zh Duplicate key: confirmChange (line 1828)
+zh Duplicate key: changeUserPassword (line 1829)
+zh Duplicate key: enterNewPassword (line 1830)
+zh Duplicate key: createdAt (line 1831)
+zh Duplicate key: newChat (line 1832)
+zh Duplicate key: kbManagement (line 1835)
+zh Duplicate key: kbManagementDesc (line 1836)
+zh Duplicate key: searchPlaceholder (line 1837)
+zh Duplicate key: allGroups (line 1838)
+zh Duplicate key: allStatus (line 1839)
+zh Duplicate key: statusReadyFragment (line 1840)
+zh Duplicate key: statusFailedFragment (line 1841)
+zh Duplicate key: statusIndexingFragment (line 1842)
+zh Duplicate key: uploadFile (line 1843)
+zh Duplicate key: fileName (line 1844)
+zh Duplicate key: size (line 1845)
+zh Duplicate key: status (line 1846)
+zh Duplicate key: groups (line 1847)
+zh Duplicate key: actions (line 1848)
+zh Duplicate key: groupsActions (line 1849)
+zh Duplicate key: noFilesFound (line 1850)
+zh Duplicate key: showingRange (line 1851)
+zh Duplicate key: confirmDeleteFile (line 1852)
+zh Duplicate key: fileDeleted (line 1853)
+zh Duplicate key: deleteFailed (line 1854)
+zh Duplicate key: fileAddedToGroup (line 1855)
+zh Duplicate key: failedToAddToGroup (line 1856)
+zh Duplicate key: fileRemovedFromGroup (line 1857)
+zh Duplicate key: failedToRemoveFromGroup (line 1858)
+zh Duplicate key: confirmClearKB (line 1859)
+zh Duplicate key: kbCleared (line 1860)
+zh Duplicate key: clearFailed (line 1861)
+zh Duplicate key: actionFailed (line 1862)
+zh Duplicate key: groupCreated (line 1863)
+zh Duplicate key: groupUpdated (line 1864)
+zh Duplicate key: groupDeleted (line 1865)
+zh Duplicate key: groupManagement (line 1866)
+zh Duplicate key: loginRequired (line 1867)
+zh Duplicate key: uploadErrors (line 1868)
+zh Duplicate key: uploadWarning (line 1869)
+zh Duplicate key: uploadFailed (line 1870)
+zh Duplicate key: preview (line 1871)
+zh Duplicate key: addGroup (line 1872)
+zh Duplicate key: delete (line 1873)
+zh Duplicate key: retry (line 1874)
+zh Duplicate key: retrying (line 1875)
+zh Duplicate key: retrySuccess (line 1876)
+zh Duplicate key: retryFailed (line 1877)
+zh Duplicate key: chunkInfo (line 1878)
+zh Duplicate key: totalChunks (line 1879)
+zh Duplicate key: chunkIndex (line 1880)
+zh Duplicate key: contentLength (line 1881)
+zh Duplicate key: position (line 1882)
+zh Duplicate key: reconfigureTitle (line 1885)
+zh Duplicate key: reconfigureDesc (line 1886)
+zh Duplicate key: indexingConfigTitle (line 1887)
+zh Duplicate key: indexingConfigDesc (line 1888)
+zh Duplicate key: pendingFiles (line 1889)
+zh Duplicate key: processingMode (line 1890)
+zh Duplicate key: analyzingFile (line 1891)
+zh Duplicate key: recommendationReason (line 1892)
+zh Duplicate key: fastMode (line 1893)
+zh Duplicate key: fastModeDesc (line 1894)
+zh Duplicate key: preciseMode (line 1895)
+zh Duplicate key: preciseModeDesc (line 1896)
+zh Duplicate key: fastModeFeatures (line 1897)
+zh Duplicate key: fastFeature1 (line 1898)
+zh Duplicate key: fastFeature2 (line 1899)
+zh Duplicate key: fastFeature3 (line 1900)
+zh Duplicate key: fastFeature4 (line 1901)
+zh Duplicate key: fastFeature5 (line 1902)
+zh Duplicate key: preciseModeFeatures (line 1903)
+zh Duplicate key: preciseFeature1 (line 1904)
+zh Duplicate key: preciseFeature2 (line 1905)
+zh Duplicate key: preciseFeature3 (line 1906)
+zh Duplicate key: preciseFeature4 (line 1907)
+zh Duplicate key: preciseFeature5 (line 1908)
+zh Duplicate key: preciseFeature6 (line 1909)
+zh Duplicate key: embeddingModel (line 1910)
+zh Duplicate key: pleaseSelect (line 1911)
+zh Duplicate key: pleaseSelectKnowledgeGroupFirst (line 1912)
+zh Duplicate key: selectUnassignGroupWarning (line 1913)
+zh Duplicate key: chunkConfig (line 1914)
+zh Duplicate key: chunkSize (line 1915)
+zh Duplicate key: min (line 1916)
+zh Duplicate key: max (line 1917)
+zh Duplicate key: chunkOverlap (line 1918)
+zh Duplicate key: modelLimitsInfo (line 1919)
+zh Duplicate key: model (line 1920)
+zh Duplicate key: maxChunkSize (line 1921)
+zh Duplicate key: maxOverlapSize (line 1922)
+zh Duplicate key: maxBatchSize (line 1923)
+zh Duplicate key: envLimitWeaker (line 1924)
+zh Duplicate key: optimizationTips (line 1925)
+zh Duplicate key: tipChunkTooLarge (line 1926)
+zh Duplicate key: tipOverlapSmall (line 1927)
+zh Duplicate key: tipMaxValues (line 1928)
+zh Duplicate key: tipPreciseCost (line 1929)
+zh Duplicate key: selectEmbeddingFirst (line 1930)
+zh Duplicate key: confirmPreciseCost (line 1931)
+zh Duplicate key: startProcessing (line 1932)
+zh Duplicate key: notebooks (line 1935)
+zh Duplicate key: notebooksDesc (line 1936)
+zh Duplicate key: createNotebook (line 1937)
+zh Duplicate key: chatWithNotebook (line 1938)
+zh Duplicate key: editNotebook (line 1939)
+zh Duplicate key: deleteNotebook (line 1940)
+zh Duplicate key: noDescription (line 1941)
+zh Duplicate key: hasIntro (line 1942)
+zh Duplicate key: noIntro (line 1943)
+zh Duplicate key: noNotebooks (line 1944)
+zh Duplicate key: createFailed (line 1945)
+zh Duplicate key: confirmDeleteNotebook (line 1946)
+zh Duplicate key: errorFileTooLarge (line 1949)
+zh Duplicate key: noFilesYet (line 1950)
+zh Duplicate key: createNotebookTitle (line 1953)
+zh Duplicate key: editNotebookTitle (line 1954)
+zh Duplicate key: createFailedRetry (line 1955)
+zh Duplicate key: updateFailedRetry (line 1956)
+zh Duplicate key: name (line 1957)
+zh Duplicate key: nameHelp (line 1958)
+zh Duplicate key: namePlaceholder (line 1959)
+zh Duplicate key: shortDescription (line 1960)
+zh Duplicate key: descPlaceholder (line 1961)
+zh Duplicate key: detailedIntro (line 1962)
+zh Duplicate key: introPlaceholder (line 1963)
+zh Duplicate key: introHelp (line 1964)
+zh Duplicate key: creating (line 1965)
+zh Duplicate key: createNow (line 1966)
+zh Duplicate key: saving (line 1967)
+zh Duplicate key: save (line 1968)
+zh Duplicate key: chatTitle (line 1971)
+zh Duplicate key: chatDesc (line 1972)
+zh Duplicate key: viewHistory (line 1973)
+zh Duplicate key: saveSettingsFailed (line 1974)
+zh Duplicate key: loginToUpload (line 1975)
+zh Duplicate key: fileSizeLimitExceeded (line 1976)
+zh Duplicate key: unsupportedFileType (line 1977)
+zh Duplicate key: readFailed (line 1978)
+zh Duplicate key: loadHistoryFailed (line 1979)
+zh Duplicate key: loadingUserData (line 1980)
+zh Duplicate key: errorMessage (line 1981)
+zh Duplicate key: welcomeMessage (line 1982)
+zh Duplicate key: selectKnowledgeGroup (line 1983)
+zh Duplicate key: allKnowledgeGroups (line 1984)
+zh Duplicate key: unknownGroup (line 1985)
+zh Duplicate key: selectedGroupsCount (line 1986)
+zh Duplicate key: generalSettings (line 1989)
+zh Duplicate key: modelManagement (line 1990)
+zh Duplicate key: languageSettings (line 1991)
+zh Duplicate key: passwordChangeSuccess (line 1992)
+zh Duplicate key: passwordChangeFailed (line 1993)
+zh Duplicate key: create (line 1994)
+zh Duplicate key: validationFailedMsg (line 1995)
+zh Duplicate key: navChat (line 1999)
+zh Duplicate key: navCoach (line 2000)
+zh Duplicate key: navKnowledge (line 2001)
+zh Duplicate key: navKnowledgeGroups (line 2002)
+zh Duplicate key: navCrawler (line 2003)
+zh Duplicate key: expandMenu (line 2004)
+zh Duplicate key: switchLanguage (line 2005)
+zh Duplicate key: selectKnowledgeGroups (line 2008)
+zh Duplicate key: searchGroupsPlaceholder (line 2009)
+zh Duplicate key: done (line 2010)
+zh Duplicate key: all (line 2011)
+zh Duplicate key: noGroupsFound (line 2012)
+zh Duplicate key: noGroups (line 2013)
+zh Duplicate key: autoRefresh (line 2016)
+zh Duplicate key: refreshInterval (line 2017)
+zh Duplicate key: errorRenderFlowchart (line 2020)
+zh Duplicate key: errorLoadData (line 2021)
+zh Duplicate key: confirmUnsupportedFile (line 2022)
+zh Duplicate key: errorReadFile (line 2023)
+zh Duplicate key: successUploadFile (line 2024)
+zh Duplicate key: errorUploadFile (line 2025)
+zh Duplicate key: errorProcessFile (line 2026)
+zh Duplicate key: errorTitleContentRequired (line 2027)
+zh Duplicate key: successNoteUpdated (line 2028)
+zh Duplicate key: successNoteCreated (line 2029)
+zh Duplicate key: errorSaveFailed (line 2030)
+zh Duplicate key: confirmDeleteNote (line 2031)
+zh Duplicate key: successNoteDeleted (line 2032)
+zh Duplicate key: confirmRemoveFileFromGroup (line 2033)
+zh Duplicate key: editNote (line 2034)
+zh Duplicate key: newNote (line 2035)
+zh Duplicate key: togglePreviewOpen (line 2036)
+zh Duplicate key: togglePreviewClose (line 2037)
+zh Duplicate key: noteTitlePlaceholder (line 2038)
+zh Duplicate key: noteContentPlaceholder (line 2039)
+zh Duplicate key: markdownPreviewArea (line 2040)
+zh Duplicate key: back (line 2041)
+zh Duplicate key: chatWithGroup (line 2042)
+zh Duplicate key: chatWithFile (line 2043)
+zh Duplicate key: filesCountLabel (line 2044)
+zh Duplicate key: notesCountLabel (line 2045)
+zh Duplicate key: indexIntoKB (line 2046)
+zh Duplicate key: noFilesOrNotes (line 2047)
+zh Duplicate key: importFolder (line 2048)
+zh Duplicate key: createPDFNote (line 2051)
+zh Duplicate key: screenshotPreview (line 2052)
+zh Duplicate key: associateKnowledgeGroup (line 2053)
+zh Duplicate key: globalNoSpecificGroup (line 2054)
+zh Duplicate key: title (line 2055)
+zh Duplicate key: enterNoteTitle (line 2056)
+zh Duplicate key: contentOCR (line 2057)
+zh Duplicate key: extractingText (line 2058)
+zh Duplicate key: analyzingImage (line 2059)
+zh Duplicate key: noTextExtracted (line 2060)
+zh Duplicate key: saveNote (line 2061)
+zh Duplicate key: page (line 2064)
+zh Duplicate key: placeholderText (line 2065)
+zh Duplicate key: createNewNotebook (line 2068)
+zh Duplicate key: nameField (line 2069)
+zh Duplicate key: required (line 2070)
+zh Duplicate key: exampleResearch (line 2071)
+zh Duplicate key: shortDescriptionField (line 2072)
+zh Duplicate key: describePurpose (line 2073)
+zh Duplicate key: detailedIntroField (line 2074)
+zh Duplicate key: provideBackgroundInfo (line 2075)
+zh Duplicate key: creationFailed (line 2076)
+zh Duplicate key: preparingPDFConversion (line 2079)
+zh Duplicate key: pleaseWait (line 2080)
+zh Duplicate key: convertingPDF (line 2081)
+zh Duplicate key: pdfConversionFailed (line 2082)
+zh Duplicate key: pdfConversionError (line 2083)
+zh Duplicate key: pdfLoadFailed (line 2084)
+zh Duplicate key: pdfLoadError (line 2085)
+zh Duplicate key: downloadingPDF (line 2086)
+zh Duplicate key: loadingPDF (line 2087)
+zh Duplicate key: zoomOut (line 2088)
+zh Duplicate key: zoomIn (line 2089)
+zh Duplicate key: resetZoom (line 2090)
+zh Duplicate key: selectPageNumber (line 2091)
+zh Duplicate key: enterPageNumber (line 2092)
+zh Duplicate key: exitSelectionMode (line 2093)
+zh Duplicate key: clickToSelectAndNote (line 2094)
+zh Duplicate key: regeneratePDF (line 2095)
+zh Duplicate key: downloadPDF (line 2096)
+zh Duplicate key: openInNewWindow (line 2097)
+zh Duplicate key: exitFullscreen (line 2098)
+zh Duplicate key: fullscreenDisplay (line 2099)
+zh Duplicate key: pdfPreview (line 2100)
+zh Duplicate key: converting (line 2101)
+zh Duplicate key: generatePDFPreview (line 2102)
+zh Duplicate key: previewNotSupported (line 2103)
+zh Duplicate key: confirmRegeneratePDF (line 2106)
+zh Duplicate key: pdfPreviewReady (line 2109)
+zh Duplicate key: convertingInProgress (line 2110)
+zh Duplicate key: conversionFailed (line 2111)
+zh Duplicate key: generatePDFPreviewButton (line 2112)
+zh Duplicate key: checkPDFStatusFailed (line 2115)
+zh Duplicate key: requestRegenerationFailed (line 2116)
+zh Duplicate key: downloadPDFFailed (line 2117)
+zh Duplicate key: openPDFInNewTabFailed (line 2118)
+zh Duplicate key: invalidFile (line 2121)
+zh Duplicate key: incompleteFileInfo (line 2122)
+zh Duplicate key: unsupportedFileFormat (line 2123)
+zh Duplicate key: willUseFastMode (line 2124)
+zh Duplicate key: formatNoPrecise (line 2125)
+zh Duplicate key: smallFileFastOk (line 2126)
+zh Duplicate key: mixedContentPreciseRecommended (line 2127)
+zh Duplicate key: willIncurApiCost (line 2128)
+zh Duplicate key: largeFilePreciseRecommended (line 2129)
+zh Duplicate key: longProcessingTime (line 2130)
+zh Duplicate key: highApiCost (line 2131)
+zh Duplicate key: considerFileSplitting (line 2132)
+zh Duplicate key: dragDropUploadTitle (line 2135)
+zh Duplicate key: dragDropUploadDesc (line 2136)
+zh Duplicate key: supportedFormats (line 2137)
+zh Duplicate key: browseFiles (line 2138)
+zh Duplicate key: recommendationMsg (line 2141)
+zh Duplicate key: autoAdjustChunk (line 2142)
+zh Duplicate key: autoAdjustOverlap (line 2143)
+zh Duplicate key: autoAdjustOverlapMin (line 2144)
+zh Duplicate key: loadLimitsFailed (line 2145)
+zh Duplicate key: maxValueMsg (line 2146)
+zh Duplicate key: overlapRatioLimit (line 2147)
+zh Duplicate key: onlyAdminCanModify (line 2148)
+zh Duplicate key: dragToSelect (line 2149)
+zh Duplicate key: fillTargetName (line 2152)
+zh Duplicate key: submitFailed (line 2153)
+zh Duplicate key: importFolderTitle (line 2154)
+zh Duplicate key: importFolderTip (line 2155)
+zh Duplicate key: lblTargetGroup (line 2156)
+zh Duplicate key: placeholderNewGroup (line 2157)
+zh Duplicate key: importToCurrentGroup (line 2158)
+zh Duplicate key: nextStep (line 2159)
+zh Duplicate key: lblImportSource (line 2160)
+zh Duplicate key: serverPath (line 2161)
+zh Duplicate key: localFolder (line 2162)
+zh Duplicate key: selectedFilesCount (line 2163)
+zh Duplicate key: clickToSelectFolder (line 2164)
+zh Duplicate key: selectFolderTip (line 2165)
+zh Duplicate key: importComplete (line 2166)
+zh Duplicate key: importedFromLocalFolder (line 2167)
+zh Duplicate key: historyTitle (line 2170)
+zh Duplicate key: confirmDeleteHistory (line 2171)
+zh Duplicate key: deleteHistorySuccess (line 2172)
+zh Duplicate key: deleteHistoryFailed (line 2173)
+zh Duplicate key: yesterday (line 2174)
+zh Duplicate key: daysAgo (line 2175)
+zh Duplicate key: historyMessages (line 2176)
+zh Duplicate key: noHistory (line 2177)
+zh Duplicate key: noHistoryDesc (line 2178)
+zh Duplicate key: loadMore (line 2179)
+zh Duplicate key: loadingHistoriesFailed (line 2180)
+zh Duplicate key: supportedFormatsInfo (line 2181)
+zh Duplicate key: navCatalog (line 2184)
+zh Duplicate key: allDocuments (line 2185)
+zh Duplicate key: uncategorized (line 2186)
+zh Duplicate key: categories (line 2187)
+zh Duplicate key: uncategorizedFiles (line 2188)
+zh Duplicate key: category (line 2189)
+zh Duplicate key: statusReadyDesc (line 2190)
+zh Duplicate key: statusIndexingDesc (line 2191)
+zh Duplicate key: selectCategory (line 2192)
+zh Duplicate key: noneUncategorized (line 2193)
+zh Duplicate key: previous (line 2194)
+zh Duplicate key: next (line 2195)
+zh Duplicate key: editCategory (line 2196)
+zh Duplicate key: createCategory (line 2197)
+zh Duplicate key: categoryDesc (line 2198)
+zh Duplicate key: categoryName (line 2199)
+zh Duplicate key: saveChanges (line 2200)
+zh Duplicate key: createCategoryBtn (line 2201)
+zh Duplicate key: totalTenants (line 2204)
+zh Duplicate key: systemUsers (line 2205)
+zh Duplicate key: systemHealth (line 2206)
+zh Duplicate key: operational (line 2207)
+zh Duplicate key: orgManagement (line 2208)
+zh Duplicate key: globalTenantControl (line 2209)
+zh Duplicate key: newTenant (line 2210)
+zh Duplicate key: tenantName (line 2211)
+zh Duplicate key: domainOptional (line 2212)
+zh Duplicate key: assignInitialAdmin (line 2213)
+zh Duplicate key: selectUserOptional (line 2214)
+zh Duplicate key: promoteToAdminWarning (line 2215)
+zh Duplicate key: editOrganization (line 2216)
+zh Duplicate key: createOrganization (line 2217)
+zh Duplicate key: bindAdmin (line 2218)
+zh Duplicate key: manageMembers (line 2219)
+zh Duplicate key: currentMembers (line 2220)
+zh Duplicate key: addMembers (line 2221)
+zh Duplicate key: systemRestricted (line 2222)
+zh Duplicate key: noUnassignedUsers (line 2223)
+zh Duplicate key: enableNotebook (line 2224)
+zh Duplicate key: disableNotebook (line 2225)
+zh Duplicate key: featureUpdated (line 2226)
+zh Duplicate key: editUserRole (line 2229)
+zh Duplicate key: targetRole (line 2230)
+zh Duplicate key: changeUserPassword (line 2231)
+zh Duplicate key: enterNewPassword (line 2232)
+zh Duplicate key: confirmDeleteUser (line 2233)
+zh Duplicate key: userDeletedSuccessfully (line 2234)
+zh Duplicate key: defaultBadge (line 2235)
+zh Duplicate key: kbSettingsSaved (line 2238)
+zh Duplicate key: failedToSaveSettings (line 2239)
+zh Duplicate key: modelConfiguration (line 2240)
+zh Duplicate key: defaultLLMModel (line 2241)
+zh Duplicate key: selectLLM (line 2242)
+zh Duplicate key: embeddingModel (line 2243)
+zh Duplicate key: selectEmbedding (line 2244)
+zh Duplicate key: rerankModel (line 2245)
+zh Duplicate key: none (line 2246)
+zh Duplicate key: indexingChunkingConfig (line 2247)
+zh Duplicate key: chatHyperparameters (line 2248)
+zh Duplicate key: precise (line 2249)
+zh Duplicate key: creative (line 2250)
+zh Duplicate key: maxResponseTokens (line 2251)
+zh Duplicate key: retrievalSearchSettings (line 2252)
+zh Duplicate key: enableHybridSearch (line 2253)
+zh Duplicate key: hybridSearchDesc (line 2254)
+zh Duplicate key: hybridWeight (line 2255)
+zh Duplicate key: pureText (line 2256)
+zh Duplicate key: pureVector (line 2257)
+zh Duplicate key: enableQueryExpansion (line 2258)
+zh Duplicate key: queryExpansionDesc (line 2259)
+zh Duplicate key: enableHyDE (line 2260)
+zh Duplicate key: hydeDesc (line 2261)
+zh Duplicate key: enableReranking (line 2262)
+zh Duplicate key: rerankingDesc (line 2263)
+zh Duplicate key: importFolder (line 2266)
+zh Duplicate key: newGroup (line 2267)
+zh Duplicate key: noKnowledgeGroups (line 2268)
+zh Duplicate key: createGroupDesc (line 2269)
+zh Duplicate key: noDescriptionProvided (line 2270)
+zh Duplicate key: files (line 2271)
+zh Duplicate key: filterGroupFiles (line 2272)
+zh Duplicate key: browseManageFiles (line 2273)
+zh Duplicate key: navAgent (line 2276)
+zh Duplicate key: agentTitle (line 2277)
+zh Duplicate key: agentDesc (line 2278)
+zh Duplicate key: searchAgent (line 2279)
+zh Duplicate key: createAgent (line 2280)
+zh Duplicate key: statusRunning (line 2281)
+zh Duplicate key: statusStopped (line 2282)
+zh Duplicate key: btnChat (line 2283)
+zh Duplicate key: navPlugin (line 2286)
+zh Duplicate key: pluginTitle (line 2287)
+zh Duplicate key: pluginDesc (line 2288)
+zh Duplicate key: searchPlugin (line 2289)
+zh Duplicate key: installPlugin (line 2290)
+zh Duplicate key: installedPlugin (line 2291)
+zh Duplicate key: updatePlugin (line 2292)
+zh Duplicate key: selectOrganization (line 2295)
+zh Duplicate key: defaultTenant (line 2296)
+zh Duplicate key: roleTenantAdmin (line 2297)
+zh Duplicate key: roleRegularUser (line 2298)
+zh Duplicate key: creatingRegularUser (line 2299)
+zh Duplicate key: defaultBadge (line 2300)
+zh Duplicate key: defaultSettingFailed (line 2301)
+zh Duplicate key: pluginOfficial (line 2302)
+zh Duplicate key: pluginCommunity (line 2303)
+zh Duplicate key: pluginBy (line 2304)
+zh Duplicate key: pluginConfig (line 2305)
+zh Duplicate key: updatedAtPrefix (line 2306)
+zh Duplicate key: plugin1Name (line 2307)
+zh Duplicate key: plugin1Desc (line 2308)
+zh Duplicate key: plugin2Name (line 2309)
+zh Duplicate key: plugin2Desc (line 2310)
+zh Duplicate key: plugin3Name (line 2311)
+zh Duplicate key: plugin3Desc (line 2312)
+zh Duplicate key: plugin4Name (line 2313)
+zh Duplicate key: plugin4Desc (line 2314)
+zh Duplicate key: plugin5Name (line 2315)
+zh Duplicate key: plugin5Desc (line 2316)
+zh Duplicate key: plugin6Name (line 2317)
+zh Duplicate key: plugin6Desc (line 2318)
+zh Duplicate key: agent1Name (line 2319)
+zh Duplicate key: agent1Desc (line 2320)
+zh Duplicate key: agent1Time (line 2321)
+zh Duplicate key: agent2Name (line 2322)
+zh Duplicate key: agent2Desc (line 2323)
+zh Duplicate key: agent2Time (line 2324)
+zh Duplicate key: agent3Name (line 2325)
+zh Duplicate key: agent3Desc (line 2326)
+zh Duplicate key: agent3Time (line 2327)
+zh Duplicate key: agent4Name (line 2328)
+zh Duplicate key: agent4Desc (line 2329)
+zh Duplicate key: agent4Time (line 2330)
+zh Duplicate key: agent5Name (line 2331)
+zh Duplicate key: agent5Desc (line 2332)
+zh Duplicate key: agent5Time (line 2333)
+zh Duplicate key: agent6Name (line 2334)
+zh Duplicate key: agent6Desc (line 2335)
+zh Duplicate key: agent6Time (line 2336)
+zh Duplicate key: agent7Name (line 2337)
+zh Duplicate key: agent7Desc (line 2338)
+zh Duplicate key: agent7Time (line 2339)
+zh Duplicate key: generalSettingsSubtitle (line 2340)
+zh Duplicate key: userManagementSubtitle (line 2341)
+zh Duplicate key: modelManagementSubtitle (line 2342)
+zh Duplicate key: kbSettingsSubtitle (line 2343)
+zh Duplicate key: tenantsSubtitle (line 2344)
+zh Duplicate key: allNotes (line 2346)
+zh Duplicate key: filterNotesPlaceholder (line 2347)
+zh Duplicate key: noteTitlePlaceholder (line 2348)
+zh Duplicate key: startWritingPlaceholder (line 2349)
+zh Duplicate key: previewHeader (line 2350)
+zh Duplicate key: noContentToPreview (line 2351)
+zh Duplicate key: hidePreview (line 2352)
+zh Duplicate key: showPreview (line 2353)
+zh Duplicate key: directoryLabel (line 2354)
+zh Duplicate key: uncategorized (line 2355)
+zh Duplicate key: enterNamePlaceholder (line 2356)
+zh Duplicate key: subFolderPlaceholder (line 2357)
+zh Duplicate key: categoryCreated (line 2358)
+zh Duplicate key: failedToCreateCategory (line 2359)
+zh Duplicate key: failedToDeleteCategory (line 2360)
+zh Duplicate key: confirmDeleteCategory (line 2361)
+en Duplicate key: embeddingModel (line 1449)
+en Duplicate key: chunkSize (line 1471)
+en Duplicate key: chunkOverlap (line 1472)
+en Duplicate key: rerankSimilarityThreshold (line 1475)
+en Duplicate key: importFolder (line 1490)
+en Duplicate key: files (line 1495)
+en Duplicate key: roleTenantAdmin (line 1521)
+en Duplicate key: roleRegularUser (line 1522)
+en Duplicate key: creatingRegularUser (line 1523)
+en Duplicate key: defaultBadge (line 1526)
+en Duplicate key: noteTitlePlaceholder (line 1574)
+en Duplicate key: uncategorized (line 1581)
+en Duplicate key: aiCommandsError (line 1590)
+en Duplicate key: appTitle (line 1591)
+en Duplicate key: loginTitle (line 1592)
+en Duplicate key: loginDesc (line 1593)
+en Duplicate key: loginButton (line 1594)
+en Duplicate key: loginError (line 1595)
+en Duplicate key: unknownError (line 1596)
+en Duplicate key: usernamePlaceholder (line 1597)
+en Duplicate key: passwordPlaceholder (line 1598)
+en Duplicate key: registerButton (line 1599)
+en Duplicate key: langZh (line 1600)
+en Duplicate key: langEn (line 1601)
+en Duplicate key: langJa (line 1602)
+en Duplicate key: confirm (line 1603)
+en Duplicate key: cancel (line 1604)
+en Duplicate key: confirmTitle (line 1605)
+en Duplicate key: confirmDeleteGroup (line 1606)
+en Duplicate key: sidebarTitle (line 1608)
+en Duplicate key: backToWorkspace (line 1609)
+en Duplicate key: goToAdmin (line 1610)
+en Duplicate key: sidebarDesc (line 1611)
+en Duplicate key: tabFiles (line 1612)
+en Duplicate key: files (line 1613)
+en Duplicate key: notes (line 1614)
+en Duplicate key: tabSettings (line 1615)
+en Duplicate key: systemConfiguration (line 1616)
+en Duplicate key: noFiles (line 1617)
+en Duplicate key: noFilesDesc (line 1618)
+en Duplicate key: addFile (line 1619)
+en Duplicate key: clearAll (line 1620)
+en Duplicate key: uploading (line 1621)
+en Duplicate key: statusIndexing (line 1622)
+en Duplicate key: statusReady (line 1623)
+en Duplicate key: ragSettings (line 1626)
+en Duplicate key: enableRerank (line 1627)
+en Duplicate key: enableRerankDesc (line 1628)
+en Duplicate key: selectRerankModel (line 1629)
+en Duplicate key: selectModelPlaceholder (line 1630)
+en Duplicate key: headerModelSelection (line 1632)
+en Duplicate key: headerHyperparams (line 1633)
+en Duplicate key: headerIndexing (line 1634)
+en Duplicate key: headerRetrieval (line 1635)
+en Duplicate key: btnManageModels (line 1636)
+en Duplicate key: lblLLM (line 1638)
+en Duplicate key: lblEmbedding (line 1639)
+en Duplicate key: lblRerankRef (line 1640)
+en Duplicate key: lblTemperature (line 1641)
+en Duplicate key: lblMaxTokens (line 1642)
+en Duplicate key: lblChunkSize (line 1643)
+en Duplicate key: lblChunkOverlap (line 1644)
+en Duplicate key: lblTopK (line 1645)
+en Duplicate key: lblRerank (line 1646)
+en Duplicate key: idxModalTitle (line 1648)
+en Duplicate key: idxDesc (line 1649)
+en Duplicate key: idxFiles (line 1650)
+en Duplicate key: idxMethod (line 1651)
+en Duplicate key: idxEmbeddingModel (line 1652)
+en Duplicate key: idxStart (line 1653)
+en Duplicate key: idxCancel (line 1654)
+en Duplicate key: idxAuto (line 1655)
+en Duplicate key: idxCustom (line 1656)
+en Duplicate key: mmTitle (line 1658)
+en Duplicate key: mmAddBtn (line 1659)
+en Duplicate key: mmEdit (line 1660)
+en Duplicate key: mmDelete (line 1661)
+en Duplicate key: mmEmpty (line 1662)
+en Duplicate key: mmFormName (line 1663)
+en Duplicate key: mmFormProvider (line 1664)
+en Duplicate key: mmFormModelId (line 1665)
+en Duplicate key: mmFormBaseUrl (line 1666)
+en Duplicate key: mmFormType (line 1667)
+en Duplicate key: mmFormVision (line 1668)
+en Duplicate key: mmFormDimensions (line 1669)
+en Duplicate key: mmFormDimensionsHelp (line 1670)
+en Duplicate key: mmSave (line 1671)
+en Duplicate key: mmCancel (line 1672)
+en Duplicate key: mmErrorNotAuthenticated (line 1673)
+en Duplicate key: mmErrorTitle (line 1674)
+en Duplicate key: modelEnabled (line 1675)
+en Duplicate key: modelDisabled (line 1676)
+en Duplicate key: confirmChangeEmbeddingModel (line 1677)
+en Duplicate key: embeddingModelWarning (line 1678)
+en Duplicate key: sourcePreview (line 1679)
+en Duplicate key: matchScore (line 1680)
+en Duplicate key: copyContent (line 1681)
+en Duplicate key: copySuccess (line 1682)
+en Duplicate key: selectLLMModel (line 1685)
+en Duplicate key: selectEmbeddingModel (line 1686)
+en Duplicate key: defaultForUploads (line 1687)
+en Duplicate key: noRerankModel (line 1688)
+en Duplicate key: vectorSimilarityThreshold (line 1689)
+en Duplicate key: rerankSimilarityThreshold (line 1690)
+en Duplicate key: filterLowResults (line 1691)
+en Duplicate key: noteCreatedSuccess (line 1692)
+en Duplicate key: noteCreatedFailed (line 1693)
+en Duplicate key: fullTextSearch (line 1694)
+en Duplicate key: hybridVectorWeight (line 1695)
+en Duplicate key: hybridVectorWeightDesc (line 1696)
+en Duplicate key: lblQueryExpansion (line 1697)
+en Duplicate key: lblHyDE (line 1698)
+en Duplicate key: lblQueryExpansionDesc (line 1699)
+en Duplicate key: lblHyDEDesc (line 1700)
+en Duplicate key: apiKeyValidationFailed (line 1702)
+en Duplicate key: keepOriginalKey (line 1703)
+en Duplicate key: leaveEmptyNoChange (line 1704)
+en Duplicate key: mmFormApiKey (line 1705)
+en Duplicate key: mmFormApiKeyPlaceholder (line 1706)
+en Duplicate key: reconfigureFile (line 1709)
+en Duplicate key: modifySettings (line 1710)
+en Duplicate key: filesCount (line 1711)
+en Duplicate key: allFilesIndexed (line 1712)
+en Duplicate key: noEmbeddingModels (line 1713)
+en Duplicate key: reconfigure (line 1714)
+en Duplicate key: refresh (line 1715)
+en Duplicate key: settings (line 1716)
+en Duplicate key: needLogin (line 1717)
+en Duplicate key: citationSources (line 1718)
+en Duplicate key: chunkNumber (line 1719)
+en Duplicate key: getUserListFailed (line 1720)
+en Duplicate key: usernamePasswordRequired (line 1721)
+en Duplicate key: passwordMinLength (line 1722)
+en Duplicate key: userCreatedSuccess (line 1723)
+en Duplicate key: createUserFailed (line 1724)
+en Duplicate key: userPromotedToAdmin (line 1725)
+en Duplicate key: userDemotedFromAdmin (line 1726)
+en Duplicate key: updateUserFailed (line 1727)
+en Duplicate key: confirmDeleteUser (line 1728)
+en Duplicate key: deleteUser (line 1729)
+en Duplicate key: deleteUserFailed (line 1730)
+en Duplicate key: userDeletedSuccessfully (line 1731)
+en Duplicate key: makeUserAdmin (line 1732)
+en Duplicate key: makeUserRegular (line 1733)
+en Duplicate key: loading (line 1734)
+en Duplicate key: noUsers (line 1735)
+en Duplicate key: aiAssistant (line 1738)
+en Duplicate key: polishContent (line 1739)
+en Duplicate key: expandContent (line 1740)
+en Duplicate key: summarizeContent (line 1741)
+en Duplicate key: translateToEnglish (line 1742)
+en Duplicate key: fixGrammar (line 1743)
+en Duplicate key: aiCommandInstructPolish (line 1744)
+en Duplicate key: aiCommandInstructExpand (line 1745)
+en Duplicate key: aiCommandInstructSummarize (line 1746)
+en Duplicate key: aiCommandInstructTranslateToEn (line 1747)
+en Duplicate key: aiCommandInstructFixGrammar (line 1748)
+en Duplicate key: aiCommandsPreset (line 1749)
+en Duplicate key: aiCommandsCustom (line 1750)
+en Duplicate key: aiCommandsCustomPlaceholder (line 1751)
+en Duplicate key: aiCommandsReferenceContext (line 1752)
+en Duplicate key: aiCommandsStartGeneration (line 1753)
+en Duplicate key: aiCommandsResult (line 1754)
+en Duplicate key: aiCommandsGenerating (line 1755)
+en Duplicate key: aiCommandsApplyResult (line 1756)
+en Duplicate key: aiCommandsGoBack (line 1757)
+en Duplicate key: aiCommandsReset (line 1758)
+en Duplicate key: aiCommandsModalPreset (line 1759)
+en Duplicate key: aiCommandsModalCustom (line 1760)
+en Duplicate key: aiCommandsModalCustomPlaceholder (line 1761)
+en Duplicate key: aiCommandsModalBasedOnSelection (line 1762)
+en Duplicate key: aiCommandsModalResult (line 1763)
+en Duplicate key: aiCommandsModalApply (line 1764)
+en Duplicate key: fillAllFields (line 1767)
+en Duplicate key: passwordMismatch (line 1768)
+en Duplicate key: newPasswordMinLength (line 1769)
+en Duplicate key: changePasswordFailed (line 1770)
+en Duplicate key: changePasswordTitle (line 1771)
+en Duplicate key: changing (line 1772)
+en Duplicate key: searchResults (line 1773)
+en Duplicate key: visionModelSettings (line 1776)
+en Duplicate key: defaultVisionModel (line 1777)
+en Duplicate key: loadVisionModelFailed (line 1778)
+en Duplicate key: loadFailed (line 1779)
+en Duplicate key: saveVisionModelFailed (line 1780)
+en Duplicate key: noVisionModels (line 1781)
+en Duplicate key: selectVisionModel (line 1782)
+en Duplicate key: visionModelHelp (line 1783)
+en Duplicate key: mmErrorNameRequired (line 1784)
+en Duplicate key: mmErrorModelIdRequired (line 1785)
+en Duplicate key: mmErrorBaseUrlRequired (line 1786)
+en Duplicate key: mmRequiredAsterisk (line 1787)
+en Duplicate key: typeLLM (line 1789)
+en Duplicate key: typeEmbedding (line 1790)
+en Duplicate key: typeRerank (line 1791)
+en Duplicate key: typeVision (line 1792)
+en Duplicate key: welcome (line 1794)
+en Duplicate key: placeholderWithFiles (line 1795)
+en Duplicate key: placeholderEmpty (line 1796)
+en Duplicate key: analyzing (line 1797)
+en Duplicate key: errorGeneric (line 1798)
+en Duplicate key: errorLabel (line 1799)
+en Duplicate key: errorNoModel (line 1800)
+en Duplicate key: aiDisclaimer (line 1801)
+en Duplicate key: confirmClear (line 1802)
+en Duplicate key: removeFile (line 1803)
+en Duplicate key: apiError (line 1804)
+en Duplicate key: geminiError (line 1805)
+en Duplicate key: processedButNoText (line 1806)
+en Duplicate key: unitByte (line 1807)
+en Duplicate key: readingFailed (line 1808)
+en Duplicate key: copy (line 1810)
+en Duplicate key: copied (line 1811)
+en Duplicate key: logout (line 1814)
+en Duplicate key: changePassword (line 1815)
+en Duplicate key: userManagement (line 1816)
+en Duplicate key: userList (line 1817)
+en Duplicate key: addUser (line 1818)
+en Duplicate key: username (line 1819)
+en Duplicate key: password (line 1820)
+en Duplicate key: confirmPassword (line 1821)
+en Duplicate key: currentPassword (line 1822)
+en Duplicate key: newPassword (line 1823)
+en Duplicate key: createUser (line 1824)
+en Duplicate key: admin (line 1825)
+en Duplicate key: user (line 1826)
+en Duplicate key: adminUser (line 1827)
+en Duplicate key: confirmChange (line 1828)
+en Duplicate key: changeUserPassword (line 1829)
+en Duplicate key: enterNewPassword (line 1830)
+en Duplicate key: createdAt (line 1831)
+en Duplicate key: newChat (line 1832)
+en Duplicate key: kbManagement (line 1835)
+en Duplicate key: kbManagementDesc (line 1836)
+en Duplicate key: searchPlaceholder (line 1837)
+en Duplicate key: allGroups (line 1838)
+en Duplicate key: allStatus (line 1839)
+en Duplicate key: statusReadyFragment (line 1840)
+en Duplicate key: statusFailedFragment (line 1841)
+en Duplicate key: statusIndexingFragment (line 1842)
+en Duplicate key: uploadFile (line 1843)
+en Duplicate key: fileName (line 1844)
+en Duplicate key: size (line 1845)
+en Duplicate key: status (line 1846)
+en Duplicate key: groups (line 1847)
+en Duplicate key: actions (line 1848)
+en Duplicate key: groupsActions (line 1849)
+en Duplicate key: noFilesFound (line 1850)
+en Duplicate key: showingRange (line 1851)
+en Duplicate key: confirmDeleteFile (line 1852)
+en Duplicate key: fileDeleted (line 1853)
+en Duplicate key: deleteFailed (line 1854)
+en Duplicate key: fileAddedToGroup (line 1855)
+en Duplicate key: failedToAddToGroup (line 1856)
+en Duplicate key: fileRemovedFromGroup (line 1857)
+en Duplicate key: failedToRemoveFromGroup (line 1858)
+en Duplicate key: confirmClearKB (line 1859)
+en Duplicate key: kbCleared (line 1860)
+en Duplicate key: clearFailed (line 1861)
+en Duplicate key: actionFailed (line 1862)
+en Duplicate key: groupCreated (line 1863)
+en Duplicate key: groupUpdated (line 1864)
+en Duplicate key: groupDeleted (line 1865)
+en Duplicate key: groupManagement (line 1866)
+en Duplicate key: loginRequired (line 1867)
+en Duplicate key: uploadErrors (line 1868)
+en Duplicate key: uploadWarning (line 1869)
+en Duplicate key: uploadFailed (line 1870)
+en Duplicate key: preview (line 1871)
+en Duplicate key: addGroup (line 1872)
+en Duplicate key: delete (line 1873)
+en Duplicate key: retry (line 1874)
+en Duplicate key: retrying (line 1875)
+en Duplicate key: retrySuccess (line 1876)
+en Duplicate key: retryFailed (line 1877)
+en Duplicate key: chunkInfo (line 1878)
+en Duplicate key: totalChunks (line 1879)
+en Duplicate key: chunkIndex (line 1880)
+en Duplicate key: contentLength (line 1881)
+en Duplicate key: position (line 1882)
+en Duplicate key: reconfigureTitle (line 1885)
+en Duplicate key: reconfigureDesc (line 1886)
+en Duplicate key: indexingConfigTitle (line 1887)
+en Duplicate key: indexingConfigDesc (line 1888)
+en Duplicate key: pendingFiles (line 1889)
+en Duplicate key: processingMode (line 1890)
+en Duplicate key: analyzingFile (line 1891)
+en Duplicate key: recommendationReason (line 1892)
+en Duplicate key: fastMode (line 1893)
+en Duplicate key: fastModeDesc (line 1894)
+en Duplicate key: preciseMode (line 1895)
+en Duplicate key: preciseModeDesc (line 1896)
+en Duplicate key: fastModeFeatures (line 1897)
+en Duplicate key: fastFeature1 (line 1898)
+en Duplicate key: fastFeature2 (line 1899)
+en Duplicate key: fastFeature3 (line 1900)
+en Duplicate key: fastFeature4 (line 1901)
+en Duplicate key: fastFeature5 (line 1902)
+en Duplicate key: preciseModeFeatures (line 1903)
+en Duplicate key: preciseFeature1 (line 1904)
+en Duplicate key: preciseFeature2 (line 1905)
+en Duplicate key: preciseFeature3 (line 1906)
+en Duplicate key: preciseFeature4 (line 1907)
+en Duplicate key: preciseFeature5 (line 1908)
+en Duplicate key: preciseFeature6 (line 1909)
+en Duplicate key: embeddingModel (line 1910)
+en Duplicate key: pleaseSelect (line 1911)
+en Duplicate key: pleaseSelectKnowledgeGroupFirst (line 1912)
+en Duplicate key: selectUnassignGroupWarning (line 1913)
+en Duplicate key: chunkConfig (line 1914)
+en Duplicate key: chunkSize (line 1915)
+en Duplicate key: min (line 1916)
+en Duplicate key: max (line 1917)
+en Duplicate key: chunkOverlap (line 1918)
+en Duplicate key: modelLimitsInfo (line 1919)
+en Duplicate key: model (line 1920)
+en Duplicate key: maxChunkSize (line 1921)
+en Duplicate key: maxOverlapSize (line 1922)
+en Duplicate key: maxBatchSize (line 1923)
+en Duplicate key: envLimitWeaker (line 1924)
+en Duplicate key: optimizationTips (line 1925)
+en Duplicate key: tipChunkTooLarge (line 1926)
+en Duplicate key: tipOverlapSmall (line 1927)
+en Duplicate key: tipMaxValues (line 1928)
+en Duplicate key: tipPreciseCost (line 1929)
+en Duplicate key: selectEmbeddingFirst (line 1930)
+en Duplicate key: confirmPreciseCost (line 1931)
+en Duplicate key: startProcessing (line 1932)
+en Duplicate key: notebooks (line 1935)
+en Duplicate key: notebooksDesc (line 1936)
+en Duplicate key: createNotebook (line 1937)
+en Duplicate key: chatWithNotebook (line 1938)
+en Duplicate key: editNotebook (line 1939)
+en Duplicate key: deleteNotebook (line 1940)
+en Duplicate key: noDescription (line 1941)
+en Duplicate key: hasIntro (line 1942)
+en Duplicate key: noIntro (line 1943)
+en Duplicate key: noNotebooks (line 1944)
+en Duplicate key: createFailed (line 1945)
+en Duplicate key: confirmDeleteNotebook (line 1946)
+en Duplicate key: errorFileTooLarge (line 1949)
+en Duplicate key: noFilesYet (line 1950)
+en Duplicate key: createNotebookTitle (line 1953)
+en Duplicate key: editNotebookTitle (line 1954)
+en Duplicate key: createFailedRetry (line 1955)
+en Duplicate key: updateFailedRetry (line 1956)
+en Duplicate key: name (line 1957)
+en Duplicate key: nameHelp (line 1958)
+en Duplicate key: namePlaceholder (line 1959)
+en Duplicate key: shortDescription (line 1960)
+en Duplicate key: descPlaceholder (line 1961)
+en Duplicate key: detailedIntro (line 1962)
+en Duplicate key: introPlaceholder (line 1963)
+en Duplicate key: introHelp (line 1964)
+en Duplicate key: creating (line 1965)
+en Duplicate key: createNow (line 1966)
+en Duplicate key: saving (line 1967)
+en Duplicate key: save (line 1968)
+en Duplicate key: chatTitle (line 1971)
+en Duplicate key: chatDesc (line 1972)
+en Duplicate key: viewHistory (line 1973)
+en Duplicate key: saveSettingsFailed (line 1974)
+en Duplicate key: loginToUpload (line 1975)
+en Duplicate key: fileSizeLimitExceeded (line 1976)
+en Duplicate key: unsupportedFileType (line 1977)
+en Duplicate key: readFailed (line 1978)
+en Duplicate key: loadHistoryFailed (line 1979)
+en Duplicate key: loadingUserData (line 1980)
+en Duplicate key: errorMessage (line 1981)
+en Duplicate key: welcomeMessage (line 1982)
+en Duplicate key: selectKnowledgeGroup (line 1983)
+en Duplicate key: allKnowledgeGroups (line 1984)
+en Duplicate key: unknownGroup (line 1985)
+en Duplicate key: selectedGroupsCount (line 1986)
+en Duplicate key: generalSettings (line 1989)
+en Duplicate key: modelManagement (line 1990)
+en Duplicate key: languageSettings (line 1991)
+en Duplicate key: passwordChangeSuccess (line 1992)
+en Duplicate key: passwordChangeFailed (line 1993)
+en Duplicate key: create (line 1994)
+en Duplicate key: validationFailedMsg (line 1995)
+en Duplicate key: navChat (line 1999)
+en Duplicate key: navCoach (line 2000)
+en Duplicate key: navKnowledge (line 2001)
+en Duplicate key: navKnowledgeGroups (line 2002)
+en Duplicate key: navCrawler (line 2003)
+en Duplicate key: expandMenu (line 2004)
+en Duplicate key: switchLanguage (line 2005)
+en Duplicate key: selectKnowledgeGroups (line 2008)
+en Duplicate key: searchGroupsPlaceholder (line 2009)
+en Duplicate key: done (line 2010)
+en Duplicate key: all (line 2011)
+en Duplicate key: noGroupsFound (line 2012)
+en Duplicate key: noGroups (line 2013)
+en Duplicate key: autoRefresh (line 2016)
+en Duplicate key: refreshInterval (line 2017)
+en Duplicate key: errorRenderFlowchart (line 2020)
+en Duplicate key: errorLoadData (line 2021)
+en Duplicate key: confirmUnsupportedFile (line 2022)
+en Duplicate key: errorReadFile (line 2023)
+en Duplicate key: successUploadFile (line 2024)
+en Duplicate key: errorUploadFile (line 2025)
+en Duplicate key: errorProcessFile (line 2026)
+en Duplicate key: errorTitleContentRequired (line 2027)
+en Duplicate key: successNoteUpdated (line 2028)
+en Duplicate key: successNoteCreated (line 2029)
+en Duplicate key: errorSaveFailed (line 2030)
+en Duplicate key: confirmDeleteNote (line 2031)
+en Duplicate key: successNoteDeleted (line 2032)
+en Duplicate key: confirmRemoveFileFromGroup (line 2033)
+en Duplicate key: editNote (line 2034)
+en Duplicate key: newNote (line 2035)
+en Duplicate key: togglePreviewOpen (line 2036)
+en Duplicate key: togglePreviewClose (line 2037)
+en Duplicate key: noteTitlePlaceholder (line 2038)
+en Duplicate key: noteContentPlaceholder (line 2039)
+en Duplicate key: markdownPreviewArea (line 2040)
+en Duplicate key: back (line 2041)
+en Duplicate key: chatWithGroup (line 2042)
+en Duplicate key: chatWithFile (line 2043)
+en Duplicate key: filesCountLabel (line 2044)
+en Duplicate key: notesCountLabel (line 2045)
+en Duplicate key: indexIntoKB (line 2046)
+en Duplicate key: noFilesOrNotes (line 2047)
+en Duplicate key: importFolder (line 2048)
+en Duplicate key: createPDFNote (line 2051)
+en Duplicate key: screenshotPreview (line 2052)
+en Duplicate key: associateKnowledgeGroup (line 2053)
+en Duplicate key: globalNoSpecificGroup (line 2054)
+en Duplicate key: title (line 2055)
+en Duplicate key: enterNoteTitle (line 2056)
+en Duplicate key: contentOCR (line 2057)
+en Duplicate key: extractingText (line 2058)
+en Duplicate key: analyzingImage (line 2059)
+en Duplicate key: noTextExtracted (line 2060)
+en Duplicate key: saveNote (line 2061)
+en Duplicate key: page (line 2064)
+en Duplicate key: placeholderText (line 2065)
+en Duplicate key: createNewNotebook (line 2068)
+en Duplicate key: nameField (line 2069)
+en Duplicate key: required (line 2070)
+en Duplicate key: exampleResearch (line 2071)
+en Duplicate key: shortDescriptionField (line 2072)
+en Duplicate key: describePurpose (line 2073)
+en Duplicate key: detailedIntroField (line 2074)
+en Duplicate key: provideBackgroundInfo (line 2075)
+en Duplicate key: creationFailed (line 2076)
+en Duplicate key: preparingPDFConversion (line 2079)
+en Duplicate key: pleaseWait (line 2080)
+en Duplicate key: convertingPDF (line 2081)
+en Duplicate key: pdfConversionFailed (line 2082)
+en Duplicate key: pdfConversionError (line 2083)
+en Duplicate key: pdfLoadFailed (line 2084)
+en Duplicate key: pdfLoadError (line 2085)
+en Duplicate key: downloadingPDF (line 2086)
+en Duplicate key: loadingPDF (line 2087)
+en Duplicate key: zoomOut (line 2088)
+en Duplicate key: zoomIn (line 2089)
+en Duplicate key: resetZoom (line 2090)
+en Duplicate key: selectPageNumber (line 2091)
+en Duplicate key: enterPageNumber (line 2092)
+en Duplicate key: exitSelectionMode (line 2093)
+en Duplicate key: clickToSelectAndNote (line 2094)
+en Duplicate key: regeneratePDF (line 2095)
+en Duplicate key: downloadPDF (line 2096)
+en Duplicate key: openInNewWindow (line 2097)
+en Duplicate key: exitFullscreen (line 2098)
+en Duplicate key: fullscreenDisplay (line 2099)
+en Duplicate key: pdfPreview (line 2100)
+en Duplicate key: converting (line 2101)
+en Duplicate key: generatePDFPreview (line 2102)
+en Duplicate key: previewNotSupported (line 2103)
+en Duplicate key: confirmRegeneratePDF (line 2106)
+en Duplicate key: pdfPreviewReady (line 2109)
+en Duplicate key: convertingInProgress (line 2110)
+en Duplicate key: conversionFailed (line 2111)
+en Duplicate key: generatePDFPreviewButton (line 2112)
+en Duplicate key: checkPDFStatusFailed (line 2115)
+en Duplicate key: requestRegenerationFailed (line 2116)
+en Duplicate key: downloadPDFFailed (line 2117)
+en Duplicate key: openPDFInNewTabFailed (line 2118)
+en Duplicate key: invalidFile (line 2121)
+en Duplicate key: incompleteFileInfo (line 2122)
+en Duplicate key: unsupportedFileFormat (line 2123)
+en Duplicate key: willUseFastMode (line 2124)
+en Duplicate key: formatNoPrecise (line 2125)
+en Duplicate key: smallFileFastOk (line 2126)
+en Duplicate key: mixedContentPreciseRecommended (line 2127)
+en Duplicate key: willIncurApiCost (line 2128)
+en Duplicate key: largeFilePreciseRecommended (line 2129)
+en Duplicate key: longProcessingTime (line 2130)
+en Duplicate key: highApiCost (line 2131)
+en Duplicate key: considerFileSplitting (line 2132)
+en Duplicate key: dragDropUploadTitle (line 2135)
+en Duplicate key: dragDropUploadDesc (line 2136)
+en Duplicate key: supportedFormats (line 2137)
+en Duplicate key: browseFiles (line 2138)
+en Duplicate key: recommendationMsg (line 2141)
+en Duplicate key: autoAdjustChunk (line 2142)
+en Duplicate key: autoAdjustOverlap (line 2143)
+en Duplicate key: autoAdjustOverlapMin (line 2144)
+en Duplicate key: loadLimitsFailed (line 2145)
+en Duplicate key: maxValueMsg (line 2146)
+en Duplicate key: overlapRatioLimit (line 2147)
+en Duplicate key: onlyAdminCanModify (line 2148)
+en Duplicate key: dragToSelect (line 2149)
+en Duplicate key: fillTargetName (line 2152)
+en Duplicate key: submitFailed (line 2153)
+en Duplicate key: importFolderTitle (line 2154)
+en Duplicate key: importFolderTip (line 2155)
+en Duplicate key: lblTargetGroup (line 2156)
+en Duplicate key: placeholderNewGroup (line 2157)
+en Duplicate key: importToCurrentGroup (line 2158)
+en Duplicate key: nextStep (line 2159)
+en Duplicate key: lblImportSource (line 2160)
+en Duplicate key: serverPath (line 2161)
+en Duplicate key: localFolder (line 2162)
+en Duplicate key: selectedFilesCount (line 2163)
+en Duplicate key: clickToSelectFolder (line 2164)
+en Duplicate key: selectFolderTip (line 2165)
+en Duplicate key: importComplete (line 2166)
+en Duplicate key: importedFromLocalFolder (line 2167)
+en Duplicate key: historyTitle (line 2170)
+en Duplicate key: confirmDeleteHistory (line 2171)
+en Duplicate key: deleteHistorySuccess (line 2172)
+en Duplicate key: deleteHistoryFailed (line 2173)
+en Duplicate key: yesterday (line 2174)
+en Duplicate key: daysAgo (line 2175)
+en Duplicate key: historyMessages (line 2176)
+en Duplicate key: noHistory (line 2177)
+en Duplicate key: noHistoryDesc (line 2178)
+en Duplicate key: loadMore (line 2179)
+en Duplicate key: loadingHistoriesFailed (line 2180)
+en Duplicate key: supportedFormatsInfo (line 2181)
+en Duplicate key: navCatalog (line 2184)
+en Duplicate key: allDocuments (line 2185)
+en Duplicate key: uncategorized (line 2186)
+en Duplicate key: categories (line 2187)
+en Duplicate key: uncategorizedFiles (line 2188)
+en Duplicate key: category (line 2189)
+en Duplicate key: statusReadyDesc (line 2190)
+en Duplicate key: statusIndexingDesc (line 2191)
+en Duplicate key: selectCategory (line 2192)
+en Duplicate key: noneUncategorized (line 2193)
+en Duplicate key: previous (line 2194)
+en Duplicate key: next (line 2195)
+en Duplicate key: editCategory (line 2196)
+en Duplicate key: createCategory (line 2197)
+en Duplicate key: categoryDesc (line 2198)
+en Duplicate key: categoryName (line 2199)
+en Duplicate key: saveChanges (line 2200)
+en Duplicate key: createCategoryBtn (line 2201)
+en Duplicate key: totalTenants (line 2204)
+en Duplicate key: systemUsers (line 2205)
+en Duplicate key: systemHealth (line 2206)
+en Duplicate key: operational (line 2207)
+en Duplicate key: orgManagement (line 2208)
+en Duplicate key: globalTenantControl (line 2209)
+en Duplicate key: newTenant (line 2210)
+en Duplicate key: tenantName (line 2211)
+en Duplicate key: domainOptional (line 2212)
+en Duplicate key: assignInitialAdmin (line 2213)
+en Duplicate key: selectUserOptional (line 2214)
+en Duplicate key: promoteToAdminWarning (line 2215)
+en Duplicate key: editOrganization (line 2216)
+en Duplicate key: createOrganization (line 2217)
+en Duplicate key: bindAdmin (line 2218)
+en Duplicate key: manageMembers (line 2219)
+en Duplicate key: currentMembers (line 2220)
+en Duplicate key: addMembers (line 2221)
+en Duplicate key: systemRestricted (line 2222)
+en Duplicate key: noUnassignedUsers (line 2223)
+en Duplicate key: enableNotebook (line 2224)
+en Duplicate key: disableNotebook (line 2225)
+en Duplicate key: featureUpdated (line 2226)
+en Duplicate key: editUserRole (line 2229)
+en Duplicate key: targetRole (line 2230)
+en Duplicate key: changeUserPassword (line 2231)
+en Duplicate key: enterNewPassword (line 2232)
+en Duplicate key: confirmDeleteUser (line 2233)
+en Duplicate key: userDeletedSuccessfully (line 2234)
+en Duplicate key: defaultBadge (line 2235)
+en Duplicate key: kbSettingsSaved (line 2238)
+en Duplicate key: failedToSaveSettings (line 2239)
+en Duplicate key: modelConfiguration (line 2240)
+en Duplicate key: defaultLLMModel (line 2241)
+en Duplicate key: selectLLM (line 2242)
+en Duplicate key: embeddingModel (line 2243)
+en Duplicate key: selectEmbedding (line 2244)
+en Duplicate key: rerankModel (line 2245)
+en Duplicate key: none (line 2246)
+en Duplicate key: indexingChunkingConfig (line 2247)
+en Duplicate key: chatHyperparameters (line 2248)
+en Duplicate key: precise (line 2249)
+en Duplicate key: creative (line 2250)
+en Duplicate key: maxResponseTokens (line 2251)
+en Duplicate key: retrievalSearchSettings (line 2252)
+en Duplicate key: enableHybridSearch (line 2253)
+en Duplicate key: hybridSearchDesc (line 2254)
+en Duplicate key: hybridWeight (line 2255)
+en Duplicate key: pureText (line 2256)
+en Duplicate key: pureVector (line 2257)
+en Duplicate key: enableQueryExpansion (line 2258)
+en Duplicate key: queryExpansionDesc (line 2259)
+en Duplicate key: enableHyDE (line 2260)
+en Duplicate key: hydeDesc (line 2261)
+en Duplicate key: enableReranking (line 2262)
+en Duplicate key: rerankingDesc (line 2263)
+en Duplicate key: importFolder (line 2266)
+en Duplicate key: newGroup (line 2267)
+en Duplicate key: noKnowledgeGroups (line 2268)
+en Duplicate key: createGroupDesc (line 2269)
+en Duplicate key: noDescriptionProvided (line 2270)
+en Duplicate key: files (line 2271)
+en Duplicate key: filterGroupFiles (line 2272)
+en Duplicate key: browseManageFiles (line 2273)
+en Duplicate key: navAgent (line 2276)
+en Duplicate key: agentTitle (line 2277)
+en Duplicate key: agentDesc (line 2278)
+en Duplicate key: searchAgent (line 2279)
+en Duplicate key: createAgent (line 2280)
+en Duplicate key: statusRunning (line 2281)
+en Duplicate key: statusStopped (line 2282)
+en Duplicate key: btnChat (line 2283)
+en Duplicate key: navPlugin (line 2286)
+en Duplicate key: pluginTitle (line 2287)
+en Duplicate key: pluginDesc (line 2288)
+en Duplicate key: searchPlugin (line 2289)
+en Duplicate key: installPlugin (line 2290)
+en Duplicate key: installedPlugin (line 2291)
+en Duplicate key: updatePlugin (line 2292)
+en Duplicate key: selectOrganization (line 2295)
+en Duplicate key: defaultTenant (line 2296)
+en Duplicate key: roleTenantAdmin (line 2297)
+en Duplicate key: roleRegularUser (line 2298)
+en Duplicate key: creatingRegularUser (line 2299)
+en Duplicate key: defaultBadge (line 2300)
+en Duplicate key: defaultSettingFailed (line 2301)
+en Duplicate key: pluginOfficial (line 2302)
+en Duplicate key: pluginCommunity (line 2303)
+en Duplicate key: pluginBy (line 2304)
+en Duplicate key: pluginConfig (line 2305)
+en Duplicate key: updatedAtPrefix (line 2306)
+en Duplicate key: plugin1Name (line 2307)
+en Duplicate key: plugin1Desc (line 2308)
+en Duplicate key: plugin2Name (line 2309)
+en Duplicate key: plugin2Desc (line 2310)
+en Duplicate key: plugin3Name (line 2311)
+en Duplicate key: plugin3Desc (line 2312)
+en Duplicate key: plugin4Name (line 2313)
+en Duplicate key: plugin4Desc (line 2314)
+en Duplicate key: plugin5Name (line 2315)
+en Duplicate key: plugin5Desc (line 2316)
+en Duplicate key: plugin6Name (line 2317)
+en Duplicate key: plugin6Desc (line 2318)
+en Duplicate key: agent1Name (line 2319)
+en Duplicate key: agent1Desc (line 2320)
+en Duplicate key: agent1Time (line 2321)
+en Duplicate key: agent2Name (line 2322)
+en Duplicate key: agent2Desc (line 2323)
+en Duplicate key: agent2Time (line 2324)
+en Duplicate key: agent3Name (line 2325)
+en Duplicate key: agent3Desc (line 2326)
+en Duplicate key: agent3Time (line 2327)
+en Duplicate key: agent4Name (line 2328)
+en Duplicate key: agent4Desc (line 2329)
+en Duplicate key: agent4Time (line 2330)
+en Duplicate key: agent5Name (line 2331)
+en Duplicate key: agent5Desc (line 2332)
+en Duplicate key: agent5Time (line 2333)
+en Duplicate key: agent6Name (line 2334)
+en Duplicate key: agent6Desc (line 2335)
+en Duplicate key: agent6Time (line 2336)
+en Duplicate key: agent7Name (line 2337)
+en Duplicate key: agent7Desc (line 2338)
+en Duplicate key: agent7Time (line 2339)
+en Duplicate key: generalSettingsSubtitle (line 2340)
+en Duplicate key: userManagementSubtitle (line 2341)
+en Duplicate key: modelManagementSubtitle (line 2342)
+en Duplicate key: kbSettingsSubtitle (line 2343)
+en Duplicate key: tenantsSubtitle (line 2344)
+en Duplicate key: allNotes (line 2346)
+en Duplicate key: filterNotesPlaceholder (line 2347)
+en Duplicate key: noteTitlePlaceholder (line 2348)
+en Duplicate key: startWritingPlaceholder (line 2349)
+en Duplicate key: previewHeader (line 2350)
+en Duplicate key: noContentToPreview (line 2351)
+en Duplicate key: hidePreview (line 2352)
+en Duplicate key: showPreview (line 2353)
+en Duplicate key: directoryLabel (line 2354)
+en Duplicate key: uncategorized (line 2355)
+en Duplicate key: enterNamePlaceholder (line 2356)
+en Duplicate key: subFolderPlaceholder (line 2357)
+en Duplicate key: categoryCreated (line 2358)
+en Duplicate key: failedToCreateCategory (line 2359)
+en Duplicate key: failedToDeleteCategory (line 2360)
+en Duplicate key: confirmDeleteCategory (line 2361)
+ja Duplicate key: changeUserPassword (line 2231)
+ja Duplicate key: enterNewPassword (line 2232)
+ja Duplicate key: confirmDeleteUser (line 2233)
+ja Duplicate key: userDeletedSuccessfully (line 2234)
+ja Duplicate key: embeddingModel (line 2243)
+ja Duplicate key: importFolder (line 2266)
+ja Duplicate key: files (line 2271)
+ja Duplicate key: defaultBadge (line 2300)
+ja Duplicate key: noteTitlePlaceholder (line 2348)
+ja Duplicate key: uncategorized (line 2355)

+ 4 - 4
web/components/DragDropUpload.tsx

@@ -84,15 +84,15 @@ export const DragDropUpload: React.FC<DragDropUploadProps> = ({ onFilesSelected,
         <div className="flex flex-wrap items-center justify-center gap-4 mb-8">
           <div className="flex items-center gap-2 px-3 py-1.5 bg-white border border-slate-100 rounded-full text-[11px] font-bold text-slate-500 uppercase tracking-wider shadow-sm">
             <ShieldCheck size={14} className="text-emerald-500" />
-            <span>Secure Ingestion</span>
+            <span>{t('secureIngestion')}</span>
           </div>
           <div className="flex items-center gap-2 px-3 py-1.5 bg-white border border-slate-100 rounded-full text-[11px] font-bold text-slate-500 uppercase tracking-wider shadow-sm">
             <FileText size={14} className="text-blue-500" />
-            <span>Documents & Text</span>
+            <span>{t('documentsAndText')}</span>
           </div>
           <div className="flex items-center gap-2 px-3 py-1.5 bg-white border border-slate-100 rounded-full text-[11px] font-bold text-slate-500 uppercase tracking-wider shadow-sm">
             <ImageIcon size={14} className="text-purple-500" />
-            <span>Images & Vision</span>
+            <span>{t('imagesAndVision')}</span>
           </div>
         </div>
 
@@ -126,7 +126,7 @@ export const DragDropUpload: React.FC<DragDropUploadProps> = ({ onFilesSelected,
               <div className="w-12 h-12 bg-blue-600 text-white rounded-2xl flex items-center justify-center animate-bounce">
                 <FileUp size={24} />
               </div>
-              <span className="text-blue-600 font-bold">Drop to Ingest</span>
+              <span className="text-blue-600 font-bold">{t('dropToIngest')}</span>
             </div>
           </motion.div>
         )}

+ 5 - 5
web/components/GlobalDragDropOverlay.tsx

@@ -131,27 +131,27 @@ export const GlobalDragDropOverlay: React.FC<GlobalDragDropProps> = ({ onFilesSe
                   {t('dragDropUploadTitle')}
                 </h3>
                 <p className="text-lg text-slate-500 font-medium">
-                  Drop your files anywhere to start processing
+                  {t('dropAnywhere')}
                 </p>
               </div>
 
               <div className="flex flex-wrap items-center justify-center gap-6 py-8 border-y border-slate-100 w-full">
                 <div className="flex items-center gap-3 px-4 py-2 bg-slate-50 rounded-2xl text-sm font-bold text-slate-600 uppercase tracking-wider">
                   <ShieldCheck size={20} className="text-emerald-500" />
-                  <span>Secure Processing</span>
+                  <span>{t('secureProcessing')}</span>
                 </div>
                 <div className="flex items-center gap-3 px-4 py-2 bg-slate-50 rounded-2xl text-sm font-bold text-slate-600 uppercase tracking-wider">
                   <FileText size={20} className="text-blue-500" />
-                  <span>All Formats</span>
+                  <span>{t('allFormats')}</span>
                 </div>
                 <div className="flex items-center gap-3 px-4 py-2 bg-slate-50 rounded-2xl text-sm font-bold text-slate-600 uppercase tracking-wider">
                   <ImageIcon size={20} className="text-purple-500" />
-                  <span>Visual Vision</span>
+                  <span>{t('visualVision')}</span>
                 </div>
               </div>
 
               <div className="text-slate-400 font-bold text-xs uppercase tracking-[0.3em]">
-                Release to begin ingestion
+                {t('releaseToIngest')}
               </div>
             </div>
           </motion.div>

+ 414 - 122
web/components/ImportFolderDrawer.tsx

@@ -1,10 +1,11 @@
 import React, { useState, useEffect } from 'react';
-import { X, FolderInput, ArrowRight, Info } from 'lucide-react';
-import { GROUP_ALLOWED_EXTENSIONS, isExtensionAllowed, getSupportedFormatsLabel } from '../constants/fileSupport';
+import { X, FolderInput, ArrowRight, Info, Layers, Clock, Upload, Calendar } from 'lucide-react';
+import { isExtensionAllowed } from '../constants/fileSupport';
 import { useLanguage } from '../contexts/LanguageContext';
-import { ModelConfig, ModelType, IndexingConfig } from '../types';
+import { ModelConfig, ModelType, IndexingConfig, KnowledgeGroup } from '../types';
 import { modelConfigService } from '../services/modelConfigService';
 import { knowledgeGroupService } from '../services/knowledgeGroupService';
+import { apiClient } from '../services/apiClient';
 import { useToast } from '../contexts/ToastContext';
 import IndexingModalWithMode from './IndexingModalWithMode';
 
@@ -12,131 +13,230 @@ interface ImportFolderDrawerProps {
     isOpen: boolean;
     onClose: () => void;
     authToken: string;
-    initialGroupId?: string; // If provided, locks target to this group
+    initialGroupId?: string;
     initialGroupName?: string;
     onImportSuccess?: () => void;
 }
 
+interface FileWithPath {
+    file: File;
+    relativePath: string;
+}
+
+type ImportMode = 'immediate' | 'scheduled';
+
 export const ImportFolderDrawer: React.FC<ImportFolderDrawerProps> = ({
     isOpen,
     onClose,
     authToken,
     initialGroupId,
     initialGroupName,
-    onImportSuccess
+    onImportSuccess,
 }) => {
     const { t } = useLanguage();
     const { showError, showSuccess } = useToast();
 
-    // Form State
-    const [localFiles, setLocalFiles] = useState<File[]>([]);
+    // Tab
+    const [importMode, setImportMode] = useState<ImportMode>('immediate');
+
+    // Immediate mode state
+    const [localFiles, setLocalFiles] = useState<FileWithPath[]>([]);
     const [folderName, setFolderName] = useState('');
     const [targetName, setTargetName] = useState('');
+    const [useHierarchy, setUseHierarchy] = useState(false);
     const fileInputRef = React.useRef<HTMLInputElement>(null);
-
-    // Indexing Config State
     const [isIndexingConfigOpen, setIsIndexingConfigOpen] = useState(false);
-
-    // Data State
     const [models, setModels] = useState<ModelConfig[]>([]);
+
+    // Scheduled mode state
+    const [serverPath, setServerPath] = useState('');
+    const [scheduledTime, setScheduledTime] = useState(() => {
+        // Default to 30 min from now
+        const d = new Date();
+        d.setMinutes(d.getMinutes() + 30);
+        d.setMinutes(d.getMinutes() - d.getTimezoneOffset());
+        return d.toISOString().slice(0, 16); // "YYYY-MM-DDTHH:mm"
+    });
+    const [schedTargetName, setSchedTargetName] = useState('');
+    const [schedUseHierarchy, setSchedUseHierarchy] = useState(false);
+
     const [isLoading, setIsLoading] = useState(false);
+    const [allGroups, setAllGroups] = useState<KnowledgeGroup[]>([]);
+    const [parentGroupId, setParentGroupId] = useState<string>('');
+    const [schedParentGroupId, setSchedParentGroupId] = useState<string>('');
 
     useEffect(() => {
         if (isOpen) {
-            // Reset form
             setLocalFiles([]);
             setFolderName('');
             setTargetName(initialGroupName || '');
+            setUseHierarchy(false);
             setIsIndexingConfigOpen(false);
+            setImportMode('immediate');
+            setServerPath('');
+            setSchedTargetName(initialGroupName || '');
+            setSchedUseHierarchy(false);
+            setParentGroupId('');
+            setSchedParentGroupId('');
+
+            // Default scheduled time = 30min from now
+            const d = new Date();
+            d.setMinutes(d.getMinutes() + 30);
+            d.setMinutes(d.getMinutes() - d.getTimezoneOffset());
+            setScheduledTime(d.toISOString().slice(0, 16));
 
-            // Fetch models
             modelConfigService.getAll(authToken).then(res => {
                 setModels(res.filter(m => m.type === ModelType.EMBEDDING));
             });
+
+            knowledgeGroupService.getGroups().then(groups => {
+                const flat: any[] = [];
+                function walk(items: any[], depth = 0) {
+                    for (const g of items) {
+                        flat.push({ ...g, d: depth });
+                        if (g.children?.length) walk(g.children, depth + 1);
+                    }
+                }
+                walk(groups);
+                setAllGroups(flat);
+            });
         }
     }, [isOpen, authToken, initialGroupName]);
 
+    // ---- Immediate mode handlers ----
     const handleLocalFolderChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         if (e.target.files && e.target.files.length > 0) {
             const allFiles = Array.from(e.target.files);
-            // Filter files by allowed extensions
-            const files = allFiles.filter(file => {
-                const ext = file.name.split('.').pop() || '';
-                return isExtensionAllowed(ext, 'group');
-            });
+            const files: FileWithPath[] = allFiles
+                .filter(file => {
+                    const ext = file.name.split('.').pop() || '';
+                    return isExtensionAllowed(ext, 'group');
+                })
+                .map(file => ({
+                    file,
+                    relativePath: file.webkitRelativePath || file.name,
+                }));
 
             if (files.length === 0 && allFiles.length > 0) {
                 showError(t('noFilesFound'));
                 return;
             }
-
             setLocalFiles(files);
 
-            // Get root folder name from webkitRelativePath
-            const firstPath = allFiles[0].webkitRelativePath; // Use allFiles to get path even if filtered
+            const firstPath = allFiles[0].webkitRelativePath;
             if (firstPath) {
                 const parts = firstPath.split('/');
                 if (parts.length > 0) {
                     const name = parts[0];
                     setFolderName(name);
-                    if (!initialGroupId && !targetName) {
-                        setTargetName(name);
-                    }
+                    if (!initialGroupId && !targetName) setTargetName(name);
                 }
             }
         }
     };
 
-    const handleNext = async () => {
+    const handleImmediateNext = () => {
         if (localFiles.length === 0) {
             showError(t('clickToSelectFolder'));
             return;
         }
-
         if (!initialGroupId && !targetName) {
             showError(t('fillTargetName'));
             return;
         }
-
-        // Open indexing config modal
         setIsIndexingConfigOpen(true);
     };
 
     const handleConfirmConfig = async (config: IndexingConfig) => {
         setIsLoading(true);
         try {
-            // 1. Ensure target group exists or create it
-            let groupId = initialGroupId;
-            if (!groupId) {
-                const newGroup = await knowledgeGroupService.createGroup({
-                    name: targetName,
-                    description: t('importedFromLocalFolder').replace('$1', folderName)
-                });
-                groupId = newGroup.id;
-            }
-
-            // 2. Upload files in batches
             const { uploadService } = await import('../services/uploadService');
-            const { readFile } = await import('../utils/fileUtils');
-
-            // Limit concurrent uploads for large folders
-            const BATCH_SIZE = 3;
-            for (let i = 0; i < localFiles.length; i += BATCH_SIZE) {
-                const batch = localFiles.slice(i, i + BATCH_SIZE);
-                await Promise.all(batch.map(async (file) => {
-                    try {
-                        await readFile(file); // Optional: verify readability
-                        const uploadedKb = await uploadService.uploadFileWithConfig(file, config, authToken);
-                        if (groupId) {
-                            await knowledgeGroupService.addFileToGroups(uploadedKb.id, [groupId]);
-                        }
-                    } catch (err) {
-                        console.error(`Failed to upload ${file.name}:`, err);
+
+            if (useHierarchy) {
+                // Step 1: Determine root group
+                let rootGroupId = initialGroupId ?? null;
+                if (!rootGroupId) {
+                    const newGroup = await knowledgeGroupService.createGroup({
+                        name: targetName,
+                        description: t('importedFromLocalFolder').replace('$1', folderName),
+                        parentId: parentGroupId || null,
+                    });
+                    rootGroupId = newGroup.id;
+                }
+
+                // Step 2: Collect all unique directory paths
+                const dirSet = new Set<string>();
+                for (const { relativePath } of localFiles) {
+                    const parts = relativePath.split('/');
+                    const dirParts = initialGroupId
+                        ? parts.slice(1, parts.length - 1)
+                        : parts.slice(0, parts.length - 1);
+                    for (let i = 1; i <= dirParts.length; i++) {
+                        dirSet.add(dirParts.slice(0, i).join('/'));
                     }
-                }));
+                }
+
+                // Step 3: Sort by depth, create groups sequentially
+                const sortedDirs = Array.from(dirSet).sort((a, b) =>
+                    a.split('/').length - b.split('/').length
+                );
+                const dirToGroupId = new Map<string, string>();
+                dirToGroupId.set(initialGroupId ? '' : folderName, rootGroupId);
+
+                for (const dirPath of sortedDirs) {
+                    if (!dirPath || dirToGroupId.has(dirPath)) continue;
+                    const segments = dirPath.split('/');
+                    const segName = segments[segments.length - 1];
+                    const parentPath = segments.slice(0, segments.length - 1).join('/');
+                    const parentId = dirToGroupId.get(parentPath) ?? rootGroupId;
+                    const newGroup = await knowledgeGroupService.createGroup({ name: segName, parentId });
+                    dirToGroupId.set(dirPath, newGroup.id);
+                }
+
+                // Step 4: Upload files in parallel batches
+                const BATCH_SIZE = 3;
+                for (let i = 0; i < localFiles.length; i += BATCH_SIZE) {
+                    const batch = localFiles.slice(i, i + BATCH_SIZE);
+                    await Promise.all(batch.map(async ({ file, relativePath }) => {
+                        try {
+                            const parts = relativePath.split('/');
+                            const dirParts = initialGroupId
+                                ? parts.slice(1, parts.length - 1)
+                                : parts.slice(0, parts.length - 1);
+                            const fileDirPath = dirParts.join('/');
+                            const targetGroupId = dirToGroupId.get(fileDirPath) ?? rootGroupId!;
+                            const uploadedKb = await uploadService.uploadFileWithConfig(file, config, authToken);
+                            await knowledgeGroupService.addFileToGroups(uploadedKb.id, [targetGroupId]);
+                        } catch (err) {
+                            console.error(`Failed to upload ${file.name}:`, err);
+                        }
+                    }));
+                }
+            } else {
+                // Single-group mode
+                let groupId = initialGroupId ?? null;
+                if (!groupId) {
+                    const newGroup = await knowledgeGroupService.createGroup({
+                        name: targetName,
+                        description: t('importedFromLocalFolder').replace('$1', folderName),
+                    });
+                    groupId = newGroup.id;
+                }
+                const BATCH_SIZE = 3;
+                for (let i = 0; i < localFiles.length; i += BATCH_SIZE) {
+                    const batch = localFiles.slice(i, i + BATCH_SIZE);
+                    await Promise.all(batch.map(async ({ file }) => {
+                        try {
+                            const uploadedKb = await uploadService.uploadFileWithConfig(file, config, authToken);
+                            if (groupId) await knowledgeGroupService.addFileToGroups(uploadedKb.id, [groupId]);
+                        } catch (err) {
+                            console.error(`Failed to upload ${file.name}:`, err);
+                        }
+                    }));
+                }
             }
-            showSuccess(t('importComplete'));
 
+            showSuccess(t('importComplete'));
             onImportSuccess?.();
             onClose();
         } catch (error: any) {
@@ -147,6 +247,58 @@ export const ImportFolderDrawer: React.FC<ImportFolderDrawerProps> = ({
         }
     };
 
+    // ---- Scheduled mode handler ----
+    const handleScheduledSubmit = async () => {
+        if (!serverPath.trim()) {
+            showError(t('fillServerPath'));
+            return;
+        }
+        if (!initialGroupId && !schedTargetName.trim()) {
+            showError(t('fillTargetName'));
+            return;
+        }
+
+        const scheduledAt = new Date(scheduledTime);
+        if (isNaN(scheduledAt.getTime())) {
+            showError(t('invalidDateTime' as any));
+            return;
+        }
+
+        setIsLoading(true);
+        try {
+            let finalGroupId = initialGroupId || undefined;
+            if (!finalGroupId && schedTargetName.trim()) {
+                const newGroup = await knowledgeGroupService.createGroup({
+                    name: schedTargetName.trim(),
+                    description: t('importedFromLocalFolder').replace('$1', schedTargetName.trim()),
+                    parentId: schedParentGroupId || null,
+                });
+                finalGroupId = newGroup.id;
+            }
+
+            const defaultModel = models[0];
+            await apiClient.post('/import-tasks', {
+                sourcePath: serverPath.trim(),
+                targetGroupId: finalGroupId,
+                targetGroupName: undefined,
+                embeddingModelId: defaultModel?.id,
+                scheduledAt: scheduledAt.toISOString(),
+                chunkSize: 500,
+                chunkOverlap: 50,
+                mode: 'fast',
+                useHierarchy: schedUseHierarchy,
+            });
+
+            showSuccess(t('scheduleTaskCreated'));
+            onImportSuccess?.();
+            onClose();
+        } catch (error: any) {
+            showError(t('submitFailed', error.message));
+        } finally {
+            setIsLoading(false);
+        }
+    };
+
     if (!isOpen) return null;
 
     return (
@@ -166,65 +318,167 @@ export const ImportFolderDrawer: React.FC<ImportFolderDrawerProps> = ({
                         </button>
                     </div>
 
+                    {/* Mode Tabs */}
+                    <div className="flex border-b border-slate-100 shrink-0">
+                        <button
+                            onClick={() => setImportMode('immediate')}
+                            className={`flex-1 flex items-center justify-center gap-2 py-3 text-sm font-semibold transition-colors border-b-2 ${importMode === 'immediate'
+                                ? 'border-blue-600 text-blue-600 bg-blue-50/40'
+                                : 'border-transparent text-slate-500 hover:text-slate-700'
+                                }`}
+                        >
+                            <Upload size={15} />
+                            {t('importImmediate')}
+                        </button>
+                        <button
+                            onClick={() => setImportMode('scheduled')}
+                            className={`flex-1 flex items-center justify-center gap-2 py-3 text-sm font-semibold transition-colors border-b-2 ${importMode === 'scheduled'
+                                ? 'border-blue-600 text-blue-600 bg-blue-50/40'
+                                : 'border-transparent text-slate-500 hover:text-slate-700'
+                                }`}
+                        >
+                            <Clock size={15} />
+                            {t('importScheduled')}
+                        </button>
+                    </div>
+
                     {/* Body */}
-                    <div className="flex-1 overflow-y-auto p-6 space-y-6">
-                        <div className="bg-blue-50 border border-blue-100 rounded-lg p-3 text-sm text-blue-800 flex items-start gap-2">
-                            <Info className="w-4 h-4 mt-0.5 shrink-0" />
-                            <div>
-                                <p className="font-medium underline decoration-blue-200 underline-offset-2 mb-1">
-                                    {t('supportedFormatsInfo')}
-                                </p>
-                                <p className="opacity-80 text-xs">
-                                    {t('importFolderTip')}
-                                </p>
-                            </div>
-                        </div>
-
-                        {/* Local Folder Selection */}
-                        <div className="space-y-4 animate-in fade-in slide-in-from-top-2">
-                            <div
-                                onClick={() => fileInputRef.current?.click()}
-                                className="border-2 border-dashed border-slate-200 rounded-xl p-8 flex flex-col items-center justify-center gap-3 cursor-pointer hover:border-blue-400 hover:bg-blue-50 transition-all group"
-                            >
-                                <div className="w-12 h-12 bg-slate-50 text-slate-400 rounded-full flex items-center justify-center group-hover:bg-blue-100 group-hover:text-blue-600 transition-colors">
-                                    <FolderInput size={24} />
+                    <div className="flex-1 overflow-y-auto p-6 space-y-5">
+                        {importMode === 'immediate' ? (
+                            <>
+                                {/* Immediate: folder picker */}
+                                <div
+                                    onClick={() => fileInputRef.current?.click()}
+                                    className="border-2 border-dashed border-slate-200 rounded-xl p-8 flex flex-col items-center justify-center gap-3 cursor-pointer hover:border-blue-400 hover:bg-blue-50 transition-all group"
+                                >
+                                    <div className="w-12 h-12 bg-slate-50 text-slate-400 rounded-full flex items-center justify-center group-hover:bg-blue-100 group-hover:text-blue-600 transition-colors">
+                                        <FolderInput size={24} />
+                                    </div>
+                                    <div className="text-center">
+                                        <p className="text-sm font-medium text-slate-700">
+                                            {localFiles.length > 0
+                                                ? t('selectedFilesCount').replace('$1', localFiles.length.toString())
+                                                : t('clickToSelectFolder')}
+                                        </p>
+                                        <p className="text-xs text-slate-400 mt-1">
+                                            {localFiles.length > 0 ? folderName : t('selectFolderTip')}
+                                        </p>
+                                    </div>
+                                    <input
+                                        type="file"
+                                        ref={fileInputRef}
+                                        onChange={handleLocalFolderChange}
+                                        className="hidden"
+                                        multiple
+                                        // @ts-ignore
+                                        webkitdirectory=""
+                                        directory=""
+                                    />
                                 </div>
-                                <div className="text-center">
-                                    <p className="text-sm font-medium text-slate-700">
-                                        {localFiles.length > 0
-                                            ? t('selectedFilesCount').replace('$1', localFiles.length.toString())
-                                            : t('clickToSelectFolder')}
-                                    </p>
-                                    <p className="text-xs text-slate-400 mt-1">
-                                        {localFiles.length > 0 ? folderName : t('selectFolderTip')}
-                                    </p>
+
+                                {/* Target group */}
+                                <div className="space-y-1.5">
+                                    <label className="text-sm font-medium text-slate-700">{t('lblTargetGroup')}</label>
+                                    <input
+                                        type="text"
+                                        value={targetName}
+                                        onChange={e => setTargetName(e.target.value)}
+                                        disabled={!!initialGroupId}
+                                        placeholder={t('placeholderNewGroup')}
+                                        className={`w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none ${initialGroupId ? 'bg-slate-50 text-slate-500' : ''}`}
+                                    />
+                                    {initialGroupId && <p className="text-xs text-slate-400">{t('importToCurrentGroup')}</p>}
+                                </div>
+                                {!initialGroupId && (
+                                    <div className="space-y-1.5">
+                                        <label className="text-sm font-medium text-slate-700">{t('parentCategory') || 'Parent Category'}</label>
+                                        <select
+                                            value={parentGroupId}
+                                            onChange={e => setParentGroupId(e.target.value)}
+                                            className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none"
+                                        >
+                                            <option value="">{t('allGroups' as any) || '-- Root --'}</option>
+                                            {allGroups.map((g: any) => (
+                                                <option key={g.id} value={g.id}>
+                                                    {'\u00A0'.repeat(g.d * 4)}{g.name}
+                                                </option>
+                                            ))}
+                                        </select>
+                                    </div>
+                                )}
+
+                                {/* Hierarchy toggle */}
+                                <HierarchyToggle value={useHierarchy} onChange={setUseHierarchy} t={t} />
+                            </>
+                        ) : (
+                            <>
+                                {/* Scheduled: server path */}
+                                <div className="bg-amber-50 border border-amber-100 rounded-lg p-3 text-sm text-amber-800 flex items-start gap-2">
+                                    <Info className="w-4 h-4 mt-0.5 shrink-0" />
+                                    <p className="text-xs">{t('scheduledImportTip')}</p>
                                 </div>
-                                <input
-                                    type="file"
-                                    ref={fileInputRef}
-                                    onChange={handleLocalFolderChange}
-                                    className="hidden"
-                                    multiple
-                                    // @ts-ignore
-                                    webkitdirectory=""
-                                    directory=""
-                                />
-                            </div>
-                        </div>
-
-                        {/* Target Group */}
-                        <div className="space-y-2">
-                            <label className="text-sm font-medium text-slate-700">{t('lblTargetGroup')}</label>
-                            <input
-                                type="text"
-                                value={targetName}
-                                onChange={e => setTargetName(e.target.value)}
-                                disabled={!!initialGroupId} // Readonly if locking to group
-                                placeholder={t('placeholderNewGroup')}
-                                className={`w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none ${initialGroupId ? 'bg-slate-50 text-slate-500' : ''}`}
-                            />
-                            {initialGroupId && <p className="text-xs text-slate-400">{t('importToCurrentGroup')}</p>}
-                        </div>
+
+                                <div className="space-y-1.5">
+                                    <label className="text-sm font-medium text-slate-700">{t('lblServerPath')}</label>
+                                    <input
+                                        type="text"
+                                        value={serverPath}
+                                        onChange={e => setServerPath(e.target.value)}
+                                        placeholder={t('placeholderServerPath')}
+                                        className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none font-mono"
+                                    />
+                                </div>
+
+                                {/* Target group */}
+                                <div className="space-y-1.5">
+                                    <label className="text-sm font-medium text-slate-700">{t('lblTargetGroup')}</label>
+                                    <input
+                                        type="text"
+                                        value={schedTargetName}
+                                        onChange={e => setSchedTargetName(e.target.value)}
+                                        disabled={!!initialGroupId}
+                                        placeholder={t('placeholderNewGroup')}
+                                        className={`w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none ${initialGroupId ? 'bg-slate-50 text-slate-500' : ''}`}
+                                    />
+                                    {initialGroupId && <p className="text-xs text-slate-400">{t('importToCurrentGroup')}</p>}
+                                </div>
+                                {!initialGroupId && (
+                                    <div className="space-y-1.5">
+                                        <label className="text-sm font-medium text-slate-700">{t('parentCategory') || 'Parent Category'}</label>
+                                        <select
+                                            value={schedParentGroupId}
+                                            onChange={e => setSchedParentGroupId(e.target.value)}
+                                            className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none"
+                                        >
+                                            <option value="">{t('allGroups' as any) || '-- Root --'}</option>
+                                            {allGroups.map((g: any) => (
+                                                <option key={g.id} value={g.id}>
+                                                    {'\u00A0'.repeat(g.d * 4)}{g.name}
+                                                </option>
+                                            ))}
+                                        </select>
+                                    </div>
+                                )}
+
+                                {/* Scheduled datetime */}
+                                <div className="space-y-1.5">
+                                    <label className="text-sm font-medium text-slate-700 flex items-center gap-1.5">
+                                        <Calendar size={14} className="text-blue-500" />
+                                        {t('lblScheduledTime')}
+                                    </label>
+                                    <input
+                                        type="datetime-local"
+                                        value={scheduledTime}
+                                        onChange={e => setScheduledTime(e.target.value)}
+                                        className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none"
+                                    />
+                                    <p className="text-xs text-slate-400">{t('scheduledTimeHint')}</p>
+                                </div>
+
+                                {/* Hierarchy toggle */}
+                                <HierarchyToggle value={schedUseHierarchy} onChange={setSchedUseHierarchy} t={t} />
+                            </>
+                        )}
                     </div>
 
                     {/* Footer */}
@@ -235,23 +489,34 @@ export const ImportFolderDrawer: React.FC<ImportFolderDrawerProps> = ({
                         >
                             {t('cancel')}
                         </button>
-                        <button
-                            onClick={handleNext}
-                            className="flex-1 px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg shadow-sm flex justify-center items-center gap-2 transition-all"
-                            disabled={isLoading}
-                        >
-                            <span>{isLoading ? t('uploading') : t('nextStep')}</span>
-                            <ArrowRight size={16} />
-                        </button>
+                        {importMode === 'immediate' ? (
+                            <button
+                                onClick={handleImmediateNext}
+                                className="flex-1 px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg shadow-sm flex justify-center items-center gap-2 transition-all"
+                                disabled={isLoading}
+                            >
+                                <span>{isLoading ? t('uploading') : t('nextStep')}</span>
+                                <ArrowRight size={16} />
+                            </button>
+                        ) : (
+                            <button
+                                onClick={handleScheduledSubmit}
+                                className="flex-1 px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg shadow-sm flex justify-center items-center gap-2 transition-all"
+                                disabled={isLoading}
+                            >
+                                <Clock size={16} />
+                                <span>{isLoading ? t('uploading') : t('scheduleImport')}</span>
+                            </button>
+                        )}
                     </div>
                 </div>
             </div>
 
-            {/* Indexing Config Modal */}
+            {/* Indexing Config Modal (immediate mode only) */}
             <IndexingModalWithMode
                 isOpen={isIndexingConfigOpen}
                 onClose={() => setIsIndexingConfigOpen(false)}
-                files={[]} // Empty array for folder import mode
+                files={[]}
                 embeddingModels={models}
                 defaultEmbeddingId={models.length > 0 ? models[0].id : ''}
                 onConfirm={handleConfirmConfig}
@@ -260,3 +525,30 @@ export const ImportFolderDrawer: React.FC<ImportFolderDrawerProps> = ({
         </>
     );
 };
+
+/** Reusable hierarchy toggle */
+const HierarchyToggle: React.FC<{
+    value: boolean;
+    onChange: (v: boolean) => void;
+    t: (key: string) => string;
+}> = ({ value, onChange, t }) => (
+    <label className="flex items-center gap-3 cursor-pointer select-none">
+        <div className="relative">
+            <input
+                type="checkbox"
+                checked={value}
+                onChange={e => onChange(e.target.checked)}
+                className="sr-only"
+            />
+            <div className={`w-10 h-5 rounded-full transition-colors ${value ? 'bg-blue-600' : 'bg-slate-200'}`} />
+            <div className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white shadow transition-transform ${value ? 'translate-x-5' : ''}`} />
+        </div>
+        <div>
+            <p className="text-sm font-medium text-slate-700 flex items-center gap-1.5">
+                <Layers size={14} className="text-blue-500" />
+                {t('useHierarchyImport')}
+            </p>
+            <p className="text-xs text-slate-400 mt-0.5">{t('useHierarchyImportDesc')}</p>
+        </div>
+    </label>
+);

+ 174 - 0
web/components/drawers/ImportTasksDrawer.tsx

@@ -0,0 +1,174 @@
+import React, { useState, useEffect } from 'react';
+import { X, Box, Loader2, Trash2 } from 'lucide-react';
+import { importService, ImportTask } from '../../services/importService';
+import { useLanguage } from '../../contexts/LanguageContext';
+import { knowledgeGroupService } from '../../services/knowledgeGroupService';
+import { KnowledgeGroup } from '../../types';
+
+interface ImportTasksDrawerProps {
+    isOpen: boolean;
+    onClose: () => void;
+    authToken: string;
+}
+
+export const ImportTasksDrawer: React.FC<ImportTasksDrawerProps> = ({
+    isOpen,
+    onClose,
+    authToken,
+}) => {
+    const { t } = useLanguage();
+    const [importTasks, setImportTasks] = useState<ImportTask[]>([]);
+    const [groups, setGroups] = useState<KnowledgeGroup[]>([]);
+    const [isLoading, setIsLoading] = useState(false);
+
+    const fetchData = async () => {
+        if (!authToken) return;
+        try {
+            setIsLoading(true);
+            const [tasks, groupsData] = await Promise.all([
+                importService.getAll(authToken),
+                knowledgeGroupService.getGroups()
+            ]);
+            setImportTasks(tasks);
+
+            // Flatten the groups tree so we can easily find names by ID
+            const flat: KnowledgeGroup[] = [];
+            const walk = (items: KnowledgeGroup[]) => {
+                for (const g of items) {
+                    flat.push(g);
+                    if (g.children?.length) walk(g.children);
+                }
+            };
+            if (groupsData) walk(groupsData);
+            setGroups(flat);
+        } catch (error) {
+            console.error('Failed to fetch data:', error);
+        } finally {
+            setIsLoading(false);
+        }
+    };
+
+    const handleDelete = async (taskId: string) => {
+        if (!window.confirm(t('confirmDeleteTask'))) return;
+        try {
+            await importService.delete(authToken, taskId);
+            fetchData();
+        } catch (error) {
+            console.error('Failed to delete task:', error);
+            alert(t('deleteTaskFailed'));
+        }
+    };
+
+    useEffect(() => {
+        if (isOpen) {
+            fetchData();
+        }
+    }, [isOpen, authToken]);
+
+    if (!isOpen) return null;
+
+    return (
+        <div className="fixed inset-0 z-50 flex justify-end">
+            <div className="absolute inset-0 bg-black/20 backdrop-blur-sm transition-opacity" onClick={onClose} />
+
+            <div className="relative w-full max-w-4xl bg-white shadow-2xl flex flex-col h-full animate-in slide-in-from-right duration-300">
+                {/* Header */}
+                <div className="px-6 py-4 border-b border-slate-100 flex items-center justify-between shrink-0 bg-white">
+                    <div className="flex items-center gap-3">
+                        <div className="w-8 h-8 rounded-xl bg-indigo-50 flex items-center justify-center text-indigo-600">
+                            <Box size={16} />
+                        </div>
+                        <h2 className="text-lg font-bold text-slate-800">{t('importTasksTitle')}</h2>
+                    </div>
+                    <div className="flex items-center gap-2">
+                        <button
+                            onClick={fetchData}
+                            className="p-2 text-slate-400 hover:text-indigo-600 hover:bg-slate-50 rounded-lg transition-all"
+                            title={t('refresh')}
+                        >
+                            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path></svg>
+                        </button>
+                        <button
+                            onClick={onClose}
+                            className="p-2 text-slate-400 hover:text-slate-600 hover:bg-slate-50 rounded-full transition-colors"
+                        >
+                            <X size={20} />
+                        </button>
+                    </div>
+                </div>
+
+                {/* Body */}
+                <div className="flex-1 overflow-y-auto p-6 bg-slate-50/30">
+                    {isLoading ? (
+                        <div className="p-12 flex justify-center">
+                            <Loader2 className="animate-spin text-slate-300 w-8 h-8" />
+                        </div>
+                    ) : importTasks.length === 0 ? (
+                        <div className="p-12 text-center text-slate-400 flex flex-col items-center">
+                            <Box size={48} className="mb-4 opacity-20" />
+                            <span className="text-sm font-bold uppercase tracking-widest">{t('noTasksFound')}</span>
+                        </div>
+                    ) : (
+                        <div className="bg-white rounded-2xl border border-slate-200/60 shadow-sm overflow-hidden">
+                            <div className="overflow-x-auto">
+                                <table className="w-full text-left">
+                                    <thead>
+                                        <tr className="border-b border-slate-200/60 bg-slate-50/50 text-[10px] font-black uppercase tracking-widest text-slate-500">
+                                            <th className="px-6 py-4">{t('sourcePath')}</th>
+                                            <th className="px-6 py-4">{t('targetGroup')}</th>
+                                            <th className="px-6 py-4">{t('status')}</th>
+                                            <th className="px-6 py-4">{t('scheduledAt')}</th>
+                                            <th className="px-6 py-4">{t('createdAt')}</th>
+                                            <th className="px-6 py-4 text-right">{t('actions')}</th>
+                                        </tr>
+                                    </thead>
+                                    <tbody className="divide-y divide-slate-100/80 text-sm">
+                                        {importTasks.map(task => (
+                                            <tr key={task.id} className="hover:bg-slate-50/50 transition-colors">
+                                                <td className="px-6 py-4 text-slate-900 font-medium">
+                                                    {task.sourcePath}
+                                                </td>
+                                                <td className="px-6 py-4 text-slate-500">
+                                                    {groups.find((g: any) => g.id === task.targetGroupId)?.name || task.targetGroupName || task.targetGroupId || '-'}
+                                                </td>
+                                                <td className="px-6 py-4">
+                                                    <span className={`px-2 py-1 rounded-md text-xs font-bold ${task.status === 'COMPLETED' ? 'bg-emerald-100 text-emerald-700' :
+                                                        task.status === 'FAILED' ? 'bg-red-100 text-red-700' :
+                                                            task.status === 'PROCESSING' ? 'bg-blue-100 text-blue-700' :
+                                                                'bg-amber-100 text-amber-700'
+                                                        }`}>
+                                                        {task.status}
+                                                    </span>
+                                                    {task.status === 'FAILED' && task.logs && (
+                                                        <div className="text-xs text-red-500 mt-1 max-w-xs truncate" title={task.logs}>
+                                                            {task.logs}
+                                                        </div>
+                                                    )}
+                                                </td>
+                                                <td className="px-6 py-4 text-slate-500">
+                                                    {task.scheduledAt ? new Date(task.scheduledAt).toLocaleString() : '-'}
+                                                </td>
+                                                <td className="px-6 py-4 text-slate-400 text-xs">
+                                                    {new Date(task.createdAt).toLocaleString()}
+                                                </td>
+                                                <td className="px-6 py-4 text-right">
+                                                    <button
+                                                        onClick={() => handleDelete(task.id)}
+                                                        className="p-1.5 text-slate-400 hover:text-red-500 hover:bg-red-50 rounded-lg transition-colors"
+                                                        title={t('delete')}
+                                                    >
+                                                        <Trash2 size={16} />
+                                                    </button>
+                                                </td>
+                                            </tr>
+                                        ))}
+                                    </tbody>
+                                </table>
+                            </div>
+                        </div>
+                    )}
+                </div>
+            </div>
+        </div>
+    );
+};

+ 352 - 134
web/components/views/KnowledgeBaseView.tsx

@@ -25,7 +25,6 @@ import { useConfirm } from '../../contexts/ConfirmContext'
 import { ChunkInfoDrawer } from '../../components/ChunkInfoDrawer'
 import {
     Search,
-    Filter,
     Plus,
     FileText,
     Image as ImageIcon,
@@ -35,18 +34,20 @@ import {
     RefreshCw,
     Eye,
     Trash2,
-    RotateCcw,
     Settings,
-    MoreVertical,
     Folder,
     Hash,
-    ChevronRight,
     Tag,
     Layers,
-    MoreHorizontal
+    ChevronRight,
+    ChevronDown,
+    FolderInput,
+    Box,
 } from 'lucide-react'
 import { motion, AnimatePresence } from 'framer-motion'
 import { KB_ALLOWED_EXTENSIONS, IMAGE_MIME_TYPES, isExtensionAllowed, isFormatSupportedForPreview } from '../../constants/fileSupport'
+import { ImportFolderDrawer } from '../../components/ImportFolderDrawer'
+import { ImportTasksDrawer } from '../../components/drawers/ImportTasksDrawer'
 
 interface KnowledgeBaseViewProps {
     authToken: string;
@@ -56,6 +57,166 @@ interface KnowledgeBaseViewProps {
     isAdmin?: boolean;
 }
 
+/** Flatten a tree of groups into a flat list (for file counts, filtering, etc.) */
+function flattenGroups(groups: KnowledgeGroup[]): (KnowledgeGroup & { depth?: number })[] {
+    const result: (KnowledgeGroup & { depth?: number })[] = [];
+    function walk(items: KnowledgeGroup[], depth = 0) {
+        for (const g of items) {
+            result.push({ ...g, depth });
+            if (g.children?.length) walk(g.children, depth + 1);
+        }
+    }
+    walk(groups);
+    return result;
+}
+
+/** Recursively collect all descendant group IDs (including self) */
+function collectGroupIds(group: KnowledgeGroup): string[] {
+    const ids = [group.id];
+    if (group.children?.length) {
+        for (const child of group.children) {
+            ids.push(...collectGroupIds(child));
+        }
+    }
+    return ids;
+}
+
+// ---- Tree node component ----
+interface GroupTreeNodeProps {
+    group: KnowledgeGroup;
+    selectedGroupId?: string;
+    onSelect: (groupId: string) => void;
+    isAdmin: boolean;
+    onEdit: (group: KnowledgeGroup) => void;
+    onDelete: (group: KnowledgeGroup) => void;
+    depth?: number;
+}
+
+const GroupTreeNode: React.FC<GroupTreeNodeProps> = ({
+    group,
+    selectedGroupId,
+    onSelect,
+    isAdmin,
+    onEdit,
+    onDelete,
+    depth = 0,
+}) => {
+    const hasChildren = group.children && group.children.length > 0;
+    const isSelected = selectedGroupId === group.id ||
+        (hasChildren && group.children!.some(c => collectGroupIds(c).includes(selectedGroupId || '')));
+    const [collapsed, setCollapsed] = useState(false);
+
+    // Auto-expand if a child is selected
+    useEffect(() => {
+        if (selectedGroupId && hasChildren) {
+            const allIds = collectGroupIds(group);
+            if (allIds.includes(selectedGroupId)) setCollapsed(false);
+        }
+    }, [selectedGroupId]);
+
+    return (
+        <div>
+            <div
+                className={`group flex items-center justify-between rounded-lg transition-colors ${selectedGroupId === group.id ? 'bg-blue-50 text-blue-700' : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'}`}
+                style={{ paddingLeft: `${depth * 12 + 12}px` }}
+            >
+                {/* Expand/collapse toggle */}
+                <div className="flex items-center flex-1">
+                    {hasChildren ? (
+                        <button
+                            onClick={(e) => { e.stopPropagation(); setCollapsed(c => !c); }}
+                            className="p-0.5 mr-1 shrink-0 text-slate-400 hover:text-slate-700"
+                        >
+                            {collapsed ? <ChevronRight size={12} /> : <ChevronDown size={12} />}
+                        </button>
+                    ) : (
+                        <span className="w-5 shrink-0" />
+                    )}
+                    <button
+                        onClick={() => onSelect(group.id)}
+                        className="flex-1 flex items-center gap-1.5 py-1.5 text-sm font-medium text-left whitespace-nowrap"
+                    >
+                        <Folder size={14} className={selectedGroupId === group.id ? 'text-blue-500 shrink-0' : 'text-slate-400 shrink-0'} />
+                        <span>{group.name}</span>
+                    </button>
+                </div>
+
+                {isAdmin && (
+                    <div className="opacity-0 group-hover:opacity-100 flex items-center gap-0.5 pr-2 shrink-0">
+                        <button
+                            onClick={() => onEdit(group)}
+                            className="p-1 text-slate-400 hover:text-blue-600 rounded"
+                        >
+                            <Settings size={11} />
+                        </button>
+                        <button
+                            onClick={() => onDelete(group)}
+                            className="p-1 text-slate-400 hover:text-red-500 rounded"
+                        >
+                            <Trash2 size={11} />
+                        </button>
+                    </div>
+                )}
+            </div>
+
+            {/* Children */}
+            {hasChildren && !collapsed && (
+                <div>
+                    {group.children!.map(child => (
+                        <GroupTreeNode
+                            key={child.id}
+                            group={child}
+                            selectedGroupId={selectedGroupId}
+                            onSelect={onSelect}
+                            isAdmin={isAdmin}
+                            onEdit={onEdit}
+                            onDelete={onDelete}
+                            depth={depth + 1}
+                        />
+                    ))}
+                </div>
+            )}
+        </div>
+    );
+};
+
+// ---- Pagination component ----
+interface PaginationProps {
+    currentPage: number;
+    totalPages: number;
+    totalItems: number;
+    pageSize: number;
+    onPageChange: (page: number) => void;
+    t: (key: string, ...args: any[]) => string;
+}
+
+const Pagination: React.FC<PaginationProps> = ({ currentPage, totalPages, totalItems, pageSize, onPageChange, t }) => {
+    if (totalItems === 0) return null;
+    const start = (currentPage - 1) * pageSize + 1;
+    const end = Math.min(currentPage * pageSize, totalItems);
+    return (
+        <div className="px-8 py-4 border-t border-slate-200/60 bg-white/50 backdrop-blur-md flex items-center justify-center gap-2 shrink-0">
+            <button
+                onClick={() => onPageChange(Math.max(1, currentPage - 1))}
+                disabled={currentPage === 1}
+                className="p-2 border border-slate-200 rounded-lg hover:bg-slate-50 disabled:opacity-30 transition-all font-medium text-slate-600 text-sm"
+            >
+                {t('previous')}
+            </button>
+            <div className="px-3 py-2 text-sm font-semibold text-slate-700">
+                {t('showingRange', start, end, totalItems)}
+            </div>
+            <button
+                onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}
+                disabled={currentPage === totalPages}
+                className="p-2 border border-slate-200 rounded-lg hover:bg-slate-50 disabled:opacity-30 transition-all font-medium text-slate-600 text-sm"
+            >
+                {t('next')}
+            </button>
+        </div>
+    );
+};
+
 export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
     const { authToken, modelConfigs = DEFAULT_MODELS, isAdmin = false } = props;
     const { showError, showWarning, showSuccess } = useToast()
@@ -64,7 +225,9 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
 
     // Data State
     const [files, setFiles] = useState<KnowledgeFile[]>([])
+    // groups is now a tree; flatGroups is the flattened version for lookups
     const [groups, setGroups] = useState<KnowledgeGroup[]>([])
+    const flatGroups = useMemo(() => flattenGroups(groups), [groups])
     const [settings, setSettings] = useState<AppSettings>(DEFAULT_SETTINGS)
     const [isLoadingSettings, setIsLoadingSettings] = useState(true)
     const [isLoadingFiles, setIsLoadingFiles] = useState(true)
@@ -78,20 +241,22 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
 
     // Filter & Pagination State
     const [filterName, setFilterName] = useState('')
-    const [filterGroup, setFilterGroup] = useState('all')
     const [filterStatus, setFilterStatus] = useState<'all' | 'ready' | 'indexing' | 'failed'>('all')
     const [currentPage, setCurrentPage] = useState(1)
-    const pageSize = 12 // Adjusted for card grid
+    const pageSize = 12
     const [chunkDrawer, setChunkDrawer] = useState<{ isOpen: boolean; fileId: string; fileName: string } | null>(null)
 
-    const [isAutoRefreshEnabled, setIsAutoRefreshEnabled] = useState(true)
+    const [isAutoRefreshEnabled] = useState(true)
     const [autoRefreshInterval] = useState<number>(5000)
 
     // Sidebar State
     const [selectedSidebarFilter, setSelectedSidebarFilter] = useState<{ type: 'all' | 'uncategorized' | 'group'; groupId?: string }>({ type: 'all' })
     const [isGroupModalOpen, setIsGroupModalOpen] = useState(false)
+    const [isImportDrawerOpen, setIsImportDrawerOpen] = useState(false);
+    const [isImportTasksDrawerOpen, setIsImportTasksDrawerOpen] = useState(false);
     const [editingGroup, setEditingGroup] = useState<KnowledgeGroup | null>(null)
     const [newGroupName, setNewGroupName] = useState('')
+    const [newGroupParentId, setNewGroupParentId] = useState<string | null>(null)
 
     const fetchAndSetSettings = useCallback(async () => {
         if (!authToken) return
@@ -217,14 +382,14 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
         try {
             const currentGroupIds = file.groups?.map(g => g.id) || [];
             const isAssigned = currentGroupIds.includes(groupId);
-            
+
             let newGroupIds: string[];
             if (isAssigned) {
                 newGroupIds = currentGroupIds.filter(id => id !== groupId);
             } else {
                 newGroupIds = [...currentGroupIds, groupId];
             }
-            
+
             await knowledgeGroupService.addFileToGroups(file.id, newGroupIds);
             await fetchAndSetFiles();
         } catch (error: any) {
@@ -245,26 +410,28 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
         }
     }
 
-
+    // Filtering: when a group is selected, include files in that group AND all descendant groups
     const filteredFiles = useMemo(() => {
         return files.filter(file => {
             const matchName = file.name.toLowerCase().includes(filterName.toLowerCase());
-            
-            // Updated filtering logic for sidebar
+
             let matchGroup = true;
             if (selectedSidebarFilter.type === 'uncategorized') {
                 matchGroup = !file.groups || file.groups.length === 0;
-            } else if (selectedSidebarFilter.type === 'group') {
-                matchGroup = file.groups?.some(g => g.id === selectedSidebarFilter.groupId) || false;
+            } else if (selectedSidebarFilter.type === 'group' && selectedSidebarFilter.groupId) {
+                // Find the selected group in the tree to collect all descendant IDs
+                const selectedGroup = flatGroups.find(g => g.id === selectedSidebarFilter.groupId);
+                const allIds = selectedGroup ? collectGroupIds(selectedGroup) : [selectedSidebarFilter.groupId];
+                matchGroup = file.groups?.some(g => allIds.includes(g.id)) || false;
             }
-            
+
             const matchStatus = filterStatus === 'all' ||
                 (filterStatus === 'ready' && (file.status === 'ready' || file.status === 'vectorized')) ||
                 (filterStatus === 'indexing' && (file.status === 'indexing' || file.status === 'pending' || file.status === 'extracted')) ||
                 (filterStatus === 'failed' && (file.status === 'failed' || file.status === 'error'));
             return matchName && matchGroup && matchStatus;
         });
-    }, [files, filterName, selectedSidebarFilter, filterStatus]);
+    }, [files, filterName, selectedSidebarFilter, filterStatus, flatGroups]);
 
     const totalPages = Math.ceil(filteredFiles.length / pageSize);
     const paginatedFiles = useMemo(() => {
@@ -274,7 +441,7 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
 
     useEffect(() => {
         setCurrentPage(1);
-    }, [filterName, filterGroup, filterStatus]);
+    }, [filterName, filterStatus, selectedSidebarFilter]);
 
     useEffect(() => {
         let intervalId: NodeJS.Timeout | null = null;
@@ -299,23 +466,24 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
         if (!newGroupName.trim()) return
         try {
             if (editingGroup) {
-                await knowledgeGroupService.updateGroup(editingGroup.id, { name: newGroupName })
+                await knowledgeGroupService.updateGroup(editingGroup.id, { name: newGroupName, parentId: newGroupParentId })
                 showSuccess(t('groupUpdated'))
             } else {
-                await knowledgeGroupService.createGroup({ name: newGroupName })
+                await knowledgeGroupService.createGroup({ name: newGroupName, parentId: newGroupParentId })
                 showSuccess(t('groupCreated'))
             }
             fetchAndSetGroups()
             setIsGroupModalOpen(false)
             setEditingGroup(null)
             setNewGroupName('')
+            setNewGroupParentId(null)
         } catch (error: any) {
             showError(t('actionFailed') + ': ' + error.message)
         }
     }
 
     const handleDeleteGroup = async (group: KnowledgeGroup) => {
-        if (!(await confirm(t('confirmDeleteGroup')))) return
+        if (!(await confirm(t('confirmDeleteGroup').replace('$1', group.name)))) return
         try {
             await knowledgeGroupService.deleteGroup(group.id)
             showSuccess(t('groupDeleted'))
@@ -328,6 +496,24 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
         }
     }
 
+    const openCreateGroup = (parentId?: string | null) => {
+        setEditingGroup(null);
+        setNewGroupName('');
+        setNewGroupParentId(parentId ?? null);
+        setIsGroupModalOpen(true);
+    }
+
+    const openEditGroup = (group: KnowledgeGroup) => {
+        setEditingGroup(group);
+        setNewGroupName(group.name);
+        setNewGroupParentId(group.parentId ?? null);
+        setIsGroupModalOpen(true);
+    }
+
+    const selectedGroupObj = selectedSidebarFilter.type === 'group'
+        ? flatGroups.find(g => g.id === selectedSidebarFilter.groupId)
+        : null;
+
     if (isLoadingSettings) {
         return (
             <div className='flex items-center justify-center min-h-[400px] w-full'>
@@ -340,8 +526,8 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
         <div className='flex flex-row h-full w-full bg-slate-50 overflow-hidden'>
             {/* Sidebar */}
             <div className="w-64 bg-white border-r border-slate-200 flex flex-col shrink-0">
-                <div className="p-6">
-                    <h2 className="text-sm font-semibold text-slate-400 uppercase tracking-wider mb-4">Catalog</h2>
+                <div className="p-6 flex flex-col min-h-0">
+                    <h2 className="text-sm font-semibold text-slate-400 uppercase tracking-wider mb-4">{t('navCatalog')}</h2>
                     <nav className="space-y-1">
                         <button
                             onClick={() => setSelectedSidebarFilter({ type: 'all' })}
@@ -349,7 +535,7 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
                         >
                             <div className="flex items-center gap-2">
                                 <Layers size={16} />
-                                <span>All Documents</span>
+                                <span>{t('allDocuments')}</span>
                             </div>
                             <span className="text-xs bg-slate-100 text-slate-500 px-1.5 py-0.5 rounded-full">{files.length}</span>
                         </button>
@@ -359,7 +545,7 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
                         >
                             <div className="flex items-center gap-2">
                                 <FileText size={16} />
-                                <span>Uncategorized</span>
+                                <span>{t('uncategorized')}</span>
                             </div>
                             <span className="text-xs bg-slate-100 text-slate-500 px-1.5 py-0.5 rounded-full">
                                 {files.filter(f => !f.groups || f.groups.length === 0).length}
@@ -367,45 +553,31 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
                         </button>
                     </nav>
 
-                    <div className="mt-8 flex items-center justify-between mb-4">
-                        <h2 className="text-sm font-semibold text-slate-400 uppercase tracking-wider">Categories</h2>
+                    <div className="mt-6 flex items-center justify-between mb-3">
+                        <h2 className="text-sm font-semibold text-slate-400 uppercase tracking-wider">{t('categories')}</h2>
                         {isAdmin && (
                             <button
-                                onClick={() => { setEditingGroup(null); setNewGroupName(''); setIsGroupModalOpen(true); }}
+                                onClick={() => openCreateGroup(null)}
                                 className="p-1 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded transition-all"
+                                title={t('createCategory') as string}
                             >
                                 <Plus size={14} />
                             </button>
                         )}
                     </div>
 
-                    <div className="space-y-1 overflow-y-auto max-h-[calc(100vh-320px)] pr-1">
+                    <div className="space-y-0.5 overflow-y-auto overflow-x-auto flex-1 pr-1 pb-4">
                         {groups.map(group => (
-                            <div key={group.id} className="group flex items-center justify-between group">
-                                <button
-                                    onClick={() => setSelectedSidebarFilter({ type: 'group', groupId: group.id })}
-                                    className={`flex-1 flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors text-left ${selectedSidebarFilter.groupId === group.id ? 'bg-blue-50 text-blue-700' : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'}`}
-                                >
-                                    <Hash size={16} className={selectedSidebarFilter.groupId === group.id ? 'text-blue-500' : 'text-slate-400'} />
-                                    <span className="truncate">{group.name}</span>
-                                </button>
-                                {isAdmin && (
-                                    <div className="opacity-0 group-hover:opacity-100 flex items-center gap-0.5 pr-2">
-                                        <button
-                                            onClick={() => { setEditingGroup(group); setNewGroupName(group.name); setIsGroupModalOpen(true); }}
-                                            className="p-1 text-slate-400 hover:text-blue-600"
-                                        >
-                                            <Settings size={12} />
-                                        </button>
-                                        <button
-                                            onClick={() => handleDeleteGroup(group)}
-                                            className="p-1 text-slate-400 hover:text-red-500"
-                                        >
-                                            <Trash2 size={12} />
-                                        </button>
-                                    </div>
-                                )}
-                            </div>
+                            <GroupTreeNode
+                                key={group.id}
+                                group={group}
+                                selectedGroupId={selectedSidebarFilter.type === 'group' ? selectedSidebarFilter.groupId : undefined}
+                                onSelect={(gId) => setSelectedSidebarFilter({ type: 'group', groupId: gId })}
+                                isAdmin={isAdmin}
+                                onEdit={openEditGroup}
+                                onDelete={handleDeleteGroup}
+                                depth={0}
+                            />
                         ))}
                     </div>
                 </div>
@@ -430,25 +602,50 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
                 <div className="px-8 pt-8 pb-6 flex items-start justify-between shrink-0">
                     <div>
                         <h1 className="text-2xl font-bold text-slate-900 leading-tight">
-                            {selectedSidebarFilter.type === 'all' ? 'Knowledge Base' :
-                             selectedSidebarFilter.type === 'uncategorized' ? 'Uncategorized Files' :
-                             groups.find(g => g.id === selectedSidebarFilter.groupId)?.name || 'Category'}
+                            {selectedSidebarFilter.type === 'all' ? t('kbManagement') :
+                                selectedSidebarFilter.type === 'uncategorized' ? t('uncategorizedFiles') :
+                                    selectedGroupObj?.name || t('category')}
                         </h1>
                         <p className="text-[15px] text-slate-500 mt-1">
-                            {selectedSidebarFilter.type === 'group' 
-                                ? groups.find(g => g.id === selectedSidebarFilter.groupId)?.description || 'Documents in this category.'
-                                : 'Manage your documents and data sources.'}
+                            {selectedSidebarFilter.type === 'group'
+                                ? selectedGroupObj?.description || t('kbManagementDesc')
+                                : t('kbManagementDesc')}
                         </p>
                     </div>
                     <div className="flex items-center gap-3">
                         {isAdmin && (
-                            <button
-                                onClick={() => fileInputRef.current?.click()}
-                                className="flex items-center gap-2 px-5 py-2.5 bg-blue-600 hover:bg-blue-700 text-white rounded-lg shadow-sm shadow-blue-100 transition-all font-semibold text-sm active:scale-95"
-                            >
-                                <Plus size={18} />
-                                Add Document
-                            </button>
+                            <>
+                                {selectedSidebarFilter.type === 'group' && (
+                                    <button
+                                        onClick={() => openCreateGroup(selectedSidebarFilter.groupId)}
+                                        className="flex items-center gap-2 px-4 py-2.5 bg-white border border-slate-200 text-slate-700 rounded-lg shadow-sm transition-all font-semibold text-sm active:scale-95 hover:bg-slate-50"
+                                    >
+                                        <Plus size={16} />
+                                        {t('addSubcategory')}
+                                    </button>
+                                )}
+                                <button
+                                    onClick={() => setIsImportTasksDrawerOpen(true)}
+                                    className="flex items-center gap-2 px-4 py-2.5 bg-white border border-slate-200 text-slate-700 rounded-lg shadow-sm transition-all font-semibold text-sm active:scale-95 hover:bg-slate-50"
+                                >
+                                    <Box size={18} className="text-indigo-600" />
+                                    {t('importTasksTitle')}
+                                </button>
+                                <button
+                                    onClick={() => setIsImportDrawerOpen(true)}
+                                    className="flex items-center gap-2 px-4 py-2.5 bg-white border border-slate-200 text-slate-700 rounded-lg shadow-sm transition-all font-semibold text-sm active:scale-95 hover:bg-slate-50"
+                                >
+                                    <FolderInput size={18} className="text-blue-600" />
+                                    {t('importFolder')}
+                                </button>
+                                <button
+                                    onClick={() => fileInputRef.current?.click()}
+                                    className="flex items-center gap-2 px-5 py-2.5 bg-blue-600 hover:bg-blue-700 text-white rounded-lg shadow-sm shadow-blue-100 transition-all font-semibold text-sm active:scale-95"
+                                >
+                                    <Plus size={18} />
+                                    {t('addFile')}
+                                </button>
+                            </>
                         )}
                     </div>
                 </div>
@@ -460,7 +657,7 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
                             <Search className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" size={16} />
                             <input
                                 type="text"
-                                placeholder="Filter by name..."
+                                placeholder={t('searchPlaceholder')}
                                 value={filterName}
                                 onChange={(e) => setFilterName(e.target.value)}
                                 className="w-full h-9 pl-9 pr-4 bg-white border border-slate-200 rounded-lg text-sm transition-all focus:ring-1 focus:ring-blue-500 focus:border-blue-500 outline-none"
@@ -473,13 +670,13 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
                             className="p-2 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all"
                             title="Refresh"
                         >
-                            <RefreshCw size={18} className={isAutoRefreshEnabled ? 'animate-spin' : ''} />
+                            <RefreshCw size={18} />
                         </button>
                     </div>
                 </div>
 
-                {/* Card Grid */}
-                <div className="flex-1 overflow-y-auto px-8 pb-8">
+                {/* File List */}
+                <div className="flex-1 overflow-y-auto px-8 pb-4">
                     {isLoadingFiles ? (
                         <div className="flex flex-col items-center justify-center py-20 gap-4">
                             <div className="w-10 h-10 border-2 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
@@ -517,8 +714,8 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
                                             <div className="flex items-center gap-2">
                                                 <p className="text-[13px] text-slate-500 truncate">
                                                     {file.status === 'ready' || file.status === 'vectorized'
-                                                        ? `Document processed and ready.`
-                                                        : `Processing... Currently in ${file.status} state.`
+                                                        ? t('statusReadyDesc')
+                                                        : t('statusIndexingDesc', file.status)
                                                     }
                                                 </p>
                                                 {file.groups && file.groups.length > 0 && (
@@ -536,7 +733,7 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
                                         {/* Meta & Actions */}
                                         <div className="flex items-center gap-6 shrink-0">
                                             <div className="text-[12px] font-medium text-slate-400 flex flex-col items-end">
-                                                <span>{new Date(file.createdAt || Date.now()).toLocaleDateString('ja-JP')}</span>
+                                                <span>{new Date(file.createdAt || Date.now()).toLocaleDateString()}</span>
                                                 <span>{formatBytes(file.size)}</span>
                                             </div>
 
@@ -549,42 +746,43 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
                                             <div className="flex items-center gap-1 opacity-100 md:opacity-0 group-hover:opacity-100 transition-opacity">
                                                 {isFormatSupportedForPreview(file.name) && (
                                                     <button onClick={() => setPdfPreview({ fileId: file.id, fileName: file.name })} className="p-1.5 text-slate-400 hover:text-blue-600 rounded-md bg-slate-50" title={t('preview') as string || 'Preview'}>
-                                                    <Eye size={16} />
-                                                </button>
-                                            )}
-                                            <div className="relative group">
-                                                <button className="p-1.5 text-slate-400 hover:text-blue-600 rounded-md bg-slate-50" title="Move to Category">
-                                                    <Tag size={16} />
-                                                </button>
-                                                <div className="absolute right-0 top-full mt-1 w-48 bg-white border border-slate-200 rounded-lg shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-20 overflow-hidden">
-                                                    <div className="p-2 border-b border-slate-100 bg-slate-50 text-[10px] font-bold text-slate-400 uppercase tracking-wider">
-                                                        Select Category
-                                                    </div>
-                                                    <div className="max-h-40 overflow-y-auto">
-                                                        <button
-                                                            onClick={() => knowledgeGroupService.addFileToGroups(file.id, []).then(fetchAndSetFiles)}
-                                                            className="w-full text-left px-3 py-2 text-xs text-slate-600 hover:bg-slate-50 flex items-center gap-2"
-                                                        >
-                                                            <Layers size={12} />
-                                                            None (Uncategorized)
-                                                        </button>
-                                                        {groups.map(g => (
+                                                        <Eye size={16} />
+                                                    </button>
+                                                )}
+                                                <div className="relative group/tag">
+                                                    <button className="p-1.5 text-slate-400 hover:text-blue-600 rounded-md bg-slate-50" title={t('groups') as string || 'Groups'}>
+                                                        <Tag size={16} />
+                                                    </button>
+                                                    <div className="absolute right-0 top-full mt-1 w-52 bg-white border border-slate-200 rounded-lg shadow-lg opacity-0 invisible group-hover/tag:opacity-100 group-hover/tag:visible transition-all z-20 overflow-hidden">
+                                                        <div className="p-2 border-b border-slate-100 bg-slate-50 text-[10px] font-bold text-slate-400 uppercase tracking-wider">
+                                                            {t('selectCategory')}
+                                                        </div>
+                                                        <div className="max-h-48 overflow-y-auto">
                                                             <button
-                                                                key={g.id}
-                                                                onClick={() => handleToggleFileCategory(file, g.id)}
-                                                                className="w-full text-left px-3 py-2 text-xs text-slate-600 hover:bg-slate-50 border-t border-slate-50 flex items-center justify-between"
+                                                                onClick={() => knowledgeGroupService.addFileToGroups(file.id, []).then(fetchAndSetFiles)}
+                                                                className="w-full text-left px-3 py-2 text-xs text-slate-600 hover:bg-slate-50 flex items-center gap-2"
                                                             >
-                                                                <div className="flex items-center gap-2 truncate">
-                                                                    <Hash size={12} className={file.groups?.some(fg => fg.id === g.id) ? 'text-blue-500' : 'text-slate-400'} />
-                                                                    <span className={file.groups?.some(fg => fg.id === g.id) ? 'text-blue-600 font-medium' : ''}>{g.name}</span>
-                                                                </div>
-                                                                {file.groups?.some(fg => fg.id === g.id) && <CheckCircle2 size={12} className="text-blue-600" />}
+                                                                <Layers size={12} />
+                                                                {t('noneUncategorized')}
                                                             </button>
-                                                        ))}
+                                                            {flatGroups.map(g => (
+                                                                <button
+                                                                    key={g.id}
+                                                                    onClick={() => handleToggleFileCategory(file, g.id)}
+                                                                    style={{ paddingLeft: `${(g.depth || 0) * 12 + 12}px` }}
+                                                                    className="w-full text-left pr-3 py-2 text-xs text-slate-600 hover:bg-slate-50 border-t border-slate-50 flex items-center justify-between"
+                                                                >
+                                                                    <div className="flex items-center gap-1.5 truncate">
+                                                                        <Hash size={12} className={file.groups?.some(fg => fg.id === g.id) ? 'text-blue-500 shrink-0' : 'text-slate-400 shrink-0'} />
+                                                                        <span className={file.groups?.some(fg => fg.id === g.id) ? 'text-blue-600 font-medium truncate' : 'truncate'}>{g.name}</span>
+                                                                    </div>
+                                                                    {file.groups?.some(fg => fg.id === g.id) && <CheckCircle2 size={12} className="text-blue-600 shrink-0 ml-2" />}
+                                                                </button>
+                                                            ))}
+                                                        </div>
                                                     </div>
                                                 </div>
-                                            </div>
-                                            {isAdmin && (
+                                                {isAdmin && (
                                                     <button onClick={() => handleRemoveFile(file.id)} className="p-1.5 text-slate-400 hover:text-red-500 rounded-md bg-slate-50" title={t('delete') as string || 'Delete'}>
                                                         <Trash2 size={16} />
                                                     </button>
@@ -603,27 +801,14 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
                 </div>
 
                 {/* Pagination */}
-                {totalPages > 1 && (
-                    <div className="px-8 py-4 border-t border-slate-200/60 bg-white/50 backdrop-blur-md flex items-center justify-center gap-2 shrink-0">
-                        <button
-                            onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
-                            disabled={currentPage === 1}
-                            className="p-2 border border-slate-200 rounded-lg hover:bg-slate-50 disabled:opacity-30 transition-all font-medium text-slate-600"
-                        >
-                            Previous
-                        </button>
-                        <div className="px-3 py-2 text-sm font-semibold text-slate-700">
-                            {currentPage} of {totalPages}
-                        </div>
-                        <button
-                            onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
-                            disabled={currentPage === totalPages}
-                            className="p-2 border border-slate-200 rounded-lg hover:bg-slate-50 disabled:opacity-30 transition-all font-medium text-slate-600"
-                        >
-                            Next
-                        </button>
-                    </div>
-                )}
+                <Pagination
+                    currentPage={currentPage}
+                    totalPages={totalPages}
+                    totalItems={filteredFiles.length}
+                    pageSize={pageSize}
+                    onPageChange={setCurrentPage}
+                    t={t}
+                />
             </div>
 
             <IndexingModalWithMode
@@ -636,7 +821,7 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
                 authToken={authToken}
             />
 
-            {/* Group Modal */}
+            {/* Group Create/Edit Modal */}
             <AnimatePresence>
                 {isGroupModalOpen && (
                     <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/40 backdrop-blur-sm">
@@ -648,38 +833,59 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
                         >
                             <div className="p-6">
                                 <h2 className="text-xl font-bold text-slate-900 mb-2">
-                                    {editingGroup ? 'Edit Category' : 'Create New Category'}
+                                    {editingGroup ? t('editCategory') : t('createCategory')}
                                 </h2>
                                 <p className="text-slate-500 text-sm mb-6">
-                                    Organize your documents with descriptive categories.
+                                    {t('categoryDesc')}
                                 </p>
 
                                 <div className="space-y-4">
                                     <div>
-                                        <label className="block text-sm font-medium text-slate-700 mb-1">Category Name</label>
+                                        <label className="block text-sm font-medium text-slate-700 mb-1">{t('categoryName')}</label>
                                         <input
                                             type="text"
                                             value={newGroupName}
                                             onChange={(e) => setNewGroupName(e.target.value)}
-                                            placeholder="e.g. Finance, Projects..."
+                                            placeholder={t('exampleResearch')}
                                             className="w-full h-11 px-4 bg-slate-50 border border-slate-200 rounded-xl outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
+                                            autoFocus
+                                            onKeyDown={(e) => e.key === 'Enter' && handleCreateOrUpdateGroup()}
                                         />
                                     </div>
+
+                                    {/* Parent category selector */}
+                                    <div>
+                                        <label className="block text-sm font-medium text-slate-700 mb-1">{t('parentCategory')}</label>
+                                        <select
+                                            value={newGroupParentId ?? ''}
+                                            onChange={(e) => setNewGroupParentId(e.target.value || null)}
+                                            className="w-full h-11 px-4 bg-slate-50 border border-slate-200 rounded-xl outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all text-sm"
+                                        >
+                                            <option value="">{t('noParentTopLevel')}</option>
+                                            {flatGroups
+                                                .filter(g => g.id !== editingGroup?.id) // don't allow self as parent
+                                                .map(g => (
+                                                    <option key={g.id} value={g.id}>
+                                                        {'\u00A0'.repeat((g.depth || 0) * 4)}{g.name}
+                                                    </option>
+                                                ))}
+                                        </select>
+                                    </div>
                                 </div>
                             </div>
 
                             <div className="px-6 py-4 bg-slate-50 border-t border-slate-100 flex justify-end gap-3">
                                 <button
-                                    onClick={() => setIsGroupModalOpen(false)}
+                                    onClick={() => { setIsGroupModalOpen(false); setEditingGroup(null); setNewGroupParentId(null); }}
                                     className="px-4 py-2 text-slate-600 font-medium hover:bg-slate-200/50 rounded-lg transition-all"
                                 >
-                                    Cancel
+                                    {t('cancel')}
                                 </button>
                                 <button
                                     onClick={handleCreateOrUpdateGroup}
                                     className="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-lg shadow-sm transition-all active:scale-95"
                                 >
-                                    {editingGroup ? 'Save Changes' : 'Create Category'}
+                                    {editingGroup ? t('saveChanges') : t('createCategoryBtn')}
                                 </button>
                             </div>
                         </motion.div>
@@ -706,6 +912,18 @@ export const KnowledgeBaseView: React.FC<KnowledgeBaseViewProps> = (props) => {
                 />
             )}
 
+            <ImportFolderDrawer
+                isOpen={isImportDrawerOpen}
+                onClose={() => setIsImportDrawerOpen(false)}
+                authToken={authToken}
+                onImportSuccess={() => fetchAndSetFiles()}
+            />
+
+            <ImportTasksDrawer
+                isOpen={isImportTasksDrawerOpen}
+                onClose={() => setIsImportTasksDrawerOpen(false)}
+                authToken={authToken}
+            />
         </div>
     )
 }

+ 23 - 23
web/components/views/MemosView.tsx

@@ -136,9 +136,9 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
             setShowAddCategory(false)
             setAddingSubCategoryId(null)
             fetchCategories()
-            showSuccess('Category created')
+            showSuccess(t('categoryCreated'))
         } catch (error: any) {
-            showError(`Failed to create category: ${error.message}`)
+            showError(`${t('failedToCreateCategory')}: ${error.message}`)
         }
     }
 
@@ -148,22 +148,22 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
             await noteCategoryService.update(authToken, id, editCategoryName.trim())
             setEditingCategoryId(null)
             fetchCategories()
-            showSuccess('Category updated')
+            showSuccess(t('groupUpdated'))
         } catch (error) {
-            showError('Failed to update category')
+            showError(t('actionFailed'))
         }
     }
 
     const handleDeleteCategory = async (e: React.MouseEvent, id: string) => {
         e.stopPropagation()
-        if (!(await confirm('Are you sure you want to delete this category? Sub-folders and notes will be affected.'))) return
+        if (!(await confirm(t('confirmDeleteCategory')))) return
         try {
             await noteCategoryService.delete(authToken, id)
             if (selectedCategoryId === id) setSelectedCategoryId(null)
             fetchCategories()
-            showSuccess('Category deleted')
+            showSuccess(t('groupDeleted'))
         } catch (error) {
-            showError('Failed to delete category')
+            showError(t('failedToDeleteCategory'))
         }
     }
 
@@ -227,7 +227,7 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
                                                 }
                                             }}
                                             className="p-1 hover:text-blue-600"
-                                            title="Add sub-folder"
+                                            title={t('subFolderPlaceholder')}
                                         >
                                             <FolderPlus size={12} />
                                         </button>
@@ -266,7 +266,7 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
                                         <input
                                             autoFocus
                                             className="flex-1 text-xs border-none outline-none p-0 focus:ring-0"
-                                            placeholder="Sub-folder..."
+                                            placeholder={t('subFolderPlaceholder')}
                                             value={newCategoryName}
                                             onChange={e => setNewCategoryName(e.target.value)}
                                             onBlur={() => !newCategoryName && setAddingSubCategoryId(null)}
@@ -291,7 +291,7 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
                         <button
                             onClick={() => setIsEditing(false)}
                             className="p-2 -ml-2 hover:bg-slate-100 rounded-lg text-slate-400 hover:text-slate-600 transition-colors"
-                            title="Back"
+                            title={t('back')}
                         >
                             <ArrowLeft size={20} />
                         </button>
@@ -300,13 +300,13 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
                                 {currentNote.id ? t('editNote') : t('newNote')}
                             </h2>
                             <div className="flex items-center gap-2 mt-1">
-                                <span className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Directory:</span>
+                                <span className="text-[10px] font-black text-slate-400 uppercase tracking-widest">{t('directoryLabel')}:</span>
                                 <select
                                     className="text-[11px] font-bold text-blue-600 bg-blue-50/50 px-2 py-0.5 rounded border-none outline-none focus:ring-0 cursor-pointer max-w-[150px] truncate"
                                     value={currentNote.categoryId || ''}
                                     onChange={(e) => setCurrentNote({ ...currentNote, categoryId: e.target.value || undefined })}
                                 >
-                                    <option value="">Uncategorized</option>
+                                    <option value="">{t('uncategorized')}</option>
                                     {categories.map(c => {
                                         const parent = categories.find(p => p.id === c.parentId)
                                         const grandparent = parent ? categories.find(gp => gp.id === parent.parentId) : null
@@ -327,7 +327,7 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
                             className="flex items-center gap-2 px-3 py-1.5 text-[13px] font-semibold text-slate-500 hover:bg-slate-50 rounded-lg transition-all"
                         >
                             {showPreview ? <EyeOff size={16} /> : <Eye size={16} />}
-                            {showPreview ? "Hide Preview" : "Show Preview"}
+                            {showPreview ? t('hidePreview') : t('showPreview')}
                         </button>
                         <button
                             onClick={handleSaveNote}
@@ -342,13 +342,13 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
                     <div className={`flex-1 flex flex-col p-8 gap-6 ${showPreview ? 'border-r border-slate-100' : ''}`}>
                         <input
                             type="text"
-                            placeholder="Note title..."
+                            placeholder={t('noteTitlePlaceholder')}
                             value={currentNote.title || ''}
                             onChange={(e) => setCurrentNote({ ...currentNote, title: e.target.value })}
                             className="text-2xl font-bold text-slate-900 bg-transparent border-none focus:ring-0 placeholder:text-slate-200 w-full"
                         />
                         <textarea
-                            placeholder="Start writing..."
+                            placeholder={t('startWritingPlaceholder')}
                             value={currentNote.content || ''}
                             onChange={(e) => setCurrentNote({ ...currentNote, content: e.target.value })}
                             className="flex-1 text-[15px] text-slate-700 bg-transparent border-none focus:ring-0 placeholder:text-slate-200 w-full resize-none leading-relaxed"
@@ -358,12 +358,12 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
                     {showPreview && (
                         <div className="flex-1 p-8 overflow-y-auto bg-slate-50/20">
                             <div className="prose prose-slate prose-sm max-w-none">
-                                <h1 className="text-2xl font-bold text-slate-900 mb-6">{currentNote.title || "Preview"}</h1>
+                                <h1 className="text-2xl font-bold text-slate-900 mb-6">{currentNote.title || t('previewHeader')}</h1>
                                 <ReactMarkdown
                                     remarkPlugins={[remarkGfm, remarkMath]}
                                     rehypePlugins={[rehypeKatex]}
                                 >
-                                    {currentNote.content || '*No content to preview*'}
+                                    {currentNote.content || t('noContentToPreview')}
                                 </ReactMarkdown>
                             </div>
                         </div>
@@ -379,7 +379,7 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
             <aside className="w-64 border-r border-slate-100 flex flex-col bg-slate-50/30 shrink-0">
                 <div className="p-6 pb-2">
                     <div className="flex items-center justify-between mb-4">
-                        <h2 className="text-[11px] font-black text-slate-400 uppercase tracking-widest">{t('personalNotebook') || 'Directories'}</h2>
+                        <h2 className="text-[11px] font-black text-slate-400 uppercase tracking-widest">{t('personalNotebook') || t('directoryLabel')}</h2>
                         <button
                             onClick={() => setShowAddCategory(true)}
                             className="p-1 hover:bg-slate-100 rounded-md text-slate-400 transition-colors"
@@ -394,7 +394,7 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
                             className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-all ${!selectedCategoryId ? 'bg-blue-50 text-blue-700 font-bold shadow-sm' : 'text-slate-500 hover:bg-slate-50'}`}
                         >
                             <Book size={16} className={!selectedCategoryId ? 'text-blue-600' : 'text-slate-400'} />
-                            All Notes
+                            {t('allNotes')}
                         </button>
                     </div>
                 </div>
@@ -406,7 +406,7 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
                                 <input
                                     autoFocus
                                     className="flex-1 text-xs border-none outline-none p-0 focus:ring-0"
-                                    placeholder="Enter name..."
+                                    placeholder={t('enterNamePlaceholder')}
                                     value={newCategoryName}
                                     onChange={e => setNewCategoryName(e.target.value)}
                                     onBlur={() => !newCategoryName && setShowAddCategory(false)}
@@ -433,7 +433,7 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
                                 <>
                                     <ChevronRight size={16} className="text-slate-300" />
                                     <span className="text-2xl font-bold text-blue-600 truncate max-w-[200px]">
-                                        {categories.find(c => c.id === selectedCategoryId)?.name || 'Directory'}
+                                        {categories.find(c => c.id === selectedCategoryId)?.name || t('directoryLabel')}
                                     </span>
                                 </>
                             )}
@@ -457,7 +457,7 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
                         <Search className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" size={16} />
                         <input
                             type="text"
-                            placeholder="Filter notes..."
+                            placeholder={t('filterNotesPlaceholder')}
                             value={filterText}
                             onChange={(e) => setFilterText(e.target.value)}
                             className="w-full h-9 pl-9 pr-4 bg-white border border-slate-200 rounded-lg text-sm transition-all focus:ring-1 focus:ring-blue-500 focus:border-blue-500 outline-none"
@@ -534,7 +534,7 @@ export const MemosView: React.FC<MemosViewProps> = ({ authToken, isAdmin = false
                                             </div>
                                             {note.categoryId && (
                                                 <span className="text-[10px] bg-slate-100 text-slate-500 px-2 py-0.5 rounded font-medium">
-                                                    {categories.find(c => c.id === note.categoryId)?.name || 'Directory'}
+                                                    {categories.find(c => c.id === note.categoryId)?.name || t('directoryLabel')}
                                                 </span>
                                             )}
                                         </div>

+ 3 - 3
web/components/views/NotebookDetailView.tsx

@@ -91,7 +91,7 @@ export const NotebookDetailView: React.FC<NotebookDetailViewProps> = ({ authToke
                 }
                 const extension = file.name.split('.').pop() || ''
                 if (!isExtensionAllowed(extension, 'group')) {
-                    if (!(await confirm(t('confirmUnsupportedFile', extension || 'unknown')))) continue
+                    if (!(await confirm(t('confirmUnsupportedFile', extension || t('unknown'))))) continue
                 }
                 const rawFile = await readFile(file)
                 newPendingFiles.push(rawFile)
@@ -176,7 +176,7 @@ export const NotebookDetailView: React.FC<NotebookDetailViewProps> = ({ authToke
                             </h1>
                         </div>
                         <p className="text-[15px] text-slate-500 mt-1 truncate max-w-2xl">
-                            {notebook.description || "Browse and manage files within this group."}
+                            {notebook.description || t('browseManageFiles')}
                         </p>
                     </div>
                 </div>
@@ -206,7 +206,7 @@ export const NotebookDetailView: React.FC<NotebookDetailViewProps> = ({ authToke
                     <Search className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" size={16} />
                     <input
                         type="text"
-                        placeholder="Filter group files..."
+                        placeholder={t('filterGroupFiles')}
                         value={filterName}
                         onChange={(e) => setFilterName(e.target.value)}
                         className="w-full h-9 pl-9 pr-4 bg-white border border-slate-200 rounded-lg text-sm transition-all focus:ring-1 focus:ring-blue-500 focus:border-blue-500 outline-none"

+ 64 - 13
web/components/views/NotebooksView.tsx

@@ -1,7 +1,7 @@
-import React from 'react'
+import React, { useMemo } from 'react'
 import { knowledgeGroupService } from '../../services/knowledgeGroupService'
 import { KnowledgeGroup, UpdateGroupData, CreateGroupData } from '../../types'
-import { Plus, Book, MoreVertical, Library, MessageSquare, Trash2, Edit2, FolderInput } from 'lucide-react'
+import { Plus, Book, Library, MessageSquare, Trash2, Edit2, FolderInput, ChevronLeft, ChevronRight } from 'lucide-react'
 import { motion, AnimatePresence } from 'framer-motion'
 import { NotebookDetailView } from './NotebookDetailView'
 import { CreateNotebookDrawer } from '../CreateNotebookDrawer'
@@ -17,6 +17,21 @@ interface NotebooksViewProps {
     isAdmin?: boolean
 }
 
+/** Flatten a tree of groups into a flat list */
+function flattenGroups(groups: KnowledgeGroup[]): KnowledgeGroup[] {
+    const result: KnowledgeGroup[] = [];
+    function walk(items: KnowledgeGroup[]) {
+        for (const g of items) {
+            result.push(g);
+            if (g.children?.length) walk(g.children);
+        }
+    }
+    walk(groups);
+    return result;
+}
+
+const PAGE_SIZE = 12;
+
 export const NotebooksView: React.FC<NotebooksViewProps> = ({ authToken, onChatWithContext, isAdmin = false }) => {
     const { t } = useLanguage()
     const { showError } = useToast()
@@ -27,6 +42,15 @@ export const NotebooksView: React.FC<NotebooksViewProps> = ({ authToken, onChatW
     const [isCreateDrawerOpen, setIsCreateDrawerOpen] = React.useState(false)
     const [isImportDrawerOpen, setIsImportDrawerOpen] = React.useState(false)
     const [editingNotebook, setEditingNotebook] = React.useState<KnowledgeGroup | null>(null)
+    const [currentPage, setCurrentPage] = React.useState(1)
+
+    // Flatten tree for display in the grid
+    const flatNotebooks = useMemo(() => flattenGroups(notebooks), [notebooks])
+    const totalPages = Math.ceil(flatNotebooks.length / PAGE_SIZE)
+    const paginatedNotebooks = useMemo(() => {
+        const start = (currentPage - 1) * PAGE_SIZE;
+        return flatNotebooks.slice(start, start + PAGE_SIZE);
+    }, [flatNotebooks, currentPage])
 
     const fetchNotebooks = async () => {
         try {
@@ -68,7 +92,8 @@ export const NotebooksView: React.FC<NotebooksViewProps> = ({ authToken, onChatW
         try {
             setIsLoading(true)
             await knowledgeGroupService.deleteGroup(id)
-            setNotebooks(prev => prev.filter(n => n.id !== id))
+            setNotebooks(prev => flattenGroups(prev).filter(n => n.id !== id) as any)
+            await fetchNotebooks()
         } catch (error) {
             console.error(error)
             showError(t('deleteFailed'))
@@ -103,7 +128,7 @@ export const NotebooksView: React.FC<NotebooksViewProps> = ({ authToken, onChatW
                             className="flex items-center gap-2 px-4 py-2.5 bg-white border border-slate-200 text-slate-700 text-sm font-semibold rounded-lg hover:bg-slate-50 hover:border-slate-300 transition-all active:scale-95 shadow-sm"
                         >
                             <FolderInput size={18} className="text-blue-600" />
-                            <span>Import Folder</span>
+                            <span>{t('importFolder')}</span>
                         </button>
                     )}
                     {isAdmin && (
@@ -112,22 +137,22 @@ export const NotebooksView: React.FC<NotebooksViewProps> = ({ authToken, onChatW
                             className="flex items-center gap-2 px-5 py-2.5 bg-blue-600 hover:bg-blue-700 text-white rounded-lg shadow-sm shadow-blue-100 transition-all font-semibold text-sm active:scale-95"
                         >
                             <Plus size={18} />
-                            <span>New Group</span>
+                            <span>{t('newGroup')}</span>
                         </button>
                     )}
                 </div>
             </div>
 
-            <div className="px-8 pb-8 flex-1 overflow-y-auto">
+            <div className="px-8 flex-1 overflow-y-auto pb-4">
                 {isLoading ? (
                     <div className="flex h-64 items-center justify-center">
                         <div className="w-8 h-8 border-2 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
                     </div>
-                ) : notebooks.length === 0 ? (
+                ) : flatNotebooks.length === 0 ? (
                     <div className="flex flex-col items-center justify-center py-32 border-2 border-dashed border-slate-200 rounded-2xl bg-white/50 text-center">
                         <Library className="w-12 h-12 text-slate-200 mx-auto mb-4" />
-                        <h3 className="text-slate-900 font-bold">No Knowledge Groups</h3>
-                        <p className="text-slate-500 text-sm mt-1">Create a group to start organizing your files.</p>
+                        <h3 className="text-slate-900 font-bold">{t('noKnowledgeGroups')}</h3>
+                        <p className="text-slate-500 text-sm mt-1">{t('createGroupDesc')}</p>
                         {isAdmin && (
                             <button
                                 onClick={() => setIsCreateDrawerOpen(true)}
@@ -140,7 +165,7 @@ export const NotebooksView: React.FC<NotebooksViewProps> = ({ authToken, onChatW
                 ) : (
                     <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 max-w-7xl mx-auto">
                         <AnimatePresence>
-                            {notebooks.map((notebook, index) => (
+                            {paginatedNotebooks.map((notebook) => (
                                 <motion.div
                                     key={notebook.id}
                                     layout
@@ -184,18 +209,19 @@ export const NotebooksView: React.FC<NotebooksViewProps> = ({ authToken, onChatW
                                         <div className="w-11 h-11 bg-blue-50 rounded-lg text-blue-600 shadow-sm border border-blue-100/30 flex items-center justify-center mb-4 transition-transform group-hover:scale-105">
                                             <Book size={20} />
                                         </div>
-                                        <h3 className="font-bold text-slate-900 text-[16px] mb-2 leading-tight group-hover:text-blue-600 transition-colors truncate">
+                                        <h3 className="font-bold text-slate-900 text-[16px] mb-1 leading-tight group-hover:text-blue-600 transition-colors truncate">
+                                            {notebook.parentId && <span className="text-slate-300 text-xs mr-1">↳</span>}
                                             {notebook.name}
                                         </h3>
                                         <p className="text-[13px] text-slate-500 leading-relaxed line-clamp-3 italic opacity-85">
-                                            {notebook.description || "No description provided."}
+                                            {notebook.description || t('noDescriptionProvided')}
                                         </p>
                                     </div>
 
                                     <div className="mt-auto pt-4 border-t border-slate-50 flex items-center justify-between">
                                         <div className="flex items-center gap-2 px-2.5 py-1 bg-slate-50 border border-slate-100 rounded-md">
                                             <span className="text-[11px] font-bold text-slate-700">{notebook.fileCount || 0}</span>
-                                            <span className="text-[11px] font-semibold text-slate-400 uppercase tracking-tight">Files</span>
+                                            <span className="text-[11px] font-semibold text-slate-400 uppercase tracking-tight">{t('files')}</span>
                                         </div>
                                         <span className="text-[11px] font-medium text-slate-300">
                                             {notebook.updatedAt ? new Date(notebook.updatedAt).toLocaleDateString() : ''}
@@ -208,6 +234,31 @@ export const NotebooksView: React.FC<NotebooksViewProps> = ({ authToken, onChatW
                 )}
             </div>
 
+            {/* Pagination: always show when there are notebooks */}
+            {flatNotebooks.length > 0 && (
+                <div className="px-8 py-4 border-t border-slate-200/60 bg-white/50 backdrop-blur-md flex items-center justify-center gap-2 shrink-0">
+                    <button
+                        onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
+                        disabled={currentPage === 1}
+                        className="p-2 border border-slate-200 rounded-lg hover:bg-slate-50 disabled:opacity-30 transition-all text-slate-600 flex items-center gap-1 text-sm font-medium"
+                    >
+                        <ChevronLeft size={16} />
+                        {t('previous')}
+                    </button>
+                    <div className="px-3 py-2 text-sm font-semibold text-slate-700">
+                        {t('showingRange', (currentPage - 1) * PAGE_SIZE + 1, Math.min(currentPage * PAGE_SIZE, flatNotebooks.length), flatNotebooks.length)}
+                    </div>
+                    <button
+                        onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
+                        disabled={currentPage === totalPages || totalPages === 0}
+                        className="p-2 border border-slate-200 rounded-lg hover:bg-slate-50 disabled:opacity-30 transition-all text-slate-600 flex items-center gap-1 text-sm font-medium"
+                    >
+                        {t('next')}
+                        <ChevronRight size={16} />
+                    </button>
+                </div>
+            )}
+
             {isCreateDrawerOpen && (
                 <CreateNotebookDrawer
                     isOpen={isCreateDrawerOpen}

+ 128 - 91
web/components/views/SettingsView.tsx

@@ -4,9 +4,10 @@ import { useLanguage } from '../../contexts/LanguageContext';
 import { X, Plus, Trash2, Edit2, Save, Cpu, Box, Loader2, User, Shield, Key, LogOut, Globe, Settings as SettingsIcon, ToggleLeft, ToggleRight, Database, Sparkles, ChevronRight, Lock, Building2, BookOpen, UserCircle, HardDrive, LayoutGrid } from 'lucide-react';
 import { motion, AnimatePresence } from 'framer-motion';
 import { userService } from '../../services/userService';
-// import { settingsService } from '../../services/settingsService';
+import { settingsService } from '../../services/settingsService';
 import { userSettingService } from '../../services/userSettingService';
 import { knowledgeGroupService } from '../../services/knowledgeGroupService';
+
 import { useConfirm } from '../../contexts/ConfirmContext';
 import { useToast } from '../../contexts/ToastContext';
 
@@ -20,7 +21,7 @@ interface SettingsViewProps {
     initialTab?: TabType;
 }
 
-type TabType = 'general' | 'user' | 'model' | 'tenants' | 'knowledge_base';
+type TabType = 'general' | 'user' | 'model' | 'tenants' | 'knowledge_base' | 'import_tasks';
 
 export const SettingsView: React.FC<SettingsViewProps> = ({
     models,
@@ -62,6 +63,8 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
     }
     const [users, setUsers] = useState<UserType[]>([]);
     const [isUserLoading, setIsUserLoading] = useState(false);
+    const [userPage, setUserPage] = useState(1);
+    const USER_PAGE_SIZE = 20;
     const [showAddUser, setShowAddUser] = useState(false);
     const [newUser, setNewUser] = useState({ username: '', password: '', role: 'USER' });
     const [userSuccess, setUserSuccess] = useState('');
@@ -93,20 +96,23 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
     }, [initialTab]);
 
     // ユーザー一覧の取得(ユーザータブがアクティブな場合)
+    // Data fetching on tab change
     useEffect(() => {
         if (activeTab === 'user') {
             fetchUsers();
         } else if (activeTab === 'general') {
             fetchSettingsAndGroups();
-        } else if (activeTab === 'model' && (isAdmin || currentUser?.role === 'SUPER_ADMIN')) {
-            // Model tab initialization
         } else if (activeTab === 'tenants' && currentUser?.role === 'SUPER_ADMIN') {
             fetchTenantsData();
             fetchUsers(); // Ensure users are loaded for admin binding
-        } else if (activeTab === 'knowledge_base' && (currentUser?.role === 'TENANT_ADMIN' || currentUser?.role === 'SUPER_ADMIN')) {
+        }
+
+        // Independent check for KB/Model settings to avoid being blocked by the branches above
+        if ((activeTab === 'knowledge_base' || activeTab === 'model') &&
+            (currentUser?.role === 'TENANT_ADMIN' || currentUser?.role === 'SUPER_ADMIN' || isAdmin)) {
             fetchKnowledgeBaseSettings();
         }
-    }, [activeTab, currentUser]);
+    }, [activeTab, currentUser, authToken, isAdmin]);
 
     const [kbSettings, setKbSettings] = useState<any>(null);
     const [localKbSettings, setLocalKbSettings] = useState<any>(null);
@@ -122,6 +128,11 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                 const data = await res.json();
                 setKbSettings(data);
                 setLocalKbSettings(data);
+                if (data.enabledModelIds) {
+                    setEnabledModelIds(data.enabledModelIds);
+                } else {
+                    setEnabledModelIds([]);
+                }
             }
         } catch (error) {
             console.error(error);
@@ -145,12 +156,12 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
             });
             if (res.ok) {
                 setKbSettings(localKbSettings);
-                showSuccess('Knowledge Base settings saved');
+                showSuccess(t('kbSettingsSaved'));
             } else {
-                showError('Failed to save settings');
+                showError(t('failedToSaveSettings'));
             }
         } catch (error) {
-            showError('Error saving settings');
+            showError(t('actionFailed'));
         } finally {
             setIsSavingKbSettings(false);
         }
@@ -304,7 +315,8 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                 body: JSON.stringify({ userId, role: 'USER' }),
             });
             if (res.ok) {
-                showSuccess('User added to organization');
+                showSuccess(t('userAddedToOrganization')); // Need to add this key? No, used generic success
+                showSuccess(t('confirm')); // Using t('confirm') for now or generic
                 fetchTenantMembers(tenantId);
                 fetchTenantsData();
                 fetchUsers();
@@ -477,7 +489,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
         if (!roleChangeUserData) return;
         try {
             await userService.updateUserInfo(roleChangeUserData.userId, { role: roleChangeUserData.newRole });
-            showSuccess('Role updated successfully.');
+            showSuccess(t('featureUpdated'));
             setRoleChangeUserData(null);
             fetchUsers();
         } catch (error: any) {
@@ -533,6 +545,9 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                     body: JSON.stringify({ enabledModelIds: newEnabledIds })
                 });
                 setEnabledModelIds(newEnabledIds);
+                // Keep localKbSettings in sync to prevent overwrite on save in KB tab
+                setLocalKbSettings(prev => prev ? { ...prev, enabledModelIds: newEnabledIds } : prev);
+                setKbSettings(prev => prev ? { ...prev, enabledModelIds: newEnabledIds } : prev);
                 showSuccess('Settings updated successfully');
             } catch (error) {
                 console.error(error);
@@ -793,7 +808,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
 
             <div className="grid grid-cols-1 gap-4 overflow-y-auto pr-2 scrollbar-hide">
                 <AnimatePresence>
-                    {users.map((user, index) => {
+                    {users.slice((userPage - 1) * USER_PAGE_SIZE, userPage * USER_PAGE_SIZE).map((user, index) => {
                         let IconComponent = User;
                         let iconColors = 'bg-slate-50 text-slate-400';
                         if (user.role === 'SUPER_ADMIN') {
@@ -850,7 +865,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                                         setRoleChangeUserData({ userId: user.id, newRole: user.role && user.role !== 'USER' ? user.role : (user.isAdmin ? 'TENANT_ADMIN' : 'USER') });
                                                     }}
                                                     className="p-2.5 rounded-xl text-slate-400 hover:text-indigo-600 hover:bg-indigo-50 transition-all cursor-pointer relative z-10"
-                                                    title="Edit Role"
+                                                    title={t('editCategory')} // Using editCategory as generic edit for now or need editRole
                                                 >
                                                     <Edit2 className="w-4.5 h-4.5" />
                                                 </button>
@@ -879,6 +894,33 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                     })}
                 </AnimatePresence>
             </div>
+
+            {/* User list pagination - show whenever there are users */}
+            {users.length > 0 && (
+                <div className="flex items-center justify-center gap-3 pt-4 border-t border-slate-100 mt-4">
+                    <button
+                        onClick={() => setUserPage(p => Math.max(1, p - 1))}
+                        disabled={userPage === 1}
+                        className="px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-xl hover:bg-slate-50 disabled:opacity-30 transition-all"
+                    >
+                        {t('previous')}
+                    </button>
+                    <span className="text-xs font-semibold text-slate-500">
+                        {t('showingRange',
+                            (userPage - 1) * USER_PAGE_SIZE + 1,
+                            Math.min(userPage * USER_PAGE_SIZE, users.length),
+                            users.length
+                        )}
+                    </span>
+                    <button
+                        onClick={() => setUserPage(p => Math.min(Math.ceil(users.length / USER_PAGE_SIZE), p + 1))}
+                        disabled={userPage === Math.ceil(users.length / USER_PAGE_SIZE)}
+                        className="px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-xl hover:bg-slate-50 disabled:opacity-30 transition-all"
+                    >
+                        {t('next')}
+                    </button>
+                </div>
+            )}
         </div>
     );
 
@@ -888,47 +930,47 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                 <div className="p-6 bg-white border border-slate-200 rounded-3xl shadow-sm">
                     <div className="flex items-center gap-3 mb-2 text-blue-600">
                         <Building2 size={20} />
-                        <span className="text-xs font-black uppercase tracking-widest">Total Tenants</span>
+                        <span className="text-xs font-black uppercase tracking-widest">{t('totalTenants')}</span>
                     </div>
                     <p className="text-3xl font-black text-slate-900">{stats.tenants}</p>
                 </div>
                 <div className="p-6 bg-white border border-slate-200 rounded-3xl shadow-sm">
                     <div className="flex items-center gap-3 mb-2 text-indigo-600">
                         <User size={20} />
-                        <span className="text-xs font-black uppercase tracking-widest">System Users</span>
+                        <span className="text-xs font-black uppercase tracking-widest">{t('systemUsers')}</span>
                     </div>
                     <p className="text-3xl font-black text-slate-900">{stats.users}</p>
                 </div>
                 <div className="p-6 bg-emerald-50 border border-emerald-100 rounded-3xl shadow-sm">
                     <div className="flex items-center gap-3 mb-2 text-emerald-600">
                         <Sparkles size={20} />
-                        <span className="text-xs font-black uppercase tracking-widest">System Health</span>
+                        <span className="text-xs font-black uppercase tracking-widest">{t('systemHealth')}</span>
                     </div>
-                    <p className="text-xl font-bold text-emerald-700">Operational</p>
+                    <p className="text-xl font-bold text-emerald-700">{t('operational')}</p>
                 </div>
             </div>
 
             <div className="bg-white border border-slate-200 rounded-3xl shadow-xl overflow-hidden">
                 <div className="p-6 border-b border-slate-100 flex items-center justify-between">
                     <div>
-                        <h3 className="font-black text-slate-900 text-lg tracking-tight">Organization Management</h3>
-                        <p className="text-xs font-medium text-slate-400">Global tenant list and control</p>
+                        <h3 className="font-black text-slate-900 text-lg tracking-tight">{t('orgManagement')}</h3>
+                        <p className="text-xs font-medium text-slate-400">{t('globalTenantControl')}</p>
                     </div>
                     <button onClick={() => setShowCreateTenant(true)} className="flex items-center gap-2 px-4 py-2 bg-slate-900 text-white text-xs font-bold rounded-xl hover:bg-slate-700 transition-all">
-                        <Plus size={16} /> New Tenant
+                        <Plus size={16} /> {t('newTenant')}
                     </button>
                 </div>
                 <div className="overflow-x-auto">
                     <table className="w-full text-left">
                         <thead className="bg-slate-50/50 text-[10px] font-black text-slate-400 uppercase tracking-widest border-b border-slate-100">
                             <tr>
-                                <th className="px-6 py-4">Name</th>
-                                <th className="px-6 py-4">Domain</th>
-                                <th className="px-6 py-4">Admin</th>
-                                <th className="px-6 py-4">Members</th>
-                                <th className="px-6 py-4">Features</th>
-                                <th className="px-6 py-4">Created</th>
-                                <th className="px-6 py-4 text-right">Actions</th>
+                                <th className="px-6 py-4">{t('name')}</th>
+                                <th className="px-6 py-4">{t('domainOptional')}</th>
+                                <th className="px-6 py-4">{t('admin')}</th>
+                                <th className="px-6 py-4">{t('userList')}</th>
+                                <th className="px-6 py-4">{t('tabSettings')}</th>
+                                <th className="px-6 py-4">{t('createdAt')}</th>
+                                <th className="px-6 py-4 text-right">{t('actions')}</th>
                             </tr>
                         </thead>
                         <tbody className="divide-y divide-slate-100">
@@ -1325,7 +1367,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                             disabled={isSavingKbSettings || JSON.stringify(localKbSettings) === JSON.stringify(kbSettings)}
                             className="px-6 py-2 text-sm font-bold text-slate-500 hover:text-slate-700 disabled:opacity-30"
                         >
-                            Cancel
+                            {t('cancel')}
                         </button>
                         <button
                             onClick={handleSaveKbSettings}
@@ -1333,7 +1375,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                             className="flex items-center gap-2 px-8 py-2 bg-indigo-600 text-white text-sm font-black uppercase tracking-widest rounded-2xl hover:bg-indigo-700 shadow-lg shadow-indigo-100 transition-all active:scale-95 disabled:opacity-50"
                         >
                             {isSavingKbSettings ? <Loader2 size={16} className="animate-spin" /> : <Save size={16} />}
-                            Save Changes
+                            {t('saveChanges')}
                         </button>
                     </div>
 
@@ -1343,17 +1385,17 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                             <div className="w-8 h-8 rounded-xl bg-indigo-50 flex items-center justify-center text-indigo-600">
                                 <Cpu size={16} />
                             </div>
-                            Model Configuration
+                            {t('modelConfiguration')}
                         </div>
                         <div className="grid grid-cols-1 gap-6">
                             <div>
-                                <label className="block text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2 px-1">Default LLM Model</label>
+                                <label className="block text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2 px-1">{t('defaultLLMModel')}</label>
                                 <select
                                     value={localKbSettings.selectedLLMId || ''}
                                     onChange={(e) => handleUpdateKbSettings('selectedLLMId', e.target.value)}
                                     className="w-full px-4 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl text-sm font-medium outline-none focus:ring-4 focus:ring-indigo-500/10 transition-all cursor-pointer appearance-none"
                                 >
-                                    <option value="">Select LLM...</option>
+                                    <option value="">{t('selectLLM')}</option>
                                     {models.filter(m => m.type === ModelType.LLM).map(m => (
                                         <option key={m.id} value={m.id}>{m.name} ({m.modelId})</option>
                                     ))}
@@ -1361,26 +1403,26 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                             </div>
                             <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
                                 <div>
-                                    <label className="block text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2 px-1">Embedding Model</label>
+                                    <label className="block text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2 px-1">{t('embeddingModel')}</label>
                                     <select
                                         value={localKbSettings.selectedEmbeddingId || ''}
                                         onChange={(e) => handleUpdateKbSettings('selectedEmbeddingId', e.target.value)}
                                         className="w-full px-4 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl text-sm font-medium outline-none focus:ring-4 focus:ring-indigo-500/10 transition-all"
                                     >
-                                        <option value="">Select Embedding...</option>
+                                        <option value="">{t('selectEmbedding')}</option>
                                         {models.filter(m => m.type === ModelType.EMBEDDING).map(m => (
                                             <option key={m.id} value={m.id}>{m.name}</option>
                                         ))}
                                     </select>
                                 </div>
                                 <div>
-                                    <label className="block text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2 px-1">Rerank Model</label>
+                                    <label className="block text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2 px-1">{t('rerankModel')}</label>
                                     <select
                                         value={localKbSettings.selectedRerankId || ''}
                                         onChange={(e) => handleUpdateKbSettings('selectedRerankId', e.target.value)}
                                         className="w-full px-4 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl text-sm font-medium outline-none focus:ring-4 focus:ring-indigo-500/10 transition-all"
                                     >
-                                        <option value="">None</option>
+                                        <option value="">{t('none')}</option>
                                         {models.filter(m => m.type === ModelType.RERANK).map(m => (
                                             <option key={m.id} value={m.id}>{m.name}</option>
                                         ))}
@@ -1396,12 +1438,12 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                             <div className="w-8 h-8 rounded-xl bg-orange-50 flex items-center justify-center text-orange-600">
                                 <BookOpen size={16} />
                             </div>
-                            Indexing & Chunking Configuration
+                            {t('indexingChunkingConfig')}
                         </div>
                         <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
                             <div>
                                 <div className="flex justify-between mb-3 px-1">
-                                    <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Chunk Size (Tokens)</label>
+                                    <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">{t('chunkSize')}</label>
                                     <span className="text-sm font-black text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-lg">{localKbSettings.chunkSize || 1000}</span>
                                 </div>
                                 <input
@@ -1416,7 +1458,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                             </div>
                             <div>
                                 <div className="flex justify-between mb-3 px-1">
-                                    <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Chunk Overlap</label>
+                                    <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">{t('chunkOverlap')}</label>
                                     <span className="text-sm font-black text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-lg">{localKbSettings.chunkOverlap || 100}</span>
                                 </div>
                                 <input
@@ -1430,20 +1472,20 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                 />
                             </div>
                         </div>
-                    </section>
+                    </section >
 
                     {/* Chat Hyperparameters */}
-                    <section className="bg-white/80 backdrop-blur-md p-8 rounded-3xl border border-slate-200/50 shadow-sm space-y-6">
+                    < section className="bg-white/80 backdrop-blur-md p-8 rounded-3xl border border-slate-200/50 shadow-sm space-y-6" >
                         <div className="flex items-center gap-3 text-slate-900 font-black uppercase tracking-widest text-[11px] border-b border-slate-100 pb-4">
                             <div className="w-8 h-8 rounded-xl bg-pink-50 flex items-center justify-center text-pink-600">
                                 <Sparkles size={16} />
                             </div>
-                            Chat Hyperparameters
+                            {t('chatHyperparameters')}
                         </div>
                         <div className="space-y-8">
                             <div>
                                 <div className="flex justify-between mb-3 px-1">
-                                    <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Temperature</label>
+                                    <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">{t('temperature')}</label>
                                     <span className="text-sm font-black text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-lg">{localKbSettings.temperature}</span>
                                 </div>
                                 <input
@@ -1456,12 +1498,12 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                     className="w-full h-2 bg-slate-100 rounded-lg appearance-none cursor-pointer accent-indigo-600"
                                 />
                                 <div className="flex justify-between mt-2 px-1 text-[9px] font-bold text-slate-300 uppercase tracking-tighter">
-                                    <span>Precise</span>
-                                    <span>Creative</span>
+                                    <span>{t('precise')}</span>
+                                    <span>{t('creative')}</span>
                                 </div>
                             </div>
                             <div>
-                                <label className="block text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2 px-1">Max Response Tokens</label>
+                                <label className="block text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2 px-1">{t('maxResponseTokens')}</label>
                                 <input
                                     type="number"
                                     value={localKbSettings.maxTokens || 2000}
@@ -1470,21 +1512,21 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                 />
                             </div>
                         </div>
-                    </section>
+                    </section >
 
                     {/* Retrieval & Search Settings */}
-                    <section className="bg-white/80 backdrop-blur-md p-8 rounded-3xl border border-slate-200/50 shadow-sm space-y-6">
+                    < section className="bg-white/80 backdrop-blur-md p-8 rounded-3xl border border-slate-200/50 shadow-sm space-y-6" >
                         <div className="flex items-center gap-3 text-slate-900 font-black uppercase tracking-widest text-[11px] border-b border-slate-100 pb-4">
                             <div className="w-8 h-8 rounded-xl bg-emerald-50 flex items-center justify-center text-emerald-600">
                                 <Database size={16} />
                             </div>
-                            Retrieval & Search Settings
+                            {t('retrievalSearchSettings')}
                         </div>
                         <div className="space-y-8">
                             <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
                                 <div>
                                     <div className="flex justify-between mb-3 px-1">
-                                        <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Top-K Results</label>
+                                        <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">{t('topK')}</label>
                                         <span className="text-sm font-black text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-lg">{localKbSettings.topK}</span>
                                     </div>
                                     <input
@@ -1499,7 +1541,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                 </div>
                                 <div>
                                     <div className="flex justify-between mb-3 px-1">
-                                        <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Similarity Threshold</label>
+                                        <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">{t('similarityThreshold')}</label>
                                         <span className="text-sm font-black text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-lg">{localKbSettings.similarityThreshold}</span>
                                     </div>
                                     <input
@@ -1517,8 +1559,8 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                             <div className="space-y-4 pt-4 border-t border-slate-100">
                                 <div className="flex items-center justify-between p-5 bg-slate-50/50 rounded-2xl border border-slate-200/30 transition-all hover:bg-white hover:border-indigo-100">
                                     <div>
-                                        <div className="text-sm font-bold text-slate-800">Enable Hybrid Search</div>
-                                        <div className="text-[10px] text-slate-400 font-medium">Combine vector and full-text search results.</div>
+                                        <div className="text-sm font-bold text-slate-800">{t('enableHybridSearch')}</div>
+                                        <div className="text-[10px] text-slate-400 font-medium">{t('hybridSearchDesc')}</div>
                                     </div>
                                     <button
                                         onClick={() => handleUpdateKbSettings('enableFullTextSearch', !localKbSettings.enableFullTextSearch)}
@@ -1535,7 +1577,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                         className="p-5 bg-indigo-50/30 rounded-2xl border border-indigo-100/50 space-y-4"
                                     >
                                         <div className="flex justify-between mb-2 px-1">
-                                            <label className="text-[10px] font-black text-indigo-400 uppercase tracking-widest">Hybrid Weight (Vector vs Text)</label>
+                                            <label className="text-[10px] font-black text-indigo-400 uppercase tracking-widest">{t('hybridWeight')}</label>
                                             <span className="text-sm font-black text-indigo-600">{localKbSettings.hybridVectorWeight || 0.5}</span>
                                         </div>
                                         <input
@@ -1548,8 +1590,8 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                             className="w-full h-2 bg-indigo-100 rounded-lg appearance-none cursor-pointer accent-indigo-600"
                                         />
                                         <div className="flex justify-between mt-1 px-1 text-[9px] font-bold text-indigo-300 uppercase">
-                                            <span>Pure Text</span>
-                                            <span>Pure Vector</span>
+                                            <span>{t('pureText')}</span>
+                                            <span>{t('pureVector')}</span>
                                         </div>
                                     </motion.div>
                                 )}
@@ -1557,8 +1599,8 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                 <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                                     <div className="flex items-center justify-between p-5 bg-slate-50/50 rounded-2xl border border-slate-200/30 transition-all hover:bg-white hover:border-indigo-100">
                                         <div>
-                                            <div className="text-sm font-bold text-slate-800">Enable Query Expansion</div>
-                                            <div className="text-[10px] text-slate-400 font-medium">Rewrites query for better recall.</div>
+                                            <div className="text-sm font-bold text-slate-800">{t('enableQueryExpansion')}</div>
+                                            <div className="text-[10px] text-slate-400 font-medium">{t('queryExpansionDesc')}</div>
                                         </div>
                                         <button
                                             onClick={() => handleUpdateKbSettings('enableQueryExpansion', !localKbSettings.enableQueryExpansion)}
@@ -1570,8 +1612,8 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
 
                                     <div className="flex items-center justify-between p-5 bg-slate-50/50 rounded-2xl border border-slate-200/30 transition-all hover:bg-white hover:border-indigo-100">
                                         <div>
-                                            <div className="text-sm font-bold text-slate-800">Enable HyDE</div>
-                                            <div className="text-[10px] text-slate-400 font-medium">Hypothetical Document Embeddings.</div>
+                                            <div className="text-sm font-bold text-slate-800">{t('enableHyDE')}</div>
+                                            <div className="text-[10px] text-slate-400 font-medium">{t('hydeDesc')}</div>
                                         </div>
                                         <button
                                             onClick={() => handleUpdateKbSettings('enableHyDE', !localKbSettings.enableHyDE)}
@@ -1584,8 +1626,8 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
 
                                 <div className="flex items-center justify-between p-5 bg-slate-50/50 rounded-2xl border border-slate-200/30 transition-all hover:bg-white hover:border-indigo-100">
                                     <div>
-                                        <div className="text-sm font-bold text-slate-800">Enable Reranking</div>
-                                        <div className="text-[10px] text-slate-400 font-medium">Re-score search results for higher accuracy.</div>
+                                        <div className="text-sm font-bold text-slate-800">{t('enableReranking')}</div>
+                                        <div className="text-[10px] text-slate-400 font-medium">{t('rerankingDesc')}</div>
                                     </div>
                                     <button
                                         onClick={() => handleUpdateKbSettings('enableRerank', !localKbSettings.enableRerank)}
@@ -1602,7 +1644,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                         className="p-5 bg-indigo-50/30 rounded-2xl border border-indigo-100/50 space-y-4"
                                     >
                                         <div className="flex justify-between mb-2 px-1">
-                                            <label className="text-[10px] font-black text-indigo-400 uppercase tracking-widest">Rerank Similarity Threshold</label>
+                                            <label className="text-[10px] font-black text-indigo-400 uppercase tracking-widest">{t('rerankSimilarityThreshold')}</label>
                                             <span className="text-sm font-black text-indigo-600">{localKbSettings.rerankSimilarityThreshold || 0.5}</span>
                                         </div>
                                         <input
@@ -1615,8 +1657,8 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                             className="w-full h-2 bg-indigo-100 rounded-lg appearance-none cursor-pointer accent-indigo-600"
                                         />
                                         <div className="flex justify-between mt-1 px-1 text-[9px] font-bold text-indigo-300 uppercase">
-                                            <span>Broad</span>
-                                            <span>Strict</span>
+                                            <span>{t('broad')}</span>
+                                            <span>{t('strict')}</span>
                                         </div>
                                     </motion.div>
                                 )}
@@ -1700,11 +1742,11 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                     {modelFormData.type === ModelType.EMBEDDING && (
                         <div className="grid grid-cols-2 gap-6 p-6 bg-slate-50 rounded-3xl border border-slate-200/50">
                             <div className="space-y-2">
-                                <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest px-1">Max Input</label>
+                                <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest px-1">{t('maxInput')}</label>
                                 <input type="number" className="w-full px-4 py-3 bg-white border border-slate-200 rounded-xl text-sm font-bold" value={modelFormData.maxInputTokens || 8191} onChange={e => setModelFormData({ ...modelFormData, maxInputTokens: parseInt(e.target.value) })} />
                             </div>
                             <div className="space-y-2">
-                                <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest px-1">Dimensions</label>
+                                <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest px-1">{t('dimensions')}</label>
                                 <input type="number" className="w-full px-4 py-3 bg-white border border-slate-200 rounded-xl text-sm font-bold" value={modelFormData.dimensions || 1536} onChange={e => setModelFormData({ ...modelFormData, dimensions: parseInt(e.target.value) })} />
                             </div>
                         </div>
@@ -1740,10 +1782,10 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                         <div className="flex gap-1 items-center bg-slate-50 p-1 rounded-xl border border-slate-100/50">
                                             <button
                                                 onClick={() => handleToggleModel(model)}
-                                                className={`p-1.5 rounded-lg transition-all ${((currentUser?.role === 'SUPER_ADMIN' && model.isEnabled !== false) || (currentUser?.role === 'TENANT_ADMIN' && enabledModelIds.includes(model.id))) ? 'text-indigo-600 bg-white shadow-sm' : 'text-slate-400 hover:text-slate-600'}`}
-                                                title={((currentUser?.role === 'SUPER_ADMIN' && model.isEnabled !== false) || (currentUser?.role === 'TENANT_ADMIN' && enabledModelIds.includes(model.id))) ? t('modelEnabled') : t('modelDisabled')}
+                                                className={`p-1.5 rounded-lg transition-all ${((currentUser?.role === 'SUPER_ADMIN' ? !!model.isEnabled : enabledModelIds.includes(model.id))) ? 'text-indigo-600 bg-white shadow-sm' : 'text-slate-400 hover:text-slate-600'}`}
+                                                title={((currentUser?.role === 'SUPER_ADMIN' ? !!model.isEnabled : enabledModelIds.includes(model.id))) ? t('modelEnabled') : t('modelDisabled')}
                                             >
-                                                {((currentUser?.role === 'SUPER_ADMIN' && model.isEnabled !== false) || (currentUser?.role === 'TENANT_ADMIN' && enabledModelIds.includes(model.id))) ? <ToggleRight size={24} /> : <ToggleLeft size={24} />}
+                                                {((currentUser?.role === 'SUPER_ADMIN' ? !!model.isEnabled : enabledModelIds.includes(model.id))) ? <ToggleRight size={24} /> : <ToggleLeft size={24} />}
                                             </button>
                                         </div>
                                     </div>
@@ -1758,7 +1800,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                             </span>
                                             {model.isDefault && (
                                                 <span className="text-[9px] font-black bg-amber-50 text-amber-600 px-2 py-0.5 rounded-lg uppercase tracking-wider border border-amber-100/50 flex items-center gap-1">
-                                                    <Sparkles size={8} /> Default
+                                                    <Sparkles size={8} /> {t('defaultBadge')}
                                                 </span>
                                             )}
                                         </div>
@@ -1772,18 +1814,18 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                         {model.type === ModelType.EMBEDDING && (
                                             <>
                                                 <div className="bg-slate-50/50 p-2.5 rounded-2xl border border-slate-100/50">
-                                                    <span className="block text-[8px] font-black text-slate-400 uppercase tracking-widest mb-0.5">Dims</span>
+                                                    <span className="block text-[8px] font-black text-slate-400 uppercase tracking-widest mb-0.5">{t('dims')}</span>
                                                     <span className="text-xs font-bold text-slate-700">{model.dimensions || '-'}</span>
                                                 </div>
                                                 <div className="bg-slate-50/50 p-2.5 rounded-2xl border border-slate-100/50">
-                                                    <span className="block text-[8px] font-black text-slate-400 uppercase tracking-widest mb-0.5">Ctx</span>
+                                                    <span className="block text-[8px] font-black text-slate-400 uppercase tracking-widest mb-0.5">{t('ctx')}</span>
                                                     <span className="text-xs font-bold text-slate-700">{model.maxInputTokens || '-'}</span>
                                                 </div>
                                             </>
                                         )}
                                         {model.type === ModelType.LLM && (
                                             <div className="col-span-2 bg-slate-50/50 p-2.5 rounded-2xl border border-slate-100/50 flex items-center justify-between">
-                                                <span className="text-[8px] font-black text-slate-400 uppercase tracking-widest">Base API</span>
+                                                <span className="text-[8px] font-black text-slate-400 uppercase tracking-widest">{t('baseApi')}</span>
                                                 <span className="text-[9px] font-mono font-medium text-slate-600 max-w-[140px] truncate">{model.baseUrl}</span>
                                             </div>
                                         )}
@@ -1793,7 +1835,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                                 <div className="flex items-center justify-between pt-4 border-t border-slate-100/50 relative z-10">
                                     <div className="flex items-center gap-1 text-[10px] font-bold text-slate-400">
                                         <SettingsIcon size={12} />
-                                        Configured
+                                        {t('configured')}
                                     </div>
                                     <div className="flex gap-2">
                                         {currentUser?.role === 'SUPER_ADMIN' && (
@@ -1839,9 +1881,8 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                 <div className="flex-1 overflow-y-auto p-3 space-y-1">
                     <button
                         onClick={() => setActiveTab('general')}
-                        className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all ${
-                            activeTab === 'general' ? 'bg-white text-indigo-600 shadow-sm border border-slate-200/60' : 'text-slate-600 hover:bg-slate-100'
-                        }`}
+                        className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all ${activeTab === 'general' ? 'bg-white text-indigo-600 shadow-sm border border-slate-200/60' : 'text-slate-600 hover:bg-slate-100'
+                            }`}
                     >
                         <SettingsIcon size={18} />
                         {t('generalSettings')}
@@ -1850,27 +1891,24 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                         <>
                             <button
                                 onClick={() => setActiveTab('user')}
-                                className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all ${
-                                    activeTab === 'user' ? 'bg-white text-indigo-600 shadow-sm border border-slate-200/60' : 'text-slate-600 hover:bg-slate-100'
-                                }`}
+                                className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all ${activeTab === 'user' ? 'bg-white text-indigo-600 shadow-sm border border-slate-200/60' : 'text-slate-600 hover:bg-slate-100'
+                                    }`}
                             >
                                 <UserCircle size={18} />
                                 {t('userManagement')}
                             </button>
                             <button
                                 onClick={() => setActiveTab('model')}
-                                className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all ${
-                                    activeTab === 'model' ? 'bg-white text-indigo-600 shadow-sm border border-slate-200/60' : 'text-slate-600 hover:bg-slate-100'
-                                }`}
+                                className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all ${activeTab === 'model' ? 'bg-white text-indigo-600 shadow-sm border border-slate-200/60' : 'text-slate-600 hover:bg-slate-100'
+                                    }`}
                             >
                                 <HardDrive size={18} />
                                 {t('modelManagement')}
                             </button>
                             <button
                                 onClick={() => setActiveTab('knowledge_base')}
-                                className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all ${
-                                    activeTab === 'knowledge_base' ? 'bg-white text-indigo-600 shadow-sm border border-slate-200/60' : 'text-slate-600 hover:bg-slate-100'
-                                }`}
+                                className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all ${activeTab === 'knowledge_base' ? 'bg-white text-indigo-600 shadow-sm border border-slate-200/60' : 'text-slate-600 hover:bg-slate-100'
+                                    }`}
                             >
                                 <Database size={18} />
                                 {t('sidebarTitle')}
@@ -1880,9 +1918,8 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                     {currentUser?.role === 'SUPER_ADMIN' && (
                         <button
                             onClick={() => setActiveTab('tenants')}
-                            className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all ${
-                                activeTab === 'tenants' ? 'bg-white text-indigo-600 shadow-sm border border-slate-200/60' : 'text-slate-600 hover:bg-slate-100'
-                            }`}
+                            className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all ${activeTab === 'tenants' ? 'bg-white text-indigo-600 shadow-sm border border-slate-200/60' : 'text-slate-600 hover:bg-slate-100'
+                                }`}
                         >
                             <LayoutGrid size={18} />
                             {t('navTenants')}
@@ -1899,7 +1936,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
                             {activeTab === 'general' ? t('generalSettings') : activeTab === 'user' ? t('userManagement') : activeTab === 'model' ? t('modelManagement') : activeTab === 'knowledge_base' ? t('sidebarTitle') : t('navTenants')}
                         </h1>
                         <p className="text-[15px] text-slate-500 mt-1">
-                            {activeTab === 'general' ? 'Manage your application preferences.' : activeTab === 'user' ? 'Manage access and accounts.' : activeTab === 'model' ? 'Configure global AI models.' : activeTab === 'knowledge_base' ? 'Technical configuration for indexing and chat parameters.' : 'Global system overview.'}
+                            {activeTab === 'general' ? t('generalSettingsSubtitle') : activeTab === 'user' ? t('userManagementSubtitle') : activeTab === 'model' ? t('modelManagementSubtitle') : activeTab === 'knowledge_base' ? t('kbSettingsSubtitle') : t('tenantsSubtitle')}
                         </p>
                     </div>
                 </div>

+ 6 - 0
web/postcss.config.js.bak

@@ -0,0 +1,6 @@
+export default {
+    plugins: {
+        tailwindcss: {},
+        autoprefixer: {},
+    },
+}

+ 10 - 0
web/services/importService.ts

@@ -46,5 +46,15 @@ export const importService = {
         });
         if (!response.ok) throw new Error('Failed to fetch import tasks');
         return response.json();
+    },
+
+    delete: async (token: string, id: string): Promise<void> => {
+        const response = await fetch(`${API_BASE_URL}/import-tasks/${id}`, {
+            method: 'DELETE',
+            headers: {
+                'Authorization': `Bearer ${token}`
+            }
+        });
+        if (!response.ok) throw new Error('Failed to delete import task');
     }
 };

+ 4 - 3
web/src/pages/workspace/SettingsPage.tsx

@@ -29,9 +29,10 @@ export default function SettingsPage({ initialTab }: SettingsPageProps) {
 
     const handleUpdateModels = useCallback(async (action: 'create' | 'update' | 'delete', model: ModelConfig) => {
         if (!apiKey) return;
-        if (action === 'create') await modelConfigService.create(apiKey, model);
-        else if (action === 'update') await modelConfigService.update(apiKey, model.id, model);
-        else if (action === 'delete') await modelConfigService.remove(apiKey, model.id);
+        const { id, ...data } = model;
+        if (action === 'create') await modelConfigService.create(apiKey, data);
+        else if (action === 'update') await modelConfigService.update(apiKey, id, data);
+        else if (action === 'delete') await modelConfigService.remove(apiKey, id);
         await fetchModels();
     }, [apiKey, fetchModels]);
 

+ 13 - 0
web/tailwind.config.js.bak

@@ -0,0 +1,13 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+    content: [
+        "./index.html",
+        "./**/*.{js,ts,jsx,tsx}",
+    ],
+    theme: {
+        extend: {},
+    },
+    plugins: [
+        require('@tailwindcss/typography'),
+    ],
+}

+ 4 - 0
web/types.ts

@@ -50,6 +50,8 @@ export interface KnowledgeGroup {
   description?: string;
   color: string;
   fileCount: number;
+  parentId?: string | null;
+  children?: KnowledgeGroup[];
   createdAt: string;
   updatedAt?: string;
 }
@@ -58,12 +60,14 @@ export interface CreateGroupData {
   name: string;
   description?: string;
   color?: string;
+  parentId?: string | null;
 }
 
 export interface UpdateGroupData {
   name?: string;
   description?: string;
   color?: string;
+  parentId?: string | null;
 }
 
 export interface KnowledgeFile {

+ 572 - 203
web/utils/translations.ts

@@ -12,6 +12,7 @@ export const translations = {
     aiCommandsError: "发生错误",
     registerButton: "注册",
     loginError: "密钥不能为空",
+    unknown: "未知",
     unknownError: "未知错误",
     langZh: "语言: 中文",
     langEn: "语言: English",
@@ -216,7 +217,7 @@ export const translations = {
     createUser: "创建用户",
     admin: "管理员",
     user: "普通用户",
-    adminUser: "设为管理员",  // 新增
+    adminUser: "设为管理员",  // 新增,
     confirmChange: "确认修改",
     changeUserPassword: "修改用户密码",
     enterNewPassword: "请输入新密码",
@@ -251,11 +252,6 @@ export const translations = {
     confirmClearKB: "警告:此操作将永久删除所有文件及其索引数据。\n\n确定要清空知识库吗?",
     kbCleared: "知识库已清空",
     clearFailed: "清空失败",
-    actionFailed: "操作失败",
-    groupCreated: "成功创建目录",
-    groupUpdated: "目录已更新",
-    groupDeleted: "目录已删除",
-    groupManagement: "目录管理",
     loginRequired: "请先登录",
     uploadErrors: "以下文件无法上传",
     uploadWarning: "$1 个文件已准备上传,$2 个文件被过滤",
@@ -381,8 +377,7 @@ export const translations = {
     passwordChangeSuccess: "密码修改成功",
     passwordChangeFailed: "密码修改失败",
     create: "创建",
-    validationFailedMsg:
-      '验证请求失败: $1。可能是跨域(CORS)限制或地址错误。您可以勾选"跳过验证"强制保存。',
+    validationFailedMsg: "validationFailedMsg",
 
     // Sidebar
     navChat: "对话",
@@ -390,6 +385,8 @@ export const translations = {
     navKnowledge: "知识库",
     navKnowledgeGroups: "知识组",
     navNotebook: "笔记本",
+    navAgent: "智能体",
+    navPlugin: "插件",
     notebookDesc: "记录您的个人想法和研究笔记。",
     newNote: "新建笔记",
     editNote: "编辑笔记",
@@ -601,75 +598,195 @@ export const translations = {
     noHistoryDesc: "开始一次对话来创建历史记录",
     loadMore: "加载更多",
     loadingHistoriesFailed: "加载搜索历史失败",
-    supportedFormatsInfo: "支持文档、图片及代码格式",
-
-    // Agent
-    navAgent: "智能体",
-    agentTitle: "我的智能体",
-    agentDesc: "管理和编排您的 AI 智能体应用。",
-    searchAgent: "搜索智能体...",
+    generalSettingsSubtitle: "管理您的应用程序首选项。",
+    userManagementSubtitle: "管理访问权限和帐户。",
+    modelManagementSubtitle: "配置全局 AI 模型。",
+    kbSettingsSubtitle: "索引和聊天参数的技术配置。",
+    tenantsSubtitle: "全局系统概览。",
+
+    allNotes: "所有笔记",
+    filterNotesPlaceholder: "筛选笔记...",
+    startWritingPlaceholder: "开始写作...",
+    previewHeader: "预览",
+    noContentToPreview: "没有可预览的内容",
+    hidePreview: "隐藏预览",
+    showPreview: "显示预览",
+    directoryLabel: "目录",
+    uncategorized: "未分类",
+    enterNamePlaceholder: "输入名称...",
+    subFolderPlaceholder: "子文件夹...",
+    categoryCreated: "分类已创建",
+    failedToCreateCategory: "创建分类失败",
+    failedToDeleteCategory: "删除分类失败",
+    confirmDeleteCategory: "您确定要删除此分类吗?",
+    kbSettingsSaved: "检索与对话配置已保存",
+    failedToSaveSettings: "保存设置失败",
+    actionFailed: "操作失败",
+    userAddedToOrganization: "用户已添加到组织",
+    featureUpdated: "功能已更新",
+    roleTenantAdmin: "租户管理员",
+    roleRegularUser: "普通用户",
+    creatingRegularUser: "正在创建普通用户",
+    editUserRole: "修改用户角色",
+    targetRole: "目标角色",
+    editCategory: "编辑分类",
+    totalTenants: "总租户数",
+    systemUsers: "系统用户",
+    systemHealth: "系统健康",
+    operational: "运行正常",
+    orgManagement: "组织管理",
+    globalTenantControl: "全局租户控制",
+    newTenant: "新租户",
+    domainOptional: "域名 (可选)",
+    saveChanges: "保存修改",
+    modelConfiguration: "模型配置",
+    defaultLLMModel: "默认推理模型",
+    selectLLM: "选择 LLM",
+    selectEmbedding: "选择 Embedding",
+    rerankModel: "Rerank 模型",
+    none: "无",
+    indexingChunkingConfig: "索引与切片配置",
+    chatHyperparameters: "聊天超参数",
+    temperature: "随机性 (Temperature)",
+    precise: "精确",
+    creative: "创意",
+    maxResponseTokens: "最大响应标识 (Max Tokens)",
+    retrievalSearchSettings: "检索与搜索设置",
+    topK: "召回数量 (Top K)",
+    similarityThreshold: "相似度阈值",
+    enableHybridSearch: "启用混合检索",
+    hybridSearchDesc: "同时使用向量和全文检索以提高召回率",
+    hybridWeight: "混合权重 (0.0=全文, 1.0=向量)",
+    pureText: "纯文本",
+    pureVector: "纯向量",
+    enableQueryExpansion: "启用查询扩展",
+    queryExpansionDesc: "生成多个查询变体以提高覆盖率",
+    enableHyDE: "启用 HyDE",
+    hydeDesc: "生成假设回答以改善语义搜索",
+    enableReranking: "启用重排序 (Rerank)",
+    rerankingDesc: "使用 Rerank 模型对结果进行二次排序",
+    broad: "宽泛",
+    strict: "严格",
+    maxInput: "最大输入",
+    dimensions: "维度",
+    defaultBadge: "默认",
+    dims: "维度: $1",
+    ctx: "上下文: $1",
+    baseApi: "Base API: $1",
+    configured: "已配置",
+    groupUpdated: "分组已更新",
+    groupDeleted: "分组已删除",
+    groupCreated: "分组已创建",
+    navCatalog: "目录",
+    allDocuments: "所有文档",
+    categories: "分类",
+    uncategorizedFiles: "未分类文件",
+    category: "分类",
+    statusReadyDesc: "已索引可查询",
+    statusIndexingDesc: "正在建立词向量索引",
+    selectCategory: "选择分类",
+    noneUncategorized: "无未分类文件",
+    previous: "上一页",
+    next: "下一页",
+    createCategory: "创建分类",
+    categoryDesc: "描述您的知识分类",
+    categoryName: "分类名称",
+    createCategoryBtn: "立即创建",
+    newGroup: "新建分组",
+    noKnowledgeGroups: "暂无知识库分组",
+    createGroupDesc: "开始创建您的第一个知识库分组并上传相关文档。",
+    noDescriptionProvided: "未提供描述",
+    browseManageFiles: "浏览并管理该分组下的文件和笔记。",
+    filterGroupFiles: "根据名称搜索分组内文件...",
+    "2d": "2d",
+    Authorization: "Authorization",
+    a: "a",
+    agentTitle: "智能体中心",
+    agentDesc: "管理和运行您的 AI 助手,协助完成复杂任务。",
     createAgent: "创建智能体",
+    searchAgent: "搜索智能体...",
     statusRunning: "运行中",
-    statusStopped: "已停用",
-    btnChat: "对话",
+    statusStopped: "已停止",
+    updatedAtPrefix: "最后更新于 ",
+    btnChat: "开始对话",
+
+    // Agent Mock Data
+    agent1Name: "数据分析专家",
+    agent1Desc: "精通 SQL 和数据可视化,能够从复杂数据中提取洞察。",
+    agent2Name: "代码审查助手",
+    agent2Desc: "自动检查代码质量,提供重构建议和性能优化方案。",
+    agent3Name: "学术论文润色",
+    agent3Desc: "专业的学术写作助手,帮助优化论文结构和语言表达。",
+    agent4Name: "法律顾问",
+    agent4Desc: "提供法律条文查询和基础法律建议,协助起草合同。",
+    agent5Name: "市场研究员",
+    agent5Desc: "分析行业趋势,生成竞争对手调研报告。",
+    agent6Name: "系统运维专家",
+    agent6Desc: "监控系统健康,自动处理常见告警和排障。",
+    agent7Name: "财务审计师",
+    agent7Desc: "自动化报表审计,识别财务风险和异常交易。",
+    agent1Time: "2 小时前",
+    agent2Time: "5 小时前",
+    agent3Time: "昨天",
+    agent4Time: "2 天前",
+    agent5Time: "3 天前",
+    agent6Time: "5 天前",
+    agent7Time: "1 周前",
 
-    // Plugin
-    navPlugin: "插件",
+    // Plugins
     pluginTitle: "插件中心",
-    pluginDesc: "发现和管理适用于您工作流的各类强大插件。",
+    pluginDesc: "扩展知识库的功能,集成外部工具和服务。",
     searchPlugin: "搜索插件...",
-    installPlugin: "安装",
+    installPlugin: "安装插件",
     installedPlugin: "已安装",
-    updatePlugin: "更新",
-
-    // Added translations
-    selectOrganization: "选择组织",
-    defaultTenant: "默认",
-    roleTenantAdmin: "租户管理员",
-    roleRegularUser: "普通用户",
-    creatingRegularUser: "创建普通用户",
-    editUserRole: "修改用户角色",
-    targetRole: "目标角色",
-    defaultBadge: "默认",
-    defaultSettingFailed: "默认设置失败",
+    updatePlugin: "有更新",
     pluginOfficial: "官方",
     pluginCommunity: "社区",
-    pluginBy: "开发者: ",
-    pluginConfig: "配置",
-    updatedAtPrefix: "更新于 ",
-    plugin1Name: "Jira 集成",
-    plugin1Desc: "无缝连接 Jira 敏捷开发面板,支持通过对话查询、创建和更新 Issue、Epic 等任务状态。",
-    plugin2Name: "Confluence 知识库",
-    plugin2Desc: "实时检索并读取 Confluence 团队知识库文档,为智能体提供上下文丰富的业务参考。",
-    plugin3Name: "GitLab 代码工具",
-    plugin3Desc: "直接与 GitLab 代码仓库交互,支持审查、提交分析、CI/CD 流水线状态等操作。",
-    plugin4Name: "网络搜索器",
-    plugin4Desc: "借助多引擎搜索引擎(Google/Bing等)突破大模型知识库限制,实时获取最新资讯。",
-    plugin5Name: "SQL 数据库洞察",
-    plugin5Desc: "安全连接到内部 MySQL/PostgreSQL 数据库,将自然语言转化为 SQL 查询并在沙盒中执行分析。",
-    plugin6Name: "飞书/钉钉机器人",
-    plugin6Desc: "一键将智能体的执行结果同步或发送到指定工作群,实现自动化的通知与审批流闭环。",
-    agent1Name: "AI人才评价智能体",
-    agent1Desc: "基于多维度数据自动评估AI人才能力模型,生成客观、全面的人才画像与发展建议。",
-    agent1Time: "刚刚",
-    agent2Name: "代码评审智能体",
-    agent2Desc: "深度扫描代码提交内容,识别逻辑漏洞、规范问题及安全风险,并自动提供优化建议。",
-    agent2Time: "10 分钟前",
-    agent3Name: "设计评审智能体",
-    agent3Desc: "自动分析架构设计文档与UML图,校验设计模式应用合理性,发现潜在的系统瓶颈。",
-    agent3Time: "2 小时前",
-    agent4Name: "测试用例生成智能体",
-    agent4Desc: "基于需求文档与接口定义,自动生成高覆盖率的单元测试、集成测试及端到端测试用例。",
-    agent4Time: "昨天",
-    agent5Name: "品质数据收集智能体",
-    agent5Desc: "自动对接缺陷追踪系统与监控平台,聚合产品质量数据,生成质量趋势分析报告。",
-    agent5Time: "2 天前",
-    agent6Name: "生产性收集智能体",
-    agent6Desc: "实时统计研发流程数据,分析团队产出效率、代码提交频率及任务流转周期。",
-    agent6Time: "5 天前",
-    agent7Name: "项目报告生成智能体",
-    agent7Desc: "深度集成项目管理数据,自动分析里程碑节点,一键生成高质量的项目进度与复盘报告。",
-    agent7Time: "1 周前",
+    pluginBy: "由 ",
+    pluginConfig: "插件配置",
+
+    // Plugin Mock Data
+    plugin1Name: "Web 搜索",
+    plugin1Desc: "赋予 AI 实时访问互联网的能力,获取最新信息。",
+    plugin2Name: "PDF 文档解析",
+    plugin2Desc: "深度解析复杂 PDF 布局,提取表格和数学公式。",
+    plugin3Name: "GitHub 集成",
+    plugin3Desc: "直接访问 GitHub 仓库,进行代码提交和 issue 管理。",
+    plugin4Name: "Google 日历",
+    plugin4Desc: "同步您的日程安排,自动创建会议提醒。",
+    plugin5Name: "SQL 数据库连接",
+    plugin5Desc: "安全地连接到您的数据库,执行自然语言查询。",
+    plugin6Name: "Slack 通知",
+    plugin6Desc: "将 AI 生成的报告直接发送到指定的 Slack 频道。",
+
+    // Hierarchical categories new keys
+    addSubcategory: "添加子分类",
+    parentCategory: "父分类 (可选)",
+    noParentTopLevel: "无父分类(顶级)",
+    useHierarchyImport: "按文件夹层级创建分类",
+    useHierarchyImportDesc: "启用后将为每个子文件夹创建对应的子分类,并将文件导入到匹配的分类中。",
+
+    // Folder import drawer
+    importImmediate: "立即导入",
+    importScheduled: "定时导入",
+    lblServerPath: "服务器文件夹路径",
+    placeholderServerPath: "例如: /data/documents",
+    scheduledImportTip: "服务器端定时导入:服务器将在指定时间读取该路径下的文件并自动导入。请确保服务器有访问该路径的权限。",
+    lblScheduledTime: "执行时间",
+    scheduledTimeHint: "到达指定时间后,服务器将自动执行导入任务。",
+    scheduleImport: "创建定时任务",
+    scheduleTaskCreated: "定时导入任务已创建",
+    fillServerPath: "请输入服务器文件夹路径",
+    invalidDateTime: "请输入有效的日期时间",
+
+    // Import Tasks Drawer
+    importTasksTitle: "导入任务",
+    noTasksFound: "暂无任务",
+    sourcePath: "源路径",
+    targetGroup: "目标分组",
+    scheduledAt: "计划执行时间",
+    confirmDeleteTask: "确定要删除此导入任务记录吗?",
+    deleteTaskFailed: "删除任务记录失败",
   },
   en: {
     aiCommandsError: "An error occurred",
@@ -678,6 +795,7 @@ export const translations = {
     loginDesc: "Enter access key to enter the system",
     loginButton: "Enter System",
     loginError: "Key cannot be empty",
+    unknown: "Unknown",
     unknownError: "Unknown Error",
     usernamePlaceholder: "Username",
     passwordPlaceholder: "Password",
@@ -992,12 +1110,7 @@ export const translations = {
     confirmClearKB: "WARNING: This will permanently delete all files and indices.\n\nAre you sure you want to clear the knowledge base?",
     kbCleared: "Knowledge base cleared",
     clearFailed: "Clear failed",
-    actionFailed: "Action failed",
-    groupCreated: "Category created",
-    groupUpdated: "Category updated",
-    groupDeleted: "Category deleted",
-    groupManagement: "Category Management",
-    loginRequired: "Login required",
+    loginRequired: "Please login first",
     uploadErrors: "The following files could not be uploaded",
     uploadWarning: "$1 files ready to upload, $2 files filtered",
     uploadFailed: "Upload failed",
@@ -1124,8 +1237,7 @@ export const translations = {
     passwordChangeSuccess: "Password changed successfully",
     passwordChangeFailed: "Failed to change password",
     create: "Create",
-    validationFailedMsg:
-      "Validation failed: $1. Could be CORS or incorrect URL. Check 'Skip Validation' to force save.",
+    validationFailedMsg: "validationFailedMsg",
 
     // Sidebar
     navChat: "Chat",
@@ -1133,6 +1245,8 @@ export const translations = {
     navKnowledge: "Knowledge Base",
     navKnowledgeGroups: "Knowledge Groups",
     navNotebook: "Notebook",
+    navAgent: "Agents",
+    navPlugin: "Plugins",
     notebookDesc: "Capture your personal thoughts and research notes.",
     newNote: "New Note",
     editNote: "Edit Note",
@@ -1276,74 +1390,202 @@ export const translations = {
     loadMore: "Load More",
     loadingHistoriesFailed: "Failed to load search history",
     supportedFormatsInfo: "Supports documents, images and code formats",
-
-    // Agent
-    navAgent: "Agents",
-    agentTitle: "My Agents",
-    agentDesc: "Manage and orchestrate your AI agent applications.",
-    searchAgent: "Search agents...",
+    generalSettingsSubtitle: "Manage your application preferences.",
+    userManagementSubtitle: "Manage access and accounts.",
+    modelManagementSubtitle: "Configure global AI models.",
+    kbSettingsSubtitle: "Technical configuration for indexing and chat parameters.",
+    tenantsSubtitle: "Global system overview.",
+
+    allNotes: "All Notes",
+    filterNotesPlaceholder: "Filter notes...",
+    startWritingPlaceholder: "Start writing...",
+    previewHeader: "Preview",
+    noContentToPreview: "No content to preview",
+    hidePreview: "Hide Preview",
+    showPreview: "Show Preview",
+    directoryLabel: "Directory",
+    uncategorized: "Uncategorized",
+    enterNamePlaceholder: "Enter name...",
+    subFolderPlaceholder: "Sub-folder...",
+    categoryCreated: "Category created",
+    failedToCreateCategory: "Failed to create category",
+    failedToDeleteCategory: "Failed to delete category",
+    confirmDeleteCategory: "Are you sure you want to delete this category?",
+    groupUpdated: "Group updated",
+    groupDeleted: "Group deleted",
+    actionFailed: "Action failed",
+    kbSettingsSaved: "Knowledge base settings saved",
+    failedToSaveSettings: "Failed to save settings",
+    userAddedToOrganization: "User added to organization",
+    featureUpdated: "Feature updated",
+    roleTenantAdmin: "Tenant Administrator",
+    roleRegularUser: "Regular User",
+    creatingRegularUser: "Creating regular user",
+    editUserRole: "Edit user role",
+    targetRole: "Target Role",
+    editCategory: "Edit category",
+    totalTenants: "Total Tenants",
+    systemUsers: "System Users",
+    systemHealth: "System Health",
+    operational: "Operational",
+    orgManagement: "Organization Management",
+    globalTenantControl: "Global Tenant Control",
+    newTenant: "New Tenant",
+    domainOptional: "Domain (Optional)",
+    saveChanges: "Save changes",
+    modelConfiguration: "Model Configuration",
+    defaultLLMModel: "Default LLM Model",
+    selectLLM: "Select LLM",
+    selectEmbedding: "Select Embedding",
+    rerankModel: "Rerank Model",
+    none: "None",
+    indexingChunkingConfig: "Indexing & Chunking Config",
+    chatHyperparameters: "Chat Hyperparameters",
+    temperature: "Temperature",
+    precise: "Precise",
+    creative: "Creative",
+    maxResponseTokens: "Max Response Tokens",
+    retrievalSearchSettings: "Retrieval & Search Settings",
+    topK: "Top K",
+    similarityThreshold: "Similarity Threshold",
+    enableHybridSearch: "Enable Hybrid Search",
+    hybridSearchDesc: "Use both vector and full-text search to improve recall",
+    hybridWeight: "Hybrid Weight (0.0=Fulltext, 1.0=Vector)",
+    pureText: "Pure Text",
+    pureVector: "Pure Vector",
+    enableQueryExpansion: "Enable Query Expansion",
+    queryExpansionDesc: "Generate multiple query variations for better coverage",
+    enableHyDE: "Enable HyDE",
+    hydeDesc: "Generate hypothetical answers to improve semantic search",
+    enableReranking: "Enable Reranking",
+    rerankingDesc: "Use Rerank model to re-sort results",
+    broad: "Broad",
+    strict: "Strict",
+    maxInput: "Max Input",
+    dimensions: "Dimensions",
+    defaultBadge: "Default",
+    dims: "Dims: $1",
+    ctx: "Ctx: $1",
+    baseApi: "Base API: $1",
+    configured: "Configured",
+    groupCreated: "Group created",
+    navCatalog: "Catalog",
+    allDocuments: "All Documents",
+    categories: "Categories",
+    uncategorizedFiles: "Uncategorized Files",
+    category: "Category",
+    statusReadyDesc: "Indexed and searchable",
+    statusIndexingDesc: "Building vector index",
+    selectCategory: "Select Category",
+    noneUncategorized: "No uncategorized files",
+    previous: "Previous",
+    next: "Next",
+    createCategory: "Create Category",
+    categoryDesc: "Describe your knowledge category",
+    categoryName: "Category Name",
+    createCategoryBtn: "Create Now",
+    newGroup: "New Group",
+    noKnowledgeGroups: "No knowledge groups yet",
+    createGroupDesc: "Start by creating your first knowledge group and uploading documents.",
+    noDescriptionProvided: "No description provided",
+    browseManageFiles: "Browse and manage files and notes in this group.",
+    filterGroupFiles: "Search files in group by name...",
+    "2d": "2d",
+    Authorization: "Authorization",
+    a: "a",
+    agentTitle: "Agent Center",
+    agentDesc: "Manage and run your AI assistants to help with complex tasks.",
     createAgent: "Create Agent",
+    searchAgent: "Search agents...",
     statusRunning: "Running",
     statusStopped: "Stopped",
-    btnChat: "Chat",
+    updatedAtPrefix: "Last updated at ",
+    btnChat: "Start Chat",
+
+    // Agent Mock Data
+    agent1Name: "Data Analyst Pro",
+    agent1Desc: "Expert in SQL and data visualization, capable of extracting insights from complex data.",
+    agent2Name: "Code Review Assistant",
+    agent2Desc: "Automatically checks code quality, provides refactoring suggestions and performance optimizations.",
+    agent3Name: "Academic Paper Polisher",
+    agent3Desc: "Professional academic writing assistant to help optimize paper structure and language.",
+    agent4Name: "Legal Consultant",
+    agent4Desc: "Provides legal article search and basic legal advice, assists in drafting contracts.",
+    agent5Name: "Market Researcher",
+    agent5Desc: "Analyze industry trends and generate competitor research reports.",
+    agent6Name: "SRE Expert",
+    agent6Desc: "Monitor system health, automatically handle common alerts and troubleshooting.",
+    agent7Name: "Financial Auditor",
+    agent7Desc: "Automate report auditing and identify financial risks and abnormal transactions.",
+    agent1Time: "2 hours ago",
+    agent2Time: "5 hours ago",
+    agent3Time: "Yesterday",
+    agent4Time: "2 days ago",
+    agent5Time: "3 days ago",
+    agent6Time: "5 days ago",
+    agent7Time: "1 week ago",
 
-    // Plugin
-    navPlugin: "Plugins",
-    pluginTitle: "Plugin Center",
-    pluginDesc: "Discover and manage powerful plugins for your workflow.",
+    // Plugins
+    pluginTitle: "Plugin Store",
+    pluginDesc: "Extend the functionality of your knowledge base with external tools and services.",
     searchPlugin: "Search plugins...",
     installPlugin: "Install",
     installedPlugin: "Installed",
-    updatePlugin: "Update",
-
-    // Added translations
-    selectOrganization: "Select Organization",
-    defaultTenant: "Default",
-    roleTenantAdmin: "Tenant Admin",
-    roleRegularUser: "Regular User",
-    creatingRegularUser: "Creating Regular User",
-    editUserRole: "Edit User Role",
-    targetRole: "Target Role",
-    defaultBadge: "Default",
-    defaultSettingFailed: "Failed to set default",
-    pluginOfficial: "Official",
-    pluginCommunity: "Community",
+    updatePlugin: "Update Available",
+    pluginOfficial: "OFFICIAL",
+    pluginCommunity: "COMMUNITY",
     pluginBy: "By ",
-    pluginConfig: "Config",
-    updatedAtPrefix: "Updated ",
-    plugin1Name: "Jira Integration",
-    plugin1Desc: "Seamlessly connect Jira agile boards, support querying, creating, and updating Issue, Epic statuses.",
-    plugin2Name: "Confluence Knowledge Base",
-    plugin2Desc: "Retrieve and read Confluence docs in real-time, providing agents with rich context.",
-    plugin3Name: "GitLab Code Tools",
-    plugin3Desc: "Interact directly with GitLab repos, supporting review, commit analysis, CI/CD statuses.",
-    plugin4Name: "Web Searcher",
-    plugin4Desc: "Break LLM knowledge limits using multi-engine search (Google/Bing) for real-time news.",
-    plugin5Name: "SQL DB Insights",
-    plugin5Desc: "Safely connect to internal MySQL/PostgreSQL to convert natural language to SQL and execute.",
-    plugin6Name: "Feishu/DingTalk Bot",
-    plugin6Desc: "Sync or send agent results to specific groups with one click for closed-loop notifications.",
-    agent1Name: "AI Talent Evaluator",
-    agent1Desc: "Automatically eval AI talent based on multidimensional data, generating objective portraits.",
-    agent1Time: "Just now",
-    agent2Name: "Code Review Agent",
-    agent2Desc: "Deep scan code commits, identify logic flaws, standards issues, and provide tips.",
-    agent2Time: "10 mins ago",
-    agent3Name: "Design Review Agent",
-    agent3Desc: "Analyze architecture docs/UML, validate patterns, and find bottlenecks.",
-    agent3Time: "2 hours ago",
-    agent4Name: "Test Case Generation Agent",
-    agent4Desc: "Generate high-coverage tests (unit, integration, e2e) based on requirements/interfaces.",
-    agent4Time: "Yesterday",
-    agent5Name: "Quality Data Collection Agent",
-    agent5Desc: "Connect to defect tracking/monitoring to aggregate quality data and trend reports.",
-    agent5Time: "2 days ago",
-    agent6Name: "Productivity Collection Agent",
-    agent6Desc: "Statistically analyze dev workflows, team efficiency, commit frequency in real-time.",
-    agent6Time: "5 days ago",
-    agent7Name: "Project Report Generation Agent",
-    agent7Desc: "Deeply integrate PM data to analyze milestones and generate high-quality project reports.",
-    agent7Time: "1 week ago",
+    pluginConfig: "Configuration",
+
+    // Plugin Mock Data
+    plugin1Name: "Web Search",
+    plugin1Desc: "Gives AI real-time access to the internet for the latest information.",
+    plugin2Name: "PDF Document Parser",
+    plugin2Desc: "Deeply parse complex PDF layouts, extracting tables and mathematical formulas.",
+    plugin3Name: "GitHub Integration",
+    plugin3Desc: "Directly access GitHub repositories for code commits and issue management.",
+    plugin4Name: "Google Calendar",
+    plugin4Desc: "Sync your schedule and automatically create meeting reminders.",
+    plugin5Name: "SQL Database Connector",
+    plugin5Desc: "Securely connect to your databases and perform natural language queries.",
+    plugin6Name: "Slack Notifier",
+    plugin6Desc: "Send AI-generated reports directly to specified Slack channels.",
+
+    personalNotebook: "Personal Notebook",
+    success: "Success",
+    warning: "Warning",
+    "x-api-key": "API Key",
+    "x-tenant-id": "Tenant ID",
+    "x-user-language": "User Language",
+
+    // Hierarchical categories new keys
+    addSubcategory: "Add Subcategory",
+    parentCategory: "Parent Category (optional)",
+    noParentTopLevel: "None (top-level)",
+    useHierarchyImport: "Create categories by folder hierarchy",
+    useHierarchyImportDesc: "When enabled, a sub-category will be created for each sub-folder and files imported into matching categories.",
+
+    // Folder import drawer
+    importImmediate: "Import Now",
+    importScheduled: "Scheduled Import",
+    lblServerPath: "Server Folder Path",
+    placeholderServerPath: "e.g. /data/documents",
+    scheduledImportTip: "Server-side scheduled import: the server will read files from the given path at the specified time and import them automatically. Ensure the server has access to the path.",
+    lblScheduledTime: "Execution Time",
+    scheduledTimeHint: "The server will automatically run the import task at the specified time.",
+    scheduleImport: "Create Scheduled Task",
+    scheduleTaskCreated: "Scheduled import task created",
+    fillServerPath: "Please enter the server folder path",
+    invalidDateTime: "Please enter a valid date and time",
+
+    // Import Tasks Drawer
+    importTasksTitle: "Import Tasks",
+    noTasksFound: "No tasks found",
+    sourcePath: "Source Path",
+    targetGroup: "Target Group",
+    scheduledAt: "Scheduled At",
+    confirmDeleteTask: "Are you sure you want to delete this import task record?",
+    deleteTaskFailed: "Failed to delete task record",
   },
   ja: {
     aiCommandsError: "エラーが発生しました",
@@ -1352,6 +1594,7 @@ export const translations = {
     loginDesc: "システムに入るためのキーを入力してください",
     loginButton: "ログイン",
     loginError: "キーは必須です",
+    unknown: "不明",
     unknownError: "未知のエラー",
     usernamePlaceholder: "ユーザー名",
     passwordPlaceholder: "パスワード",
@@ -1618,11 +1861,6 @@ export const translations = {
     confirmClearKB: "警告:これによりすべてのファイルとインデックスが完全に削除されます。\n\n本当にナレッジベースをクリアしますか?",
     kbCleared: "ナレッジベースをクリアしました",
     clearFailed: "クリアに失敗しました",
-    actionFailed: "操作に失敗しました",
-    groupCreated: "カテゴリーを作成しました",
-    groupUpdated: "カテゴリーを更新しました",
-    groupDeleted: "カテゴリーを削除しました",
-    groupManagement: "カテゴリー管理",
     loginRequired: "先にログインしてください",
     uploadErrors: "以下のファイルはアップロードできませんでした",
     uploadWarning: "$1 つのファイルがアップロード準備完了、$2 つがフィルタリングされました",
@@ -1751,14 +1989,16 @@ export const translations = {
     passwordChangeSuccess: "パスワードを変更しました",
     passwordChangeFailed: "パスワードの変更に失敗しました",
     create: "作成",
-    validationFailedMsg:
-      "検証失敗: $1。CORSまたはURLが間違っている可能性があります。「検証をスキップ」をチェックして強制保存してください。",
+    validationFailedMsg: "validationFailedMsg",
 
     // Sidebar
     navChat: "チャット",
     navCoach: "コーチ",
     navKnowledge: "ナレッジベース",
     navKnowledgeGroups: "ナレッジグループ",
+    navNotebook: "ノートブック",
+    navAgent: "エージェント",
+    navPlugin: "プラグイン",
     navCrawler: "リソース取得",
     expandMenu: "メニューを展開",
     switchLanguage: "言語を切り替える",
@@ -1938,73 +2178,202 @@ export const translations = {
     loadMore: "もっと読み込む",
     loadingHistoriesFailed: "履歴の読み込みに失敗しました",
     supportedFormatsInfo: "ドキュメント、画像、ソースコードをサポート",
-
-    // Agent
-    navAgent: "エージェント",
-    agentTitle: "マイエージェント",
-    agentDesc: "AIエージェントアプリケーションを管理およびオーケストレートします。",
+    kbSettingsSaved: "設定を保存しました",
+    failedToSaveSettings: "設定の保存に失敗しました",
+    actionFailed: "操作に失敗しました",
+    userAddedToOrganization: "ユーザーが組織に追加されました",
+    featureUpdated: "機能が更新されました",
+    roleTenantAdmin: "テナント管理者",
+    roleRegularUser: "一般ユーザー",
+    creatingRegularUser: "一般ユーザーを作成中",
+    editUserRole: "ユーザーロールを編集",
+    targetRole: "対象のロール",
+    editCategory: "カテゴリを編集",
+    totalTenants: "総テナント数",
+    systemUsers: "システムユーザー",
+    systemHealth: "システムヘルス",
+    operational: "正常稼働中",
+    orgManagement: "組織管理",
+    globalTenantControl: "グローバルテナントコントロール",
+    newTenant: "新規テナント",
+    domainOptional: "ドメイン (任意)",
+    saveChanges: "変更を保存",
+    modelConfiguration: "モデル設定",
+    defaultLLMModel: "デフォルト推論モデル",
+    selectLLM: "LLMを選択",
+    selectEmbedding: "埋め込みを選択",
+    rerankModel: "リランクモデル",
+    none: "なし",
+    indexingChunkingConfig: "インデックスとチャンク設定",
+    chatHyperparameters: "チャットハイパーパラメータ",
+    temperature: "温度",
+    precise: "精密",
+    creative: "クリエイティブ",
+    maxResponseTokens: "最大応答トークン数",
+    retrievalSearchSettings: "検索設定",
+    topK: "Top K",
+    similarityThreshold: "類似度しきい値",
+    enableHybridSearch: "ハイブリッド検索を有効にする",
+    hybridSearchDesc: "ベクトル検索と全文検索を併用して検索精度を向上させます",
+    hybridWeight: "ハイブリッド重み (0.0=全文, 1.0=ベクトル)",
+    pureText: "純粋なテキスト",
+    pureVector: "純粋なベクトル",
+    enableQueryExpansion: "クエリ拡張を有効にする",
+    queryExpansionDesc: "複数のクエリバリアントを生成してカバレッジを向上させます",
+    enableHyDE: "HyDEを有効にする",
+    hydeDesc: "仮想的な回答を生成してセマンティック検索を改善します",
+    enableReranking: "リランクを有効にする",
+    rerankingDesc: "リランクモデルを使用して結果を再ソートします",
+    broad: "広範",
+    strict: "厳格",
+    maxInput: "最大入力",
+    dimensions: "次元",
+    defaultBadge: "デフォルト",
+    dims: "次元: $1",
+    ctx: "コンテキスト: $1",
+    baseApi: "Base API: $1",
+    configured: "設定済み",
+    groupUpdated: "グループが更新されました",
+    groupDeleted: "グループが削除されました",
+    groupCreated: "グループが作成されました",
+    navCatalog: "カタログ",
+    allDocuments: "すべてのドキュメント",
+    categories: "カテゴリ",
+    uncategorizedFiles: "未分類ファイル",
+    category: "カテゴリ",
+    statusReadyDesc: "インデックス済みで検索可能",
+    statusIndexingDesc: "ベクトルインデックスを作成中",
+    selectCategory: "カテゴリを選択",
+    noneUncategorized: "未分類ファイルなし",
+    previous: "前へ",
+    next: "次へ",
+    createCategory: "カテゴリを作成",
+    categoryDesc: "ナレッジカテゴリを説明します",
+    categoryName: "カテゴリ名",
+    createCategoryBtn: "今すぐ作成",
+    newGroup: "新規グループ",
+    noKnowledgeGroups: "ナレッジグループがまだありません",
+    createGroupDesc: "最初のナレッジグループを作成してドキュメントをアップロードしてください。",
+    noDescriptionProvided: "説明なし",
+    browseManageFiles: "このグループ内のファイルとメモを閲覧・管理します。",
+    filterGroupFiles: "名前でグループ内のファイルを検索...",
+    generalSettingsSubtitle: "アプリケーションの設定を管理します。",
+    userManagementSubtitle: "アクセス権限とアカウントを管理します。",
+    modelManagementSubtitle: "グローバルなAIモデルを設定します。",
+    kbSettingsSubtitle: "インデックス作成とチャットパラメータの技術設定。",
+    tenantsSubtitle: "グローバルシステムの概要。",
+    allNotes: "すべてのノート",
+    filterNotesPlaceholder: "ノートをフィルタリング...",
+    startWritingPlaceholder: "書き始める...",
+    previewHeader: "プレビュー",
+    noContentToPreview: "プレビューするコンテンツがありません",
+    hidePreview: "プレビューを非表示",
+    showPreview: "プレビューを表示",
+    directoryLabel: "ディレクトリ",
+    uncategorized: "未分類",
+    enterNamePlaceholder: "名前を入力...",
+    subFolderPlaceholder: "サブフォルダ...",
+    categoryCreated: "カテゴリが作成されました",
+    failedToCreateCategory: "カテゴリの作成に失敗しました",
+    failedToDeleteCategory: "カテゴリの削除に失敗しました",
+    confirmDeleteCategory: "このカテゴリを削除してもよろしいですか?",
+    "2d": "2d",
+    Authorization: "Authorization",
+    a: "a",
+    agentTitle: "エージェントセンター",
+    agentDesc: "複雑なタスクを支援する AI アシスタントを管理および実行します。",
+    createAgent: "エージェント作成",
     searchAgent: "エージェントを検索...",
-    createAgent: "エージェントを作成",
     statusRunning: "実行中",
     statusStopped: "停止中",
-    btnChat: "チャット",
+    updatedAtPrefix: "最終更新日: ",
+    btnChat: "会話を開始",
+
+    // Agent Mock Data
+    agent1Name: "データ分析エキスパート",
+    agent1Desc: "SQL とデータ視覚化に精通し、複雑なデータから洞察を抽出できます。",
+    agent2Name: "コードレビュー助手",
+    agent2Desc: "コードの品質を自動的にチェックし、リファクタリングの提案やパフォーマンス最適化案を提供します。",
+    agent3Name: "学術論文校閲",
+    agent3Desc: "専門的な学術ライティングアシスタント。論文の構成と表現の最適化を支援します。",
+    agent4Name: "法律顧問",
+    agent4Desc: "法律条文の検索や基本的な法的アドバイスを提供し、契約書の作成を支援します。",
+    agent5Name: "市場調査員",
+    agent5Desc: "業界のトレンドを分析し、競合他社の調査レポートを生成します。",
+    agent6Name: "システム運用保守エキスパート",
+    agent6Desc: "システムの健康状態を監視し、一般的なアラートへの対応やトラブルシューティングを自動化します。",
+    agent7Name: "財務監査人",
+    agent7Desc: "レポート監査を自動化し、財務リスクや異常な取引を特定します。",
+    agent1Time: "2 時間前",
+    agent2Time: "5 時間前",
+    agent3Time: "昨日",
+    agent4Time: "2 日前",
+    agent5Time: "3 日前",
+    agent6Time: "5 日前",
+    agent7Time: "1 週間前",
 
-    // Plugin
-    navPlugin: "プラグイン",
-    pluginTitle: "プラグインセンター",
-    pluginDesc: "ワークフローに適した強力なプラグインを見つけて管理します。",
+    // Plugins
+    pluginTitle: "プラグインストア",
+    pluginDesc: "外部ツールやサービスを統合して、ナレッジベースの機能を拡張します。",
     searchPlugin: "プラグインを検索...",
     installPlugin: "インストール",
     installedPlugin: "インストール済み",
-    updatePlugin: "更新",
-
-    // Added translations
-    selectOrganization: "組織を選択",
-    defaultTenant: "デフォルト",
-    roleTenantAdmin: "テナント管理者",
-    roleRegularUser: "一般ユーザー",
-    creatingRegularUser: "一般ユーザーを作成中",
-    editUserRole: "ユーザーロールの編集",
-    targetRole: "ターゲットロール",
-    defaultBadge: "デフォルト",
-    defaultSettingFailed: "デフォルト設定に失敗しました",
+    updatePlugin: "アップデートあり",
     pluginOfficial: "公式",
     pluginCommunity: "コミュニティ",
     pluginBy: "開発者: ",
     pluginConfig: "設定",
-    updatedAtPrefix: "更新時間 ",
-    plugin1Name: "Jira 統合",
-    plugin1Desc: "Jira アジャイルボードをシームレスに接続し、クエリやIssue、Epicの更新をサポートします。",
-    plugin2Name: "Confluence ナレッジベース",
-    plugin2Desc: "Confluenceドキュメントをリアルタイムで検索・読み込みし、エージェントにコンテキストを提供します。",
-    plugin3Name: "GitLab コードツール",
-    plugin3Desc: "GitLabリポジトリと直接連携し、レビュー、コミット分析、CI/CDステータスをサポートします。",
-    plugin4Name: "ウェブ検索",
-    plugin4Desc: "LLMの知識の限界を超え、複数エンジンの検索で最新情報をリアルタイムで取得します。",
-    plugin5Name: "SQL データベース インサイト",
-    plugin5Desc: "内部MySQL/PostgreSQLに安全に接続し、自然言語をSQLに変換して実行・分析します。",
-    plugin6Name: "Feishu/DingTalk ボット",
-    plugin6Desc: "エージェントの実行結果を指定のグループにワンクリックで送信または同期します。",
-    agent1Name: "AI 人材評価エージェント",
-    agent1Desc: "多次元のデータに基づき、客観的で包括的な人材のプロファイルや成長の提案を生成します。",
-    agent1Time: "たった今",
-    agent2Name: "コードレビュー エージェント",
-    agent2Desc: "コミット内容をスキャンし、論理の欠陥やリスクを特定し、最適化を提案します。",
-    agent2Time: "10 分前",
-    agent3Name: "設計レビュー エージェント",
-    agent3Desc: "アーキテクチャやUMLを分析し、デザインパターンの妥当性を検証し、ボトルネックを発見します。",
-    agent3Time: "2 時間前",
-    agent4Name: "テストケース生成 エージェント",
-    agent4Desc: "要件ドキュメントに基づいて、カバレッジの高い広範なテストを自動生成します。",
-    agent4Time: "昨日",
-    agent5Name: "品質データ収集 エージェント",
-    agent5Desc: "欠陥追跡システム等と連携して品質データを集約し、トレンドレポートを生成します。",
-    agent5Time: "2 日前",
-    agent6Name: "生産性収集 エージェント",
-    agent6Desc: "開発フローデータを統計し、チームの効率やコミット頻度、タスクの進捗を分析します。",
-    agent6Time: "5 日前",
-    agent7Name: "プロジェクトレポート エージェント",
-    agent7Desc: "プロジェクト管理データを統合・分析し、高品質な進捗や振り返りのレポートを生成します。",
-    agent7Time: "1 週間前",
-  }
-}; // end of translations
+
+    // Plugin Mock Data
+    plugin1Name: "Web 検索",
+    plugin1Desc: "最新情報を取得するために、AI にインターネットへのリアルタイムアクセスを提供します。",
+    plugin2Name: "PDF ドキュメント解析",
+    plugin2Desc: "複雑な PDF レイアウトを詳細に解析し、表や数式を抽出します。",
+    plugin3Name: "GitHub 連携",
+    plugin3Desc: "GitHub リポジトリに直接アクセスし、コードのコミットやイシュー管理を行います。",
+    plugin4Name: "Google カレンダー",
+    plugin4Desc: "スケジュールを同期し、会議のリマインダーを自動的に作成します。",
+    plugin5Name: "SQL データベース接続",
+    plugin5Desc: "データベースに安全に接続し、自然語言でクエリを実行します。",
+    plugin6Name: "Slack 通知",
+    plugin6Desc: "AI が生成したレポートを指定された Slack チャンネルに直接送信します。",
+
+    navTenants: "テナント管理",
+    noNotesFound: "ノートが見つかりません",
+    notebookDesc: "ノートブックは知識の整理と要約に役立ちます。",
+    personalNotebook: "個人用ノートブック",
+    warning: "警告",
+    "x-api-key": "APIキー",
+    "x-tenant-id": "テナントID",
+    "x-user-language": "ユーザー言語",
+
+    // Hierarchical categories new keys
+    addSubcategory: "サブカテゴリを追加",
+    parentCategory: "親カテゴリ(任意)",
+    noParentTopLevel: "なし(トップレベル)",
+    useHierarchyImport: "フォルダ階層でカテゴリを作成",
+    useHierarchyImportDesc: "有効にすると、各サブフォルダにサブカテゴリが作成され、ファイルが対応するカテゴリにインポートされます。",
+
+    // Folder import drawer
+    importImmediate: "今すぐインポート",
+    importScheduled: "スケジュールインポート",
+    lblServerPath: "サーバーフォルダパス",
+    placeholderServerPath: "例: /data/documents",
+    scheduledImportTip: "サーバー側のスケジュールインポート:指定した時刻にサーバーがパスのファイルを読み込んで自動インポートします。サーバーがそのパスにアクセスできることを確認してください。",
+    lblScheduledTime: "実行日時",
+    scheduledTimeHint: "指定した時刻に、サーバーが自動的にインポートタスクを実行します。",
+    scheduleImport: "スケジュールタスクを作成",
+    scheduleTaskCreated: "スケジュールインポートタスクが作成されました",
+    fillServerPath: "サーバーフォルダパスを入力してください",
+    invalidDateTime: "有効な日付と時刻を入力してください",
+
+    // Import Tasks Drawer
+    importTasksTitle: "インポートタスク",
+    noTasksFound: "タスクは見つかりませんでした",
+    sourcePath: "ソースパス",
+    targetGroup: "ターゲットグループ",
+    scheduledAt: "実行予定日時",
+    confirmDeleteTask: "このインポートタスクレコードを削除してもよろしいですか?",
+    deleteTaskFailed: "タスクレコードの削除に失敗しました",
+  },
+};

+ 63 - 481
yarn.lock

@@ -320,7 +320,7 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/runtime@^7.28.4":
+"@babel/runtime@^7.21.0", "@babel/runtime@^7.28.4":
   version "7.28.4"
   resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz"
   integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==
@@ -412,22 +412,6 @@
   resolved "https://registry.npmmirror.com/@colors/colors/-/colors-1.5.0.tgz"
   integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
 
-"@cspotcode/source-map-support@^0.8.0":
-  version "0.8.1"
-  resolved "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz"
-  integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
-  dependencies:
-    "@jridgewell/trace-mapping" "0.3.9"
-
-"@elastic/elasticsearch@^9.2.0":
-  version "9.2.0"
-  resolved "https://registry.npmmirror.com/@elastic/elasticsearch/-/elasticsearch-9.2.0.tgz"
-  integrity sha512-M59qmMOZOk8pTcI9Ns2ow18PlyMbYrpcXqYwkChjiyXSmmqoCTvFXkC2bGQLxrrQkXaPbYR7aZqWD9b5F1405A==
-  dependencies:
-    "@elastic/transport" "^9.2.0"
-    apache-arrow "18.x - 21.x"
-    tslib "^2.4.0"
-
 "@elastic/transport@^9.2.0":
   version "9.2.3"
   resolved "https://registry.npmmirror.com/@elastic/transport/-/transport-9.2.3.tgz"
@@ -482,7 +466,7 @@
   dependencies:
     "@types/json-schema" "^7.0.15"
 
-"@eslint/eslintrc@^3.2.0", "@eslint/eslintrc@^3.3.1":
+"@eslint/eslintrc@^3.3.1":
   version "3.3.3"
   resolved "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz"
   integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==
@@ -497,7 +481,7 @@
     minimatch "^3.1.2"
     strip-json-comments "^3.1.1"
 
-"@eslint/js@^9.18.0", "@eslint/js@9.39.1":
+"@eslint/js@9.39.1":
   version "9.39.1"
   resolved "https://registry.npmmirror.com/@eslint/js/-/js-9.39.1.tgz"
   integrity sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==
@@ -1123,7 +1107,7 @@
     "@jridgewell/resolve-uri" "^3.0.3"
     "@jridgewell/sourcemap-codec" "^1.4.10"
 
-"@langchain/core@^1.0.0", "@langchain/core@^1.0.1", "@langchain/core@^1.1.5", "@langchain/core@1.1.4":
+"@langchain/core@^1.0.0", "@langchain/core@^1.0.1", "@langchain/core@1.1.4":
   version "1.1.5"
   resolved "https://registry.npmjs.org/@langchain/core/-/core-1.1.5.tgz"
   integrity sha512-m+EhnHhaCnVPJt4HRmhElBN3ZBvQGfXL/hm80UV3EHNUPNUCHi6q6de7dqrw/l4oTvmX0nC08Fm2ta1U59o1bQ==
@@ -1164,22 +1148,6 @@
     "@langchain/langgraph-sdk" "~1.2.0"
     uuid "^10.0.0"
 
-"@langchain/openai@^1.1.3":
-  version "1.1.3"
-  resolved "https://registry.npmmirror.com/@langchain/openai/-/openai-1.1.3.tgz"
-  integrity sha512-p+xR+4HRms5Ozjf5miC6U2AYRyNVSTdO7AMBkMYs1Tp6DWHBd+mQ72H8Ogd2dKrPuS5UDJ5dbpI1fS+OrTbgQQ==
-  dependencies:
-    js-tiktoken "^1.0.12"
-    openai "^6.9.0"
-    zod "^3.25.76 || ^4"
-
-"@langchain/textsplitters@^1.0.1":
-  version "1.0.1"
-  resolved "https://registry.npmmirror.com/@langchain/textsplitters/-/textsplitters-1.0.1.tgz"
-  integrity sha512-rheJlB01iVtrOUzttscutRgLybPH9qR79EyzBEbf1u97ljWyuxQfCwIWK+SjoQTM9O8M7GGLLRBSYE26Jmcoww==
-  dependencies:
-    js-tiktoken "^1.0.12"
-
 "@lukeed/csprng@^1.0.0":
   version "1.1.0"
   resolved "https://registry.npmmirror.com/@lukeed/csprng/-/csprng-1.1.0.tgz"
@@ -1219,30 +1187,6 @@
     "@napi-rs/canvas-win32-arm64-msvc" "0.1.88"
     "@napi-rs/canvas-win32-x64-msvc" "0.1.88"
 
-"@nestjs/cli@^11.0.0":
-  version "11.0.14"
-  resolved "https://registry.npmmirror.com/@nestjs/cli/-/cli-11.0.14.tgz"
-  integrity sha512-YwP03zb5VETTwelXU+AIzMVbEZKk/uxJL+z9pw0mdG9ogAtqZ6/mpmIM4nEq/NU8D0a7CBRLcMYUmWW/55pfqw==
-  dependencies:
-    "@angular-devkit/core" "19.2.19"
-    "@angular-devkit/schematics" "19.2.19"
-    "@angular-devkit/schematics-cli" "19.2.19"
-    "@inquirer/prompts" "7.10.1"
-    "@nestjs/schematics" "^11.0.1"
-    ansis "4.2.0"
-    chokidar "4.0.3"
-    cli-table3 "0.6.5"
-    commander "4.1.1"
-    fork-ts-checker-webpack-plugin "9.1.0"
-    glob "13.0.0"
-    node-emoji "1.11.0"
-    ora "5.4.1"
-    tsconfig-paths "4.2.0"
-    tsconfig-paths-webpack-plugin "4.2.0"
-    typescript "5.9.3"
-    webpack "5.103.0"
-    webpack-node-externals "3.0.0"
-
 "@nestjs/common@^10.0.0 || ^11.0.0", "@nestjs/common@^11.0.0", "@nestjs/common@^11.0.1", "@nestjs/common@^11.0.2", "@nestjs/common@^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0":
   version "11.1.9"
   resolved "https://registry.npmmirror.com/@nestjs/common/-/common-11.1.9.tgz"
@@ -1254,15 +1198,6 @@
     tslib "2.8.1"
     uid "2.0.2"
 
-"@nestjs/config@^4.0.2":
-  version "4.0.2"
-  resolved "https://registry.npmmirror.com/@nestjs/config/-/config-4.0.2.tgz"
-  integrity sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==
-  dependencies:
-    dotenv "16.4.7"
-    dotenv-expand "12.0.1"
-    lodash "4.17.21"
-
 "@nestjs/core@^10.0.0 || ^11.0.0", "@nestjs/core@^11.0.0", "@nestjs/core@^11.0.1", "@nestjs/core@^11.0.2":
   version "11.1.9"
   resolved "https://registry.npmmirror.com/@nestjs/core/-/core-11.1.9.tgz"
@@ -1275,25 +1210,12 @@
     tslib "2.8.1"
     uid "2.0.2"
 
-"@nestjs/jwt@^11.0.2":
-  version "11.0.2"
-  resolved "https://registry.npmmirror.com/@nestjs/jwt/-/jwt-11.0.2.tgz"
-  integrity sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==
-  dependencies:
-    "@types/jsonwebtoken" "9.0.10"
-    jsonwebtoken "9.0.3"
-
-"@nestjs/mapped-types@^2.1.0", "@nestjs/mapped-types@2.1.0":
+"@nestjs/mapped-types@2.1.0":
   version "2.1.0"
   resolved "https://registry.npmmirror.com/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz"
   integrity sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==
 
-"@nestjs/passport@^11.0.5":
-  version "11.0.5"
-  resolved "https://registry.npmmirror.com/@nestjs/passport/-/passport-11.0.5.tgz"
-  integrity sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==
-
-"@nestjs/platform-express@^11.0.0", "@nestjs/platform-express@^11.0.1":
+"@nestjs/platform-express@^11.0.0":
   version "11.1.9"
   resolved "https://registry.npmmirror.com/@nestjs/platform-express/-/platform-express-11.1.9.tgz"
   integrity sha512-GVd3+0lO0mJq2m1kl9hDDnVrX3Nd4oH3oDfklz0pZEVEVS0KVSp63ufHq2Lu9cyPdSBuelJr9iPm2QQ1yX+Kmw==
@@ -1304,14 +1226,7 @@
     path-to-regexp "8.3.0"
     tslib "2.8.1"
 
-"@nestjs/schedule@^6.1.0":
-  version "6.1.0"
-  resolved "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.1.0.tgz"
-  integrity sha512-W25Ydc933Gzb1/oo7+bWzzDiOissE+h/dhIAPugA39b9MuIzBbLybuXpc1AjoQLczO3v0ldmxaffVl87W0uqoQ==
-  dependencies:
-    cron "4.3.5"
-
-"@nestjs/schematics@^11.0.0", "@nestjs/schematics@^11.0.1":
+"@nestjs/schematics@^11.0.1":
   version "11.0.9"
   resolved "https://registry.npmmirror.com/@nestjs/schematics/-/schematics-11.0.9.tgz"
   integrity sha512-0NfPbPlEaGwIT8/TCThxLzrlz3yzDNkfRNpbL7FiplKq3w4qXpJg0JYwqgMEJnLQZm3L/L/5XjoyfJHUO3qX9g==
@@ -1322,37 +1237,6 @@
     jsonc-parser "3.3.1"
     pluralize "8.0.0"
 
-"@nestjs/serve-static@^5.0.4":
-  version "5.0.4"
-  resolved "https://registry.npmjs.org/@nestjs/serve-static/-/serve-static-5.0.4.tgz"
-  integrity sha512-3kO1M9D3vsPyWPFardxIjUYeuolS58PnhCoBTkS7t3BrdZFZCKHnBZ15js+UOzOR2Q6HmD7ssGjLd0DVYVdvOw==
-  dependencies:
-    path-to-regexp "8.3.0"
-
-"@nestjs/swagger@^11.2.6":
-  version "11.2.6"
-  resolved "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.6.tgz"
-  integrity sha512-oiXOxMQqDFyv1AKAqFzSo6JPvMEs4uA36Eyz/s2aloZLxUjcLfUMELSLSNQunr61xCPTpwEOShfmO7NIufKXdA==
-  dependencies:
-    "@microsoft/tsdoc" "0.16.0"
-    "@nestjs/mapped-types" "2.1.0"
-    js-yaml "4.1.1"
-    lodash "4.17.23"
-    path-to-regexp "8.3.0"
-    swagger-ui-dist "5.31.0"
-
-"@nestjs/testing@^11.0.1":
-  version "11.1.9"
-  resolved "https://registry.npmmirror.com/@nestjs/testing/-/testing-11.1.9.tgz"
-  integrity sha512-UFxerBDdb0RUNxQNj25pvkvNE7/vxKhXYWBt3QuwBFnYISzRIzhVlyIqLfoV5YI3zV0m0Nn4QAn1KM0zzwfEng==
-  dependencies:
-    tslib "2.8.1"
-
-"@nestjs/typeorm@^11.0.0":
-  version "11.0.0"
-  resolved "https://registry.npmmirror.com/@nestjs/typeorm/-/typeorm-11.0.0.tgz"
-  integrity sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==
-
 "@noble/hashes@^1.1.5":
   version "1.8.0"
   resolved "https://registry.npmmirror.com/@noble/hashes/-/hashes-1.8.0.tgz"
@@ -1541,26 +1425,6 @@
   resolved "https://registry.npmmirror.com/@tokenizer/token/-/token-0.3.0.tgz"
   integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==
 
-"@tsconfig/node10@^1.0.7":
-  version "1.0.12"
-  resolved "https://registry.npmmirror.com/@tsconfig/node10/-/node10-1.0.12.tgz"
-  integrity sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==
-
-"@tsconfig/node12@^1.0.7":
-  version "1.0.11"
-  resolved "https://registry.npmmirror.com/@tsconfig/node12/-/node12-1.0.11.tgz"
-  integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
-
-"@tsconfig/node14@^1.0.0":
-  version "1.0.3"
-  resolved "https://registry.npmmirror.com/@tsconfig/node14/-/node14-1.0.3.tgz"
-  integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
-
-"@tsconfig/node16@^1.0.2":
-  version "1.0.4"
-  resolved "https://registry.npmmirror.com/@tsconfig/node16/-/node16-1.0.4.tgz"
-  integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
-
 "@types/babel__core@^7.20.5":
   version "7.20.5"
   resolved "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz"
@@ -1594,20 +1458,6 @@
   dependencies:
     "@babel/types" "^7.28.2"
 
-"@types/bcrypt@^6.0.0":
-  version "6.0.0"
-  resolved "https://registry.npmmirror.com/@types/bcrypt/-/bcrypt-6.0.0.tgz"
-  integrity sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==
-  dependencies:
-    "@types/node" "*"
-
-"@types/better-sqlite3@^7.6.13":
-  version "7.6.13"
-  resolved "https://registry.npmmirror.com/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz"
-  integrity sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==
-  dependencies:
-    "@types/node" "*"
-
 "@types/body-parser@*":
   version "1.19.6"
   resolved "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.6.tgz"
@@ -1638,14 +1488,6 @@
   resolved "https://registry.npmmirror.com/@types/cookiejar/-/cookiejar-2.1.5.tgz"
   integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==
 
-"@types/cron@^2.0.1":
-  version "2.0.1"
-  resolved "https://registry.npmjs.org/@types/cron/-/cron-2.0.1.tgz"
-  integrity sha512-WHa/1rtNtD2Q/H0+YTTZoty+/5rcE66iAFX2IY+JuUoOACsevYyFkSYu/2vdw+G5LrmO7Lxowrqm0av4k3qWNQ==
-  dependencies:
-    "@types/luxon" "*"
-    "@types/node" "*"
-
 "@types/d3-array@*":
   version "3.2.2"
   resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz"
@@ -1901,7 +1743,7 @@
     "@types/range-parser" "*"
     "@types/send" "*"
 
-"@types/express@*", "@types/express@^5.0.0":
+"@types/express@*":
   version "5.0.6"
   resolved "https://registry.npmmirror.com/@types/express/-/express-5.0.6.tgz"
   integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==
@@ -1946,14 +1788,6 @@
   dependencies:
     "@types/istanbul-lib-report" "*"
 
-"@types/jest@^30.0.0":
-  version "30.0.0"
-  resolved "https://registry.npmmirror.com/@types/jest/-/jest-30.0.0.tgz"
-  integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==
-  dependencies:
-    expect "^30.0.0"
-    pretty-format "^30.0.0"
-
 "@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
   version "7.0.15"
   resolved "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz"
@@ -1994,13 +1828,6 @@
   resolved "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz"
   integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
 
-"@types/multer@^2.0.0":
-  version "2.0.0"
-  resolved "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz"
-  integrity sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==
-  dependencies:
-    "@types/express" "*"
-
 "@types/node@*", "@types/node@^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node@>=18":
   version "25.0.0"
   resolved "https://registry.npmmirror.com/@types/node/-/node-25.0.0.tgz"
@@ -2008,13 +1835,6 @@
   dependencies:
     undici-types "~7.16.0"
 
-"@types/node@^22.10.7":
-  version "22.19.2"
-  resolved "https://registry.npmmirror.com/@types/node/-/node-22.19.2.tgz"
-  integrity sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==
-  dependencies:
-    undici-types "~6.21.0"
-
 "@types/node@^22.14.0":
   version "22.19.2"
   resolved "https://registry.npmmirror.com/@types/node/-/node-22.19.2.tgz"
@@ -2029,23 +1849,6 @@
   dependencies:
     undici-types "~7.16.0"
 
-"@types/passport-jwt@^4.0.1":
-  version "4.0.1"
-  resolved "https://registry.npmmirror.com/@types/passport-jwt/-/passport-jwt-4.0.1.tgz"
-  integrity sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==
-  dependencies:
-    "@types/jsonwebtoken" "*"
-    "@types/passport-strategy" "*"
-
-"@types/passport-local@^1.0.38":
-  version "1.0.38"
-  resolved "https://registry.npmmirror.com/@types/passport-local/-/passport-local-1.0.38.tgz"
-  integrity sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==
-  dependencies:
-    "@types/express" "*"
-    "@types/passport" "*"
-    "@types/passport-strategy" "*"
-
 "@types/passport-strategy@*":
   version "0.2.38"
   resolved "https://registry.npmmirror.com/@types/passport-strategy/-/passport-strategy-0.2.38.tgz"
@@ -2125,14 +1928,6 @@
     "@types/node" "*"
     form-data "^4.0.0"
 
-"@types/supertest@^6.0.2":
-  version "6.0.3"
-  resolved "https://registry.npmmirror.com/@types/supertest/-/supertest-6.0.3.tgz"
-  integrity sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==
-  dependencies:
-    "@types/methods" "^1.1.4"
-    "@types/superagent" "^8.1.0"
-
 "@types/trusted-types@^2.0.7":
   version "2.0.7"
   resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz"
@@ -2437,14 +2232,7 @@ acorn-jsx@^5.3.2:
   resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
   integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
 
-acorn-walk@^8.1.1:
-  version "8.3.4"
-  resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.4.tgz"
-  integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==
-  dependencies:
-    acorn "^8.11.0"
-
-"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0, acorn@^8.4.1:
+"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0:
   version "8.15.0"
   resolved "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz"
   integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
@@ -2597,11 +2385,6 @@ append-field@^1.0.0:
   resolved "https://registry.npmmirror.com/append-field/-/append-field-1.0.0.tgz"
   integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==
 
-arg@^4.1.0:
-  version "4.1.3"
-  resolved "https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz"
-  integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
-
 argparse@^1.0.7:
   version "1.0.10"
   resolved "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz"
@@ -2652,15 +2435,6 @@ available-typed-arrays@^1.0.7:
   dependencies:
     possible-typed-array-names "^1.0.0"
 
-axios@^1.13.2:
-  version "1.13.2"
-  resolved "https://registry.npmmirror.com/axios/-/axios-1.13.2.tgz"
-  integrity sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==
-  dependencies:
-    follow-redirects "^1.15.6"
-    form-data "^4.0.4"
-    proxy-from-env "^1.1.0"
-
 "babel-jest@^29.0.0 || ^30.0.0", babel-jest@30.2.0:
   version "30.2.0"
   resolved "https://registry.npmmirror.com/babel-jest/-/babel-jest-30.2.0.tgz"
@@ -2746,15 +2520,7 @@ baseline-browser-mapping@^2.9.0:
   resolved "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.6.tgz"
   integrity sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==
 
-bcrypt@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.npmmirror.com/bcrypt/-/bcrypt-6.0.0.tgz"
-  integrity sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==
-  dependencies:
-    node-addon-api "^8.3.0"
-    node-gyp-build "^4.8.4"
-
-better-sqlite3@^12.5.0, "better-sqlite3@^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0":
+"better-sqlite3@^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0":
   version "12.5.0"
   resolved "https://registry.npmmirror.com/better-sqlite3/-/better-sqlite3-12.5.0.tgz"
   integrity sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg==
@@ -3030,12 +2796,12 @@ cjs-module-lexer@^2.1.0:
   resolved "https://registry.npmmirror.com/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz"
   integrity sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==
 
-class-transformer@*, "class-transformer@^0.4.0 || ^0.5.0", class-transformer@^0.5.1, class-transformer@>=0.4.1:
+class-transformer@*, "class-transformer@^0.4.0 || ^0.5.0", class-transformer@>=0.4.1:
   version "0.5.1"
   resolved "https://registry.npmmirror.com/class-transformer/-/class-transformer-0.5.1.tgz"
   integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==
 
-class-validator@*, "class-validator@^0.13.0 || ^0.14.0", class-validator@^0.14.3, class-validator@>=0.13.2:
+class-validator@*, "class-validator@^0.13.0 || ^0.14.0", class-validator@>=0.13.2:
   version "0.14.3"
   resolved "https://registry.npmmirror.com/class-validator/-/class-validator-0.14.3.tgz"
   integrity sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==
@@ -3192,6 +2958,21 @@ concat-stream@^2.0.0:
     readable-stream "^3.0.2"
     typedarray "^0.0.6"
 
+concurrently@^8.2.2:
+  version "8.2.2"
+  resolved "https://registry.npmmirror.com/concurrently/-/concurrently-8.2.2.tgz"
+  integrity sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==
+  dependencies:
+    chalk "^4.1.2"
+    date-fns "^2.30.0"
+    lodash "^4.17.21"
+    rxjs "^7.8.1"
+    shell-quote "^1.8.1"
+    spawn-command "0.0.2"
+    supports-color "^8.1.1"
+    tree-kill "^1.2.2"
+    yargs "^17.7.2"
+
 confbox@^0.1.8:
   version "0.1.8"
   resolved "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz"
@@ -3281,11 +3062,6 @@ cosmiconfig@^8.2.0:
     parse-json "^5.2.0"
     path-type "^4.0.0"
 
-create-require@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz"
-  integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
-
 cron@4.3.5:
   version "4.3.5"
   resolved "https://registry.npmjs.org/cron/-/cron-4.3.5.tgz"
@@ -3623,6 +3399,13 @@ data-uri-to-buffer@^4.0.0:
   resolved "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz"
   integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==
 
+date-fns@^2.30.0:
+  version "2.30.0"
+  resolved "https://registry.npmmirror.com/date-fns/-/date-fns-2.30.0.tgz"
+  integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
+  dependencies:
+    "@babel/runtime" "^7.21.0"
+
 dayjs@^1.11.13, dayjs@^1.11.18:
   version "1.11.19"
   resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz"
@@ -3737,11 +3520,6 @@ dezalgo@^1.0.4:
     asap "^2.0.0"
     wrappy "1"
 
-diff@^4.0.1:
-  version "4.0.2"
-  resolved "https://registry.npmmirror.com/diff/-/diff-4.0.2.tgz"
-  integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
-
 dompurify@^3.2.5:
   version "3.3.1"
   resolved "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz"
@@ -3761,11 +3539,6 @@ dotenv@^16.4.5, dotenv@^16.4.7:
   resolved "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz"
   integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==
 
-dotenv@^17.2.3:
-  version "17.2.3"
-  resolved "https://registry.npmmirror.com/dotenv/-/dotenv-17.2.3.tgz"
-  integrity sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==
-
 dotenv@16.4.7:
   version "16.4.7"
   resolved "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.7.tgz"
@@ -3938,19 +3711,11 @@ escape-string-regexp@^5.0.0:
   resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz"
   integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
 
-eslint-config-prettier@^10.0.1, "eslint-config-prettier@>= 7.0.0 <10.0.0 || >=10.1.0":
+"eslint-config-prettier@>= 7.0.0 <10.0.0 || >=10.1.0":
   version "10.1.8"
   resolved "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz"
   integrity sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==
 
-eslint-plugin-prettier@^5.2.2:
-  version "5.5.4"
-  resolved "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz"
-  integrity sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==
-  dependencies:
-    prettier-linter-helpers "^1.0.0"
-    synckit "^0.11.7"
-
 eslint-scope@^8.4.0:
   version "8.4.0"
   resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.4.0.tgz"
@@ -3977,7 +3742,7 @@ eslint-visitor-keys@^4.2.1:
   resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz"
   integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
 
-"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9.18.0, eslint@>=7.0.0, eslint@>=8.0.0:
+"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@>=7.0.0, eslint@>=8.0.0:
   version "9.39.1"
   resolved "https://registry.npmmirror.com/eslint/-/eslint-9.39.1.tgz"
   integrity sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==
@@ -4335,7 +4100,7 @@ fork-ts-checker-webpack-plugin@9.1.0:
     semver "^7.3.5"
     tapable "^2.2.1"
 
-form-data@^4.0.0, form-data@^4.0.4, form-data@^4.0.5:
+form-data@^4.0.0, form-data@^4.0.4:
   version "4.0.5"
   resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz"
   integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==
@@ -4538,11 +4303,6 @@ globals@^14.0.0:
   resolved "https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz"
   integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
 
-globals@^16.0.0:
-  version "16.5.0"
-  resolved "https://registry.npmmirror.com/globals/-/globals-16.5.0.tgz"
-  integrity sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==
-
 google-auth-library@^10.3.0:
   version "10.5.0"
   resolved "https://registry.npmmirror.com/google-auth-library/-/google-auth-library-10.5.0.tgz"
@@ -5403,7 +5163,7 @@ jest-worker@30.2.0:
     merge-stream "^2.0.0"
     supports-color "^8.1.1"
 
-"jest@^29.0.0 || ^30.0.0", jest@^30.0.0:
+"jest@^29.0.0 || ^30.0.0":
   version "30.2.0"
   resolved "https://registry.npmmirror.com/jest/-/jest-30.2.0.tgz"
   integrity sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==
@@ -5558,17 +5318,6 @@ khroma@^2.1.0:
   resolved "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz"
   integrity sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==
 
-langchain@^1.1.5:
-  version "1.1.5"
-  resolved "https://registry.npmmirror.com/langchain/-/langchain-1.1.5.tgz"
-  integrity sha512-tmJHdCsi4AQLEWDeTm9QTWgdwYgIaA4kfp14KFw6e1sUPxjsoHqdFqdf1ZJZxhs1h/n+hpIr3NBfGNBQnWxWEQ==
-  dependencies:
-    "@langchain/langgraph" "^1.0.0"
-    "@langchain/langgraph-checkpoint" "^1.0.0"
-    langsmith "~0.3.74"
-    uuid "^10.0.0"
-    zod "^3.25.76 || ^4"
-
 langium@3.3.1:
   version "3.3.1"
   resolved "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz"
@@ -5812,7 +5561,7 @@ make-dir@^4.0.0:
   dependencies:
     semver "^7.5.3"
 
-make-error@^1.1.1, make-error@^1.3.6:
+make-error@^1.3.6:
   version "1.3.6"
   resolved "https://registry.npmmirror.com/make-error/-/make-error-1.3.6.tgz"
   integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
@@ -6590,15 +6339,6 @@ node-domexception@^1.0.0:
   resolved "https://registry.npmmirror.com/node-domexception/-/node-domexception-1.0.0.tgz"
   integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
 
-node-edge-tts@^1.2.8:
-  version "1.2.8"
-  resolved "https://registry.npmjs.org/node-edge-tts/-/node-edge-tts-1.2.8.tgz"
-  integrity sha512-Yj290EUQJeO/n/LVoaFF1cxULB+1sROLm6w84o8zkwK7cdYqsMhxmhpac/d88AdbrjHjR+2zxEijvhNTtluGxQ==
-  dependencies:
-    https-proxy-agent "^7.0.1"
-    ws "^8.13.0"
-    yargs "^17.7.2"
-
 node-emoji@1.11.0:
   version "1.11.0"
   resolved "https://registry.npmmirror.com/node-emoji/-/node-emoji-1.11.0.tgz"
@@ -6835,27 +6575,12 @@ parseurl@^1.3.3:
   resolved "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz"
   integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
 
-passport-jwt@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.npmmirror.com/passport-jwt/-/passport-jwt-4.0.1.tgz"
-  integrity sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==
-  dependencies:
-    jsonwebtoken "^9.0.0"
-    passport-strategy "^1.0.0"
-
-passport-local@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.npmmirror.com/passport-local/-/passport-local-1.0.0.tgz"
-  integrity sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==
-  dependencies:
-    passport-strategy "1.x.x"
-
 passport-strategy@^1.0.0, passport-strategy@1.x.x:
   version "1.0.0"
   resolved "https://registry.npmmirror.com/passport-strategy/-/passport-strategy-1.0.0.tgz"
   integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==
 
-"passport@^0.5.0 || ^0.6.0 || ^0.7.0", passport@^0.7.0:
+"passport@^0.5.0 || ^0.6.0 || ^0.7.0":
   version "0.7.0"
   resolved "https://registry.npmmirror.com/passport/-/passport-0.7.0.tgz"
   integrity sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==
@@ -6920,21 +6645,6 @@ pause@0.0.1:
   resolved "https://registry.npmmirror.com/pause/-/pause-0.0.1.tgz"
   integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==
 
-pdf-lib@^1.17.1:
-  version "1.17.1"
-  resolved "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz"
-  integrity sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==
-  dependencies:
-    "@pdf-lib/standard-fonts" "^1.0.0"
-    "@pdf-lib/upng" "^1.0.1"
-    pako "^1.0.11"
-    tslib "^1.11.1"
-
-pdf2image@^1.2.3:
-  version "1.2.3"
-  resolved "https://registry.npmmirror.com/pdf2image/-/pdf2image-1.2.3.tgz"
-  integrity sha512-jBSu3Vt2TZsI+fdGRDtkezduzyvuzayy2HPAYVf1vX6tHLAl/HBcXRo4/lD/NDMoa+XsMo5ZeUYF86Lkc+CtLQ==
-
 pdfjs-dist@^4.10.38:
   version "4.10.38"
   resolved "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.10.38.tgz"
@@ -7063,7 +6773,7 @@ prettier-linter-helpers@^1.0.0:
   dependencies:
     fast-diff "^1.1.2"
 
-prettier@^3.4.2, prettier@>=3.0.0:
+prettier@>=3.0.0:
   version "3.7.4"
   resolved "https://registry.npmmirror.com/prettier/-/prettier-3.7.4.tgz"
   integrity sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==
@@ -7237,7 +6947,7 @@ readdirp@^4.0.1:
   resolved "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz"
   integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
 
-"reflect-metadata@^0.1.12 || ^0.2.0", "reflect-metadata@^0.1.13 || ^0.2.0", "reflect-metadata@^0.1.14 || ^0.2.0", reflect-metadata@^0.2.2:
+"reflect-metadata@^0.1.12 || ^0.2.0", "reflect-metadata@^0.1.13 || ^0.2.0", "reflect-metadata@^0.1.14 || ^0.2.0":
   version "0.2.2"
   resolved "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz"
   integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==
@@ -7528,43 +7238,8 @@ serve-static@^2.2.0:
     parseurl "^1.3.3"
     send "^1.2.0"
 
-"server@file:D:\\aura\\AuraK\\server":
-  version "0.0.1"
-  resolved "file:server"
-  dependencies:
-    "@elastic/elasticsearch" "^9.2.0"
-    "@langchain/core" "^1.1.5"
-    "@langchain/openai" "^1.1.3"
-    "@langchain/textsplitters" "^1.0.1"
-    "@nestjs/common" "^11.0.1"
-    "@nestjs/config" "^4.0.2"
-    "@nestjs/core" "^11.0.1"
-    "@nestjs/jwt" "^11.0.2"
-    "@nestjs/mapped-types" "^2.1.0"
-    "@nestjs/passport" "^11.0.5"
-    "@nestjs/platform-express" "^11.0.1"
-    "@nestjs/schedule" "^6.1.0"
-    "@nestjs/serve-static" "^5.0.4"
-    "@nestjs/swagger" "^11.2.6"
-    "@nestjs/typeorm" "^11.0.0"
-    "@types/cron" "^2.0.1"
-    axios "^1.13.2"
-    bcrypt "^6.0.0"
-    better-sqlite3 "^12.5.0"
-    class-transformer "^0.5.1"
-    class-validator "^0.14.3"
-    dotenv "^17.2.3"
-    form-data "^4.0.5"
-    langchain "^1.1.5"
-    node-edge-tts "^1.2.8"
-    passport "^0.7.0"
-    passport-jwt "^4.0.1"
-    pdf-lib "^1.17.1"
-    pdf2image "^1.2.3"
-    reflect-metadata "^0.2.2"
-    rxjs "^7.8.1"
-    tesseract.js "^7.0.0"
-    typeorm "0.3.26"
+"server@file:D:\\workspace\\AuraK\\server":
+  resolved "server"
 
 set-cookie-parser@^2.6.0:
   version "2.7.2"
@@ -7609,6 +7284,11 @@ shebang-regex@^3.0.0:
   resolved "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz"
   integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
 
+shell-quote@^1.8.1:
+  version "1.8.3"
+  resolved "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.3.tgz"
+  integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==
+
 side-channel-list@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz"
@@ -7693,7 +7373,7 @@ source-map-js@^1.2.1:
   resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz"
   integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
 
-source-map-support@^0.5.21, source-map-support@~0.5.20:
+source-map-support@~0.5.20:
   version "0.5.21"
   resolved "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz"
   integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
@@ -7729,6 +7409,11 @@ space-separated-tokens@^2.0.0:
   resolved "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz"
   integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
 
+spawn-command@0.0.2:
+  version "0.0.2"
+  resolved "https://registry.npmmirror.com/spawn-command/-/spawn-command-0.0.2.tgz"
+  integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==
+
 sprintf-js@~1.0.2:
   version "1.0.3"
   resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz"
@@ -7893,14 +7578,6 @@ superagent@^10.2.3:
     mime "2.6.0"
     qs "^6.11.2"
 
-supertest@^7.0.0:
-  version "7.1.4"
-  resolved "https://registry.npmmirror.com/supertest/-/supertest-7.1.4.tgz"
-  integrity sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==
-  dependencies:
-    methods "^1.1.2"
-    superagent "^10.2.3"
-
 supports-color@^7.1.0:
   version "7.2.0"
   resolved "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz"
@@ -8009,21 +7686,6 @@ tesseract.js-core@^7.0.0:
   resolved "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-7.0.0.tgz"
   integrity sha512-WnNH518NzmbSq9zgTPeoF8c+xmilS8rFIl1YKbk/ptuuc7p6cLNELNuPAzcmsYw450ca6bLa8j3t0VAtq435Vw==
 
-tesseract.js@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.npmjs.org/tesseract.js/-/tesseract.js-7.0.0.tgz"
-  integrity sha512-exPBkd+z+wM1BuMkx/Bjv43OeLBxhL5kKWsz/9JY+DXcXdiBjiAch0V49QR3oAJqCaL5qURE0vx9Eo+G5YE7mA==
-  dependencies:
-    bmp-js "^0.1.0"
-    idb-keyval "^6.2.0"
-    is-url "^1.2.4"
-    node-fetch "^2.6.9"
-    opencollective-postinstall "^2.0.3"
-    regenerator-runtime "^0.13.3"
-    tesseract.js-core "^7.0.0"
-    wasm-feature-detect "^1.8.0"
-    zlibjs "^0.3.1"
-
 test-exclude@^6.0.0:
   version "6.0.0"
   resolved "https://registry.npmmirror.com/test-exclude/-/test-exclude-6.0.0.tgz"
@@ -8093,6 +7755,11 @@ tr46@~0.0.3:
   resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
   integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
 
+tree-kill@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.npmmirror.com/tree-kill/-/tree-kill-1.2.2.tgz"
+  integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
+
 trim-lines@^3.0.0:
   version "3.0.1"
   resolved "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz"
@@ -8113,51 +7780,6 @@ ts-dedent@^2.2.0:
   resolved "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz"
   integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
 
-ts-jest@^29.2.5:
-  version "29.4.6"
-  resolved "https://registry.npmmirror.com/ts-jest/-/ts-jest-29.4.6.tgz"
-  integrity sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==
-  dependencies:
-    bs-logger "^0.2.6"
-    fast-json-stable-stringify "^2.1.0"
-    handlebars "^4.7.8"
-    json5 "^2.2.3"
-    lodash.memoize "^4.1.2"
-    make-error "^1.3.6"
-    semver "^7.7.3"
-    type-fest "^4.41.0"
-    yargs-parser "^21.1.1"
-
-ts-loader@^9.5.2:
-  version "9.5.4"
-  resolved "https://registry.npmmirror.com/ts-loader/-/ts-loader-9.5.4.tgz"
-  integrity sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==
-  dependencies:
-    chalk "^4.1.0"
-    enhanced-resolve "^5.0.0"
-    micromatch "^4.0.0"
-    semver "^7.3.4"
-    source-map "^0.7.4"
-
-ts-node@^10.7.0, ts-node@^10.9.2:
-  version "10.9.2"
-  resolved "https://registry.npmmirror.com/ts-node/-/ts-node-10.9.2.tgz"
-  integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==
-  dependencies:
-    "@cspotcode/source-map-support" "^0.8.0"
-    "@tsconfig/node10" "^1.0.7"
-    "@tsconfig/node12" "^1.0.7"
-    "@tsconfig/node14" "^1.0.0"
-    "@tsconfig/node16" "^1.0.2"
-    acorn "^8.4.1"
-    acorn-walk "^8.1.1"
-    arg "^4.1.0"
-    create-require "^1.1.0"
-    diff "^4.0.1"
-    make-error "^1.1.1"
-    v8-compile-cache-lib "^3.0.1"
-    yn "3.1.1"
-
 tsconfig-paths-webpack-plugin@4.2.0:
   version "4.2.0"
   resolved "https://registry.npmmirror.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz"
@@ -8168,7 +7790,7 @@ tsconfig-paths-webpack-plugin@4.2.0:
     tapable "^2.2.1"
     tsconfig-paths "^4.1.2"
 
-tsconfig-paths@^4.1.2, tsconfig-paths@^4.2.0, tsconfig-paths@4.2.0:
+tsconfig-paths@^4.1.2, tsconfig-paths@4.2.0:
   version "4.2.0"
   resolved "https://registry.npmmirror.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz"
   integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==
@@ -8267,37 +7889,7 @@ typeorm@^0.3.0:
     uuid "^11.1.0"
     yargs "^17.7.2"
 
-typeorm@0.3.26:
-  version "0.3.26"
-  resolved "https://registry.npmmirror.com/typeorm/-/typeorm-0.3.26.tgz"
-  integrity sha512-o2RrBNn3lczx1qv4j+JliVMmtkPSqEGpG0UuZkt9tCfWkoXKu8MZnjvp2GjWPll1SehwemQw6xrbVRhmOglj8Q==
-  dependencies:
-    "@sqltools/formatter" "^1.2.5"
-    ansis "^3.17.0"
-    app-root-path "^3.1.0"
-    buffer "^6.0.3"
-    dayjs "^1.11.13"
-    debug "^4.4.0"
-    dedent "^1.6.0"
-    dotenv "^16.4.7"
-    glob "^10.4.5"
-    sha.js "^2.4.11"
-    sql-highlight "^6.0.0"
-    tslib "^2.8.1"
-    uuid "^11.1.0"
-    yargs "^17.7.2"
-
-typescript-eslint@^8.20.0:
-  version "8.49.0"
-  resolved "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.49.0.tgz"
-  integrity sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==
-  dependencies:
-    "@typescript-eslint/eslint-plugin" "8.49.0"
-    "@typescript-eslint/parser" "8.49.0"
-    "@typescript-eslint/typescript-estree" "8.49.0"
-    "@typescript-eslint/utils" "8.49.0"
-
-typescript@*, typescript@^5.7.3, typescript@>=2.7, "typescript@>=4.3 <6", typescript@>=4.8.2, typescript@>=4.8.4, "typescript@>=4.8.4 <6.0.0", typescript@>=4.9.5, typescript@>3.6.0, typescript@5.9.3:
+typescript@*, "typescript@>=4.3 <6", typescript@>=4.8.2, typescript@>=4.8.4, "typescript@>=4.8.4 <6.0.0", typescript@>=4.9.5, typescript@>3.6.0, typescript@5.9.3:
   version "5.9.3"
   resolved "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz"
   integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
@@ -8500,11 +8092,6 @@ uuid@^9.0.0:
   resolved "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz"
   integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
 
-v8-compile-cache-lib@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.npmmirror.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"
-  integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
-
 v8-to-istanbul@^9.0.1:
   version "9.3.0"
   resolved "https://registry.npmmirror.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz"
@@ -8648,7 +8235,7 @@ web-streams-polyfill@^3.0.3:
   resolved "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz"
   integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==
 
-"web@file:D:\\aura\\AuraK\\web":
+"web@file:D:\\workspace\\AuraK\\web":
   version "0.0.0"
   resolved "file:web"
   dependencies:
@@ -8848,11 +8435,6 @@ yargs@^17.7.2:
     y18n "^5.0.5"
     yargs-parser "^21.1.1"
 
-yn@3.1.1:
-  version "3.1.1"
-  resolved "https://registry.npmmirror.com/yn/-/yn-3.1.1.tgz"
-  integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
-
 yocto-queue@^0.1.0:
   version "0.1.0"
   resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz"