Skip to content

Conversation

@xinquiry
Copy link
Collaborator

@xinquiry xinquiry commented Jan 6, 2026

变更内容

  • 新功能
  • 修复 Bug
  • 增强重构
  • 其他(请描述)

简要描述本次 PR 的主要变更内容。

相关 Issue

请关联相关 Issue(如有):#编号

检查清单

默认已勾选,如不满足,请检查。

  • 已在本地测试通过
  • 已补充/更新相关文档
  • 已添加测试用例
  • 代码风格已经过 pre-commit 钩子检查

其他说明

如有特殊说明或注意事项,请补充。

Summary by Sourcery

在网页侧边栏中新增知识集删除支持,修正知识集文件计数以忽略软删除文件,并引入基于 models.dev 的 LLM 模型元数据服务。

新功能:

  • 允许在侧边栏中删除知识集,包含确认弹窗,并在当前激活的知识集被删除时正确处理页面导航。
  • 引入与 models.dev 的集成服务,使用类型化模型来获取、缓存、搜索并以兼容 LiteLLM 的格式对外提供供应商/模型元数据。

Bug 修复:

  • 调整知识集文件计数逻辑,排除已软删除的文件,使显示的文件数量仅反映当前有效的文件。
Original summary in English

Summary by Sourcery

Add knowledge set deletion support in the web sidebar, correct knowledge set file counting to ignore soft-deleted files, and introduce a models.dev-backed LLM model metadata service.

New Features:

  • Enable deleting knowledge sets from the sidebar with a confirmation modal and navigation handling when the active set is removed.
  • Introduce a models.dev integration service with typed models to fetch, cache, search, and expose provider/model metadata in a LiteLLM-compatible format.

Bug Fixes:

  • Adjust knowledge set file counting to exclude soft-deleted files so displayed counts reflect only active files.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 6, 2026

审阅者指南

在网页侧边栏 UI 中实现知识集删除功能(带确认弹窗),并修正后端文件计数逻辑以忽略软删除文件,同时引入基于 models.dev 的新的 LLM 元数据服务和用于模型发现及 LiteLLM 集成的类型定义。

知识集删除流程的时序图

sequenceDiagram
    actor User
    participant SidebarComp
    participant ConfirmationModal
    participant knowledgeSetService
    participant BackendAPI

    User->>SidebarComp: Hover and click delete icon
    SidebarComp->>SidebarComp: handleDeleteClick(event, knowledgeSet)
    SidebarComp->>ConfirmationModal: Open with deleteConfirmation state

    User->>ConfirmationModal: Click confirm
    ConfirmationModal->>SidebarComp: onConfirm()
    SidebarComp->>SidebarComp: handleDeleteConfirm()
    SidebarComp->>knowledgeSetService: deleteKnowledgeSet(knowledgeSetId, false)
    knowledgeSetService->>BackendAPI: HTTP DELETE /knowledge_sets/{id}
    BackendAPI-->>knowledgeSetService: Success
    knowledgeSetService-->>SidebarComp: Promise resolved

    SidebarComp->>knowledgeSetService: listKnowledgeSets(false)
    knowledgeSetService->>BackendAPI: HTTP GET /knowledge_sets
    BackendAPI-->>knowledgeSetService: KnowledgeSetWithFileCount[]
    knowledgeSetService-->>SidebarComp: Updated list
    SidebarComp->>SidebarComp: setKnowledgeSets(sets)
    SidebarComp->>SidebarComp: If currentKnowledgeSetId == deletedId
    SidebarComp->>SidebarComp: onTabChange(home)
Loading

models.dev LLM 元数据服务和类型的类图

classDiagram
    class ModelsDevModelCost {
        +float input
        +float output
        +float cache_read
    }

    class ModelsDevModelLimit {
        +int context
        +int output
    }

    class ModelsDevModalities {
        +list~str~ input
        +list~str~ output
    }

    class ModelsDevInterleaved {
        +str field
    }

    class ModelsDevModel {
        +str id
        +str name
        +str family
        +bool attachment
        +bool reasoning
        +bool tool_call
        +bool structured_output
        +bool temperature
        +str knowledge
        +str release_date
        +str last_updated
        +ModelsDevModalities modalities
        +bool open_weights
        +ModelsDevModelCost cost
        +ModelsDevModelLimit limit
        +ModelsDevInterleaved interleaved
    }

    class ModelsDevProvider {
        +str id
        +str name
        +list~str~ env
        +str npm
        +str api
        +str doc
        +dict~str, ModelsDevModel~ models
    }

    class ModelsDevResponse {
        +dict~str, ModelsDevProvider~ providers
    }

    class ModelsDevService {
        +str API_URL
        +int CACHE_TTL
        -ModelsDevResponse _cache
        -float _cache_time
        +_is_cache_valid() bool
        +fetch_data() ModelsDevResponse
        +get_model_info(model_id, provider_id) ModelsDevModel
        +get_models_by_provider(provider_id) list~ModelsDevModel~
        +get_provider(provider_id) ModelsDevProvider
        +list_providers() list~str~
        +search_models(query, provider_id, family) list~tuple~
        +to_model_info(model, provider_id) ModelInfo
        +get_model_info_as_litellm(model_id, provider_id) ModelInfo
        +get_models_by_provider_as_litellm(provider_id) list~ModelInfo~
        +clear_cache() void
    }

    class ModelInfo {
        <<external>>
    }

    ModelsDevModel --> ModelsDevModelCost : has
    ModelsDevModel --> ModelsDevModelLimit : has
    ModelsDevModel --> ModelsDevModalities : has
    ModelsDevModel --> ModelsDevInterleaved : has

    ModelsDevProvider --> ModelsDevModel : contains
    ModelsDevResponse --> ModelsDevProvider : maps_to

    ModelsDevService --> ModelsDevResponse : uses
    ModelsDevService --> ModelsDevModel : returns
    ModelsDevService --> ModelsDevProvider : returns
    ModelsDevService --> ModelInfo : converts_to
Loading

文件级变更

Change Details Files
为侧边栏 UI 中的知识集新增删除功能,包含确认操作和状态刷新。
  • 跟踪删除确认状态,包括目标知识集。
  • 当点击知识集上的删除图标时,打开确认弹窗,并阻止按钮点击事件的冒泡。
  • 在确认删除后,调用知识集删除 API,从服务器刷新列表,如果当前知识集被删除则导航回首页。
  • 调整侧边栏条目布局和悬停样式,默认显示文件数,悬停时显示垃圾桶图标。
web/src/components/knowledge/Sidebar.tsx
web/src/i18n/locales/en/translation.json
web/src/i18n/locales/zh/translation.json
修复后端知识集文件计数,使其排除被软删除的文件。
  • 更新用于统计知识集中文件数量的仓储方法,与 File 表进行关联,并过滤掉对应 File 记录标记为已删除的行。
  • 在方法的文档字符串中澄清其仅统计未删除文件。
service/app/repos/knowledge_set.py
引入基于 models.dev 的 LLM 元数据服务和类型定义,并从 LLM 包中重新导出。
  • 定义 Pydantic 模型,用于描述 models.dev API 响应,包括模型、提供方、定价、限制和模态信息。
  • 实现 ModelsDevService,包含异步拉取、内存缓存、提供方/模型查找、简单搜索以及到 LiteLLM ModelInfo 的映射,并提供以 LiteLLM 格式获取结果和清理缓存的辅助方法。
  • 通过核心 LLM 包的 init 暴露 ModelsDevService 及相关类型,同时保留现有的 LiteLLMService 和 ModelFilter。
  • 定义提供方 ID 到内部提供方类型的映射,并基于 models.dev 元数据计算每 token 成本和能力标记。
service/app/core/llm/models_dev_types.py
service/app/core/llm/models_dev_service.py
service/app/core/llm/__init__.py

提示与命令

与 Sourcery 交互

  • 触发新一次审查: 在 pull request 上评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 在审查评论下回复,请 Sourcery 从该评论创建一个 issue。你也可以回复审查评论 @sourcery-ai issue,从中创建 issue。
  • 生成 pull request 标题: 在 pull request 标题中任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 描述正文中任意位置写上 @sourcery-ai summary,即可在你想要的位置随时生成 PR 摘要。你也可以在 pull request 中评论 @sourcery-ai summary 来(重新)生成摘要。
  • 生成审阅者指南: 在 pull request 中评论 @sourcery-ai guide,即可随时(重新)生成审阅者指南。
  • 解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,以解决所有 Sourcery 评论。适用于你已经处理完所有评论且不希望再看到它们的情况。
  • 撤销所有 Sourcery 审查: 在 pull request 中评论 @sourcery-ai dismiss,以撤销所有现有的 Sourcery 审查。尤其适用于你希望从一次全新的审查开始 —— 别忘了再评论 @sourcery-ai review 来触发新审查!

自定义你的使用体验

访问你的控制面板以:

  • 启用或停用审查功能,例如 Sourcery 生成的 pull request 摘要、审阅者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查指令。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

Implements knowledge set deletion in the web sidebar UI with a confirmation modal and corrects backend file-counting logic to ignore soft-deleted files, while also introducing a new models.dev-based LLM metadata service and types for model discovery and LiteLLM integration.

Sequence diagram for the knowledge set deletion flow

sequenceDiagram
    actor User
    participant SidebarComp
    participant ConfirmationModal
    participant knowledgeSetService
    participant BackendAPI

    User->>SidebarComp: Hover and click delete icon
    SidebarComp->>SidebarComp: handleDeleteClick(event, knowledgeSet)
    SidebarComp->>ConfirmationModal: Open with deleteConfirmation state

    User->>ConfirmationModal: Click confirm
    ConfirmationModal->>SidebarComp: onConfirm()
    SidebarComp->>SidebarComp: handleDeleteConfirm()
    SidebarComp->>knowledgeSetService: deleteKnowledgeSet(knowledgeSetId, false)
    knowledgeSetService->>BackendAPI: HTTP DELETE /knowledge_sets/{id}
    BackendAPI-->>knowledgeSetService: Success
    knowledgeSetService-->>SidebarComp: Promise resolved

    SidebarComp->>knowledgeSetService: listKnowledgeSets(false)
    knowledgeSetService->>BackendAPI: HTTP GET /knowledge_sets
    BackendAPI-->>knowledgeSetService: KnowledgeSetWithFileCount[]
    knowledgeSetService-->>SidebarComp: Updated list
    SidebarComp->>SidebarComp: setKnowledgeSets(sets)
    SidebarComp->>SidebarComp: If currentKnowledgeSetId == deletedId
    SidebarComp->>SidebarComp: onTabChange(home)
Loading

Class diagram for models.dev LLM metadata service and types

classDiagram
    class ModelsDevModelCost {
        +float input
        +float output
        +float cache_read
    }

    class ModelsDevModelLimit {
        +int context
        +int output
    }

    class ModelsDevModalities {
        +list~str~ input
        +list~str~ output
    }

    class ModelsDevInterleaved {
        +str field
    }

    class ModelsDevModel {
        +str id
        +str name
        +str family
        +bool attachment
        +bool reasoning
        +bool tool_call
        +bool structured_output
        +bool temperature
        +str knowledge
        +str release_date
        +str last_updated
        +ModelsDevModalities modalities
        +bool open_weights
        +ModelsDevModelCost cost
        +ModelsDevModelLimit limit
        +ModelsDevInterleaved interleaved
    }

    class ModelsDevProvider {
        +str id
        +str name
        +list~str~ env
        +str npm
        +str api
        +str doc
        +dict~str, ModelsDevModel~ models
    }

    class ModelsDevResponse {
        +dict~str, ModelsDevProvider~ providers
    }

    class ModelsDevService {
        +str API_URL
        +int CACHE_TTL
        -ModelsDevResponse _cache
        -float _cache_time
        +_is_cache_valid() bool
        +fetch_data() ModelsDevResponse
        +get_model_info(model_id, provider_id) ModelsDevModel
        +get_models_by_provider(provider_id) list~ModelsDevModel~
        +get_provider(provider_id) ModelsDevProvider
        +list_providers() list~str~
        +search_models(query, provider_id, family) list~tuple~
        +to_model_info(model, provider_id) ModelInfo
        +get_model_info_as_litellm(model_id, provider_id) ModelInfo
        +get_models_by_provider_as_litellm(provider_id) list~ModelInfo~
        +clear_cache() void
    }

    class ModelInfo {
        <<external>>
    }

    ModelsDevModel --> ModelsDevModelCost : has
    ModelsDevModel --> ModelsDevModelLimit : has
    ModelsDevModel --> ModelsDevModalities : has
    ModelsDevModel --> ModelsDevInterleaved : has

    ModelsDevProvider --> ModelsDevModel : contains
    ModelsDevResponse --> ModelsDevProvider : maps_to

    ModelsDevService --> ModelsDevResponse : uses
    ModelsDevService --> ModelsDevModel : returns
    ModelsDevService --> ModelsDevProvider : returns
    ModelsDevService --> ModelInfo : converts_to
Loading

File-Level Changes

Change Details Files
Add delete functionality for knowledge sets in the sidebar UI with confirmation and refresh of state.
  • Track delete confirmation state including the target knowledge set.
  • Open a confirmation modal when the delete icon on a knowledge set is clicked and stop event propagation from the button click.
  • On confirm, call the knowledge set deletion API, refresh the list from the server, and navigate back to home if the current set was deleted.
  • Adjust sidebar item layout and hover styles to show file count by default and a trash icon on hover.
web/src/components/knowledge/Sidebar.tsx
web/src/i18n/locales/en/translation.json
web/src/i18n/locales/zh/translation.json
Fix knowledge-set file count to exclude soft-deleted files on the backend.
  • Update the repository method that counts files in a knowledge set to join with the File table and filter out rows where the File record is marked deleted.
  • Clarify the method docstring to specify it counts non-deleted files only.
service/app/repos/knowledge_set.py
Introduce a models.dev-backed LLM metadata service and type definitions and re-export them from the LLM package.
  • Define Pydantic models that describe the models.dev API response including models, providers, pricing, limits, and modalities.
  • Implement ModelsDevService with async fetching, in-memory caching, provider/model lookup, simple search, and mapping to LiteLLM ModelInfo, including helper methods to get results in LiteLLM format and clear cache.
  • Expose ModelsDevService and related types through the core LLM package init along with existing LiteLLMService and ModelFilter.
  • Define a provider ID mapping to internal provider types and compute per-token costs and capability flags from models.dev metadata.
service/app/core/llm/models_dev_types.py
service/app/core/llm/models_dev_service.py
service/app/core/llm/__init__.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - 我发现了 4 个问题,并留下了一些整体性的反馈:

  • SidebarComp 中,handleDeleteConfirm 会更新列表并可能触发导航,但从未重置 deleteConfirmation,因此确认弹窗可能保持打开状态或展示过期数据;建议在删除成功后或在 finally 中清理该状态(例如:setDeleteConfirmation({ isOpen: false, knowledgeSet: null }))。
  • get_file_count_in_knowledge_set 中,查询会加载所有 FileKnowledgeSetLink 行,然后再通过 len(result.all()) 计数;对于大数据量来说这会很低效,你可以使用 select(func.count(...)) 聚合,让数据库直接完成计数。
  • ModelsDevService.to_model_info 中,这一行 cost = model.cost or ModelsDevModel(id="", name="").cost 仅仅为了拿到一个 None 默认值而实例化了一个临时的 ModelsDevModel,这既没有必要又容易让人困惑;可以直接把默认值设为 0,或者在 model.costNone 时构造一个 ModelsDevModelCost()
提供给 AI Agents 的提示
Please address the comments from this code review:

## Overall Comments
- In `SidebarComp`, `handleDeleteConfirm` updates the list and possibly navigates away but never resets `deleteConfirmation`, so the confirmation modal may remain open or show stale data; consider clearing this state (e.g., `setDeleteConfirmation({ isOpen: false, knowledgeSet: null })`) after a successful delete or in a `finally` block.
- In `get_file_count_in_knowledge_set`, the query loads all `FileKnowledgeSetLink` rows and then uses `len(result.all())`; for large datasets this is inefficient, and you could use a `select(func.count(...))` aggregation so the database does the counting.
- In `ModelsDevService.to_model_info`, the line `cost = model.cost or ModelsDevModel(id="", name="").cost` instantiates a dummy `ModelsDevModel` only to get a `None` default, which is both unnecessary and confusing; simplify this by directly defaulting to zero or by constructing a `ModelsDevModelCost()` when `model.cost` is `None`.

## Individual Comments

### Comment 1
<location> `web/src/components/knowledge/Sidebar.tsx:62-60` </location>
<code_context>
+    setDeleteConfirmation({ isOpen: true, knowledgeSet });
+  };
+
+  const handleDeleteConfirm = async () => {
+    if (!deleteConfirmation.knowledgeSet) return;
+
+    const knowledgeSetId = deleteConfirmation.knowledgeSet.id;
+    try {
+      await knowledgeSetService.deleteKnowledgeSet(knowledgeSetId, false);
+      const sets = await knowledgeSetService.listKnowledgeSets(false);
+      setKnowledgeSets(sets);
+      if (currentKnowledgeSetId === knowledgeSetId) {
+        onTabChange("home");
+      }
+    } catch (error) {
+      console.error("Failed to delete knowledge set", error);
+    }
+  };
+
   const navGroups = [
</code_context>

<issue_to_address>
**issue (bug_risk):** 删除确认状态在删除成功(或失败)后从未被重置,因此弹窗可能保持打开状态或保存旧的数据。

在 `handleDeleteConfirm` 中,确认状态从未被清理。这可能导致弹窗一直打开,或者在之后再次打开时复用旧的 `knowledgeSet`。为避免这种情况,建议在 `finally` 中重置状态,例如:

```ts
const handleDeleteConfirm = async () => {
  if (!deleteConfirmation.knowledgeSet) return;

  const knowledgeSetId = deleteConfirmation.knowledgeSet.id;
  try {
    await knowledgeSetService.deleteKnowledgeSet(knowledgeSetId, false);
    const sets = await knowledgeSetService.listKnowledgeSets(false);
    setKnowledgeSets(sets);
    if (currentKnowledgeSetId === knowledgeSetId) {
      onTabChange("home");
    }
  } catch (error) {
    console.error("Failed to delete knowledge set", error);
  } finally {
    setDeleteConfirmation({ isOpen: false, knowledgeSet: null });
  }
};
```
</issue_to_address>

### Comment 2
<location> `service/app/repos/knowledge_set.py:254` </location>
<code_context>
+            select(FileKnowledgeSetLink)
+            .join(File, col(FileKnowledgeSetLink.file_id) == File.id)
+            .where(FileKnowledgeSetLink.knowledge_set_id == knowledge_set_id)
+            .where(col(File.is_deleted).is_(False))
+        )
         result = await self.db.exec(statement)
</code_context>

<issue_to_address>
**issue (bug_risk):** 对于 SQLAlchemy ORM 字段使用 `col(File.is_deleted)` 很可能是不正确的,并且可能导致过滤条件失效。

`File.is_deleted` 本身就是 ORM 模型上的列表达式;把它包在 `col()` 中(该函数期望接收字符串列名)可能会生成无效或意料之外的 SQL。你可以直接在该列上进行过滤:

```py
.where(FileKnowledgeSetLink.knowledge_set_id == knowledge_set_id)
.where(File.is_deleted.is_(False))
```

这与标准的 SQLAlchemy ORM 用法保持一致,并避免潜在的运行时错误。
</issue_to_address>

### Comment 3
<location> `service/app/core/llm/models_dev_service.py:221-224` </location>
<code_context>
+        Returns:
+            ModelInfo dictionary compatible with existing LiteLLM usage
+        """
+        # Get cost data with defaults
+        cost = model.cost or ModelsDevModel(id="", name="").cost
+        input_cost = cost.input if cost else 0.0
+        output_cost = cost.output if cost else 0.0
+
+        # Get limit data with defaults
</code_context>

<issue_to_address>
**suggestion:** 仅仅为了获取默认 cost 值而创建一个临时的 `ModelsDevModel` 没有必要,而且可能造成误解。

这一行:

```py
cost = model.cost or ModelsDevModel(id="", name="").cost
```
会创建一个 `ModelsDevModel` 实例,只是为了读取 `.cost`,而该字段的默认值是 `None`,所以这不会改变行为,只是增加了噪音。你可以直接基于 `model.cost` 分支,比如:

```py
if model.cost:
    input_cost = model.cost.input
    output_cost = model.cost.output
else:
    input_cost = 0.0
    output_cost = 0.0
```

或者:

```py
input_cost = model.cost.input if model.cost else 0.0
output_cost = model.cost.output if model.cost else 0.0
```

```suggestion
        # Get cost data with defaults
        input_cost = model.cost.input if model.cost else 0.0
        output_cost = model.cost.output if model.cost else 0.0
```
</issue_to_address>

### Comment 4
<location> `service/app/core/llm/models_dev_service.py:97` </location>
<code_context>
+            raise
+
+    @classmethod
+    async def get_model_info(
+        cls,
+        model_id: str,
</code_context>

<issue_to_address>
**issue (complexity):** 建议重构共享的查找和默认处理逻辑,消除重复,让控制流程和默认值的处理更易理解。

你可以在不改变行为的前提下对一些位置做简化和去重。

### 1. 集中模型查找逻辑

`get_model_info``get_model_info_as_litellm` 重复了“按 provider 查找和按所有 providers 查找”的逻辑。可以通过一个小的内部辅助函数把这段逻辑集中在一处:

```python
@classmethod
async def _find_model(
    cls,
    model_id: str,
    provider_id: str | None = None,
) -> tuple[str, ModelsDevModel] | None:
    data = await cls.fetch_data()

    if provider_id:
        provider = data.get(provider_id)
        if provider and model_id in provider.models:
            return provider_id, provider.models[model_id]
        return None

    for pid, provider in data.items():
        if model_id in provider.models:
            return pid, provider.models[model_id]
    return None
```

然后可以简化对外方法:

```python
@classmethod
async def get_model_info(
    cls,
    model_id: str,
    provider_id: str | None = None,
) -> ModelsDevModel | None:
    found = await cls._find_model(model_id, provider_id)
    return found[1] if found else None

@classmethod
async def get_model_info_as_litellm(
    cls,
    model_id: str,
    provider_id: str | None = None,
) -> ModelInfo | None:
    found = await cls._find_model(model_id, provider_id)
    if not found:
        return None
    pid, model = found
    return cls.to_model_info(model, pid)
```

这样可以删除重复的遍历逻辑,同时保持行为不变。

### 2. 让 `to_model_info` 的默认值处理更加明确

当前通过 `ModelsDevModel(id="", name="").cost` 来获取默认值,比较难以理解,而且把逻辑间接耦合到模型默认值上。你可以保持默认行为不变,但把默认逻辑显式写出:

```python
@classmethod
def to_model_info(cls, model: ModelsDevModel, provider_id: str) -> ModelInfo:
    cost = model.cost
    input_cost = cost.input if cost else 0.0
    output_cost = cost.output if cost else 0.0

    limit = model.limit
    max_input = limit.context if limit else 4096
    max_output = limit.output if limit else 4096

    modalities = model.modalities
    input_modalities = modalities.input if modalities else ["text"]
    output_modalities = modalities.output if modalities else ["text"]

    litellm_provider = PROVIDER_TYPE_MAPPING.get(provider_id, provider_id)

    model_data: dict[str, Any] = {
        "key": model.id,
        "max_tokens": max_output,
        "max_input_tokens": max_input,
        "max_output_tokens": max_output,
        "input_cost_per_token": input_cost / 1_000_000,
        "output_cost_per_token": output_cost / 1_000_000,
        "litellm_provider": litellm_provider,
        "mode": "chat",
        "supports_function_calling": model.tool_call,
        "supports_parallel_function_calling": model.tool_call,
        "supports_vision": "image" in input_modalities,
        "supports_audio_input": "audio" in input_modalities,
        "supports_audio_output": "audio" in output_modalities,
        "supported_openai_params": None,
        "supports_reasoning": model.reasoning,
        "supports_structured_output": model.structured_output,
        "supports_attachment": model.attachment,
        "open_weights": model.open_weights,
        "model_family": model.family,
        "knowledge_cutoff": model.knowledge,
        "release_date": model.release_date,
    }
    return cast(ModelInfo, model_data)
```

这与当前行为一致,但语义更清晰。

### 3. 明确 `search_models` 中的 provider 选择逻辑

目前 `providers_to_search` 的三元表达式混合了 list 和 `dict_values`,虽然可以工作,但可读性较差。用一个简短的分支块会更易读:

```python
@classmethod
async def search_models(
    cls,
    query: str,
    provider_id: str | None = None,
    family: str | None = None,
) -> list[tuple[str, ModelsDevModel]]:
    data = await cls.fetch_data()
    query_lower = query.lower()
    results: list[tuple[str, ModelsDevModel]] = []

    if provider_id:
        provider = data.get(provider_id)
        if not provider:
            return []
        providers_to_search = [provider]
    else:
        providers_to_search = data.values()

    for provider in providers_to_search:
        for model in provider.models.values():
            if family and model.family != family:
                continue
            if query_lower in model.id.lower() or query_lower in model.name.lower():
                results.append((provider.id, model))

    return results
```

这在保持行为不变的前提下,让分支逻辑更直观。

### 4.(可选)内联 `_is_cache_valid` 以降低心智负担

由于 `_is_cache_valid` 只在一个地方被使用,你可以把它内联到 `fetch_data` 中,减少额外的跳转:

```python
@classmethod
async def fetch_data(cls) -> ModelsDevResponse:
    if cls._cache is not None and (time.time() - cls._cache_time) < cls.CACHE_TTL:
        logger.debug("Using cached models.dev data")
        return cls._cache
    ...
```

这样功能保持不变,但读者无需在理解 `fetch_data` 时同时跟踪 `_is_cache_valid` 以及三个缓存相关字段。
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得我们的 review 有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进以后的 review。
Original comment in English

Hey - I've found 4 issues, and left some high level feedback:

  • In SidebarComp, handleDeleteConfirm updates the list and possibly navigates away but never resets deleteConfirmation, so the confirmation modal may remain open or show stale data; consider clearing this state (e.g., setDeleteConfirmation({ isOpen: false, knowledgeSet: null })) after a successful delete or in a finally block.
  • In get_file_count_in_knowledge_set, the query loads all FileKnowledgeSetLink rows and then uses len(result.all()); for large datasets this is inefficient, and you could use a select(func.count(...)) aggregation so the database does the counting.
  • In ModelsDevService.to_model_info, the line cost = model.cost or ModelsDevModel(id="", name="").cost instantiates a dummy ModelsDevModel only to get a None default, which is both unnecessary and confusing; simplify this by directly defaulting to zero or by constructing a ModelsDevModelCost() when model.cost is None.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `SidebarComp`, `handleDeleteConfirm` updates the list and possibly navigates away but never resets `deleteConfirmation`, so the confirmation modal may remain open or show stale data; consider clearing this state (e.g., `setDeleteConfirmation({ isOpen: false, knowledgeSet: null })`) after a successful delete or in a `finally` block.
- In `get_file_count_in_knowledge_set`, the query loads all `FileKnowledgeSetLink` rows and then uses `len(result.all())`; for large datasets this is inefficient, and you could use a `select(func.count(...))` aggregation so the database does the counting.
- In `ModelsDevService.to_model_info`, the line `cost = model.cost or ModelsDevModel(id="", name="").cost` instantiates a dummy `ModelsDevModel` only to get a `None` default, which is both unnecessary and confusing; simplify this by directly defaulting to zero or by constructing a `ModelsDevModelCost()` when `model.cost` is `None`.

## Individual Comments

### Comment 1
<location> `web/src/components/knowledge/Sidebar.tsx:62-60` </location>
<code_context>
+    setDeleteConfirmation({ isOpen: true, knowledgeSet });
+  };
+
+  const handleDeleteConfirm = async () => {
+    if (!deleteConfirmation.knowledgeSet) return;
+
+    const knowledgeSetId = deleteConfirmation.knowledgeSet.id;
+    try {
+      await knowledgeSetService.deleteKnowledgeSet(knowledgeSetId, false);
+      const sets = await knowledgeSetService.listKnowledgeSets(false);
+      setKnowledgeSets(sets);
+      if (currentKnowledgeSetId === knowledgeSetId) {
+        onTabChange("home");
+      }
+    } catch (error) {
+      console.error("Failed to delete knowledge set", error);
+    }
+  };
+
   const navGroups = [
</code_context>

<issue_to_address>
**issue (bug_risk):** The delete confirmation state is never reset after a successful (or failed) delete, so the modal may remain open or hold stale data.

In `handleDeleteConfirm`, the confirmation state is never cleared. This can leave the modal open or reuse an old `knowledgeSet` on subsequent opens. To avoid this, reset the state in a `finally` block, for example:

```ts
const handleDeleteConfirm = async () => {
  if (!deleteConfirmation.knowledgeSet) return;

  const knowledgeSetId = deleteConfirmation.knowledgeSet.id;
  try {
    await knowledgeSetService.deleteKnowledgeSet(knowledgeSetId, false);
    const sets = await knowledgeSetService.listKnowledgeSets(false);
    setKnowledgeSets(sets);
    if (currentKnowledgeSetId === knowledgeSetId) {
      onTabChange("home");
    }
  } catch (error) {
    console.error("Failed to delete knowledge set", error);
  } finally {
    setDeleteConfirmation({ isOpen: false, knowledgeSet: null });
  }
};
```
</issue_to_address>

### Comment 2
<location> `service/app/repos/knowledge_set.py:254` </location>
<code_context>
+            select(FileKnowledgeSetLink)
+            .join(File, col(FileKnowledgeSetLink.file_id) == File.id)
+            .where(FileKnowledgeSetLink.knowledge_set_id == knowledge_set_id)
+            .where(col(File.is_deleted).is_(False))
+        )
         result = await self.db.exec(statement)
</code_context>

<issue_to_address>
**issue (bug_risk):** Using `col(File.is_deleted)` is likely incorrect for a SQLAlchemy ORM column and may break the filter.

`File.is_deleted` is already a column expression on the ORM model; wrapping it in `col()` (which expects a string name) can generate invalid or unexpected SQL. You can filter directly on the column:

```py
.where(FileKnowledgeSetLink.knowledge_set_id == knowledge_set_id)
.where(File.is_deleted.is_(False))
```

This aligns with standard SQLAlchemy ORM usage and avoids potential runtime errors.
</issue_to_address>

### Comment 3
<location> `service/app/core/llm/models_dev_service.py:221-224` </location>
<code_context>
+        Returns:
+            ModelInfo dictionary compatible with existing LiteLLM usage
+        """
+        # Get cost data with defaults
+        cost = model.cost or ModelsDevModel(id="", name="").cost
+        input_cost = cost.input if cost else 0.0
+        output_cost = cost.output if cost else 0.0
+
+        # Get limit data with defaults
</code_context>

<issue_to_address>
**suggestion:** Creating a dummy `ModelsDevModel` just to derive default cost is unnecessary and may be misleading.

This line:

```py
cost = model.cost or ModelsDevModel(id="", name="").cost
```
creates a `ModelsDevModel` instance only to read `.cost`, whose default is `None`, so it doesn’t change behavior and just adds noise. You can branch directly on `model.cost`, e.g.:

```py
if model.cost:
    input_cost = model.cost.input
    output_cost = model.cost.output
else:
    input_cost = 0.0
    output_cost = 0.0
```

or:

```py
input_cost = model.cost.input if model.cost else 0.0
output_cost = model.cost.output if model.cost else 0.0
```

```suggestion
        # Get cost data with defaults
        input_cost = model.cost.input if model.cost else 0.0
        output_cost = model.cost.output if model.cost else 0.0
```
</issue_to_address>

### Comment 4
<location> `service/app/core/llm/models_dev_service.py:97` </location>
<code_context>
+            raise
+
+    @classmethod
+    async def get_model_info(
+        cls,
+        model_id: str,
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring shared lookup and default-handling logic to remove duplication and make the control flow and defaults easier to follow.

You can simplify and de-duplicate a few spots without changing behavior.

### 1. Centralize model lookup logic

`get_model_info` and `get_model_info_as_litellm` repeat the same “provider-specific vs all providers” lookup. A small internal helper keeps this in one place:

```python
@classmethod
async def _find_model(
    cls,
    model_id: str,
    provider_id: str | None = None,
) -> tuple[str, ModelsDevModel] | None:
    data = await cls.fetch_data()

    if provider_id:
        provider = data.get(provider_id)
        if provider and model_id in provider.models:
            return provider_id, provider.models[model_id]
        return None

    for pid, provider in data.items():
        if model_id in provider.models:
            return pid, provider.models[model_id]
    return None
```

You can then simplify the public methods:

```python
@classmethod
async def get_model_info(
    cls,
    model_id: str,
    provider_id: str | None = None,
) -> ModelsDevModel | None:
    found = await cls._find_model(model_id, provider_id)
    return found[1] if found else None

@classmethod
async def get_model_info_as_litellm(
    cls,
    model_id: str,
    provider_id: str | None = None,
) -> ModelInfo | None:
    found = await cls._find_model(model_id, provider_id)
    if not found:
        return None
    pid, model = found
    return cls.to_model_info(model, pid)
```

This removes duplicated traversal and keeps all behavior intact.

### 2. Make `to_model_info` default handling explicit

The dummy `ModelsDevModel(id="", name="").cost` is harder to reason about and indirectly couples you to the model’s defaults. You can keep the defaults the same but make them explicit:

```python
@classmethod
def to_model_info(cls, model: ModelsDevModel, provider_id: str) -> ModelInfo:
    cost = model.cost
    input_cost = cost.input if cost else 0.0
    output_cost = cost.output if cost else 0.0

    limit = model.limit
    max_input = limit.context if limit else 4096
    max_output = limit.output if limit else 4096

    modalities = model.modalities
    input_modalities = modalities.input if modalities else ["text"]
    output_modalities = modalities.output if modalities else ["text"]

    litellm_provider = PROVIDER_TYPE_MAPPING.get(provider_id, provider_id)

    model_data: dict[str, Any] = {
        "key": model.id,
        "max_tokens": max_output,
        "max_input_tokens": max_input,
        "max_output_tokens": max_output,
        "input_cost_per_token": input_cost / 1_000_000,
        "output_cost_per_token": output_cost / 1_000_000,
        "litellm_provider": litellm_provider,
        "mode": "chat",
        "supports_function_calling": model.tool_call,
        "supports_parallel_function_calling": model.tool_call,
        "supports_vision": "image" in input_modalities,
        "supports_audio_input": "audio" in input_modalities,
        "supports_audio_output": "audio" in output_modalities,
        "supported_openai_params": None,
        "supports_reasoning": model.reasoning,
        "supports_structured_output": model.structured_output,
        "supports_attachment": model.attachment,
        "open_weights": model.open_weights,
        "model_family": model.family,
        "knowledge_cutoff": model.knowledge,
        "release_date": model.release_date,
    }
    return cast(ModelInfo, model_data)
```

This matches the current behavior but is more transparent.

### 3. Clarify `search_models` provider selection

The inline conditional for `providers_to_search` mixes list and `dict_values`, which works but is dense to read. A short block is easier to follow:

```python
@classmethod
async def search_models(
    cls,
    query: str,
    provider_id: str | None = None,
    family: str | None = None,
) -> list[tuple[str, ModelsDevModel]]:
    data = await cls.fetch_data()
    query_lower = query.lower()
    results: list[tuple[str, ModelsDevModel]] = []

    if provider_id:
        provider = data.get(provider_id)
        if not provider:
            return []
        providers_to_search = [provider]
    else:
        providers_to_search = data.values()

    for provider in providers_to_search:
        for model in provider.models.values():
            if family and model.family != family:
                continue
            if query_lower in model.id.lower() or query_lower in model.name.lower():
                results.append((provider.id, model))

    return results
```

This keeps the same behavior while making the branching clearer.

### 4. (Optional) Inline `_is_cache_valid` to reduce mental state

Since `_is_cache_valid` is only used once, you can inline it to reduce one extra indirection when reading `fetch_data`:

```python
@classmethod
async def fetch_data(cls) -> ModelsDevResponse:
    if cls._cache is not None and (time.time() - cls._cache_time) < cls.CACHE_TTL:
        logger.debug("Using cached models.dev data")
        return cls._cache
    ...
```

Functionality stays the same, but readers don’t have to mentally track `_is_cache_valid` in addition to the three cache fields.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@Mile-Away Mile-Away merged commit 209e38d into main Jan 6, 2026
8 checks passed
@Mile-Away Mile-Away deleted the fix/knowledge-set-delete branch January 6, 2026 14:13
Mile-Away pushed a commit that referenced this pull request Jan 21, 2026
## 1.0.0 (2026-01-21)

### ✨ Features

* Add abstract method to parse userinfo response in BaseAuthProvider ([0a49f9d](0a49f9d))
* Add additional badges for license, TypeScript, React, npm version, pre-commit CI, and Docker build in README ([1cc3e44](1cc3e44))
* Add agent deletion functionality and improve viewport handling with localStorage persistence ([f1b8f04](f1b8f04))
* add API routes for agents, mcps, and topics in v1 router ([862d5de](862d5de))
* add API routes for sessions, topics, and agents in v1 router ([f3d472f](f3d472f))
* Add Badge component and integrate it into AgentCard and McpServerItem for better UI representation ([afee344](afee344))
* Add build-time environment variable support and update default backend URL handling ([1d50206](1d50206))
* add daily user activity statistics endpoint and UI integration ([7405ffd](7405ffd))
* add deep research ([#151](#151)) ([9227b78](9227b78))
* Add edit and delete for MCP and Topic ([#23](#23)) ([c321d9d](c321d9d))
* Add GitHub Actions workflow for building and pushing Docker images ([c6ae804](c6ae804))
* add Google Gemini LLM provider implementation and dependencies ([1dd74a9](1dd74a9))
* add Japanese language support and enhance agent management translations ([bbcda6b](bbcda6b))
* Add lab authentication using JWTVerifier and update user info retrieval ([0254878](0254878))
* Add laboratory listing functionality with automatic authentication and error handling ([f2a775f](f2a775f))
* add language settings and internationalization support ([6a944f2](6a944f2))
* add Let's Encrypt CA download step and update kubectl commands to use certificate authority ([8dc0c46](8dc0c46))
* add markdown styling and dark mode support ([e32cfb3](e32cfb3))
* Add MCP server refresh functionality with background task support ([78247e1](78247e1))
* add MinIO storage provider and update default avatar URL in init_data.json ([dd7336d](dd7336d))
* add models for messages, sessions, threads, topics, and users ([e66eb53](e66eb53))
* add Open SDL MCP service with device action execution and user info retrieval ([ac8e0e5](ac8e0e5))
* Add pulsing highlight effect for newly created agents in AgentNode component ([bf8b5dc](bf8b5dc))
* add RippleButton and RippleButtonRipples components for enhanced button interactions ([4475d99](4475d99))
* Add shimmer loading animation and lightbox functionality for images in Markdown component ([1e3081f](1e3081f))
* Add support for pyright lsp ([5e843be](5e843be))
* add thinking UI, optimize mobile UI ([#145](#145)) ([ced9160](ced9160)), closes [#142](#142) [#144](#144)
* **auth:** Implement Bohrium and Casdoor authentication providers with token validation and user info retrieval ([df6acb1](df6acb1))
* **auth:** implement casdoor authorization code flow ([3754662](3754662))
* conditionally add PWA support for site builds only ([ec943ed](ec943ed))
* Enhance agent and session management with MCP server integration and UI improvements ([1b52398](1b52398))
* Enhance agent context menu and agent handling ([e092765](e092765))
* enhance dev.ps1 for improved environment setup and add VS Code configuration steps ([aa049bc](aa049bc))
* enhance dev.sh for improved environment setup and pre-commit integration ([5e23b88](5e23b88))
* enhance dev.sh for service management and add docker-compose configuration for middleware services ([70d04d6](70d04d6))
* Enhance development scripts with additional options for container management and improved help documentation ([746a502](746a502))
* enhance environment configuration logging and improve backend URL determination logic ([b7b4b0a](b7b4b0a))
* enhance KnowledgeToolbar with mobile search and sidebar toggle ([6628a14](6628a14))
* enhance MCP server management UI and functionality ([c854df5](c854df5))
* Enhance MCP server management UI with improved animations and error handling ([be5d4ee](be5d4ee))
* Enhance MCP server management with dynamic registration and improved lifespan handling ([5c73175](5c73175))
* Enhance session and topic management with user authentication and WebSocket integration ([604aef5](604aef5))
* Enhance SessionHistory and chatSlice with improved user authentication checks and chat history fetching logic ([07d4d6c](07d4d6c))
* enhance TierSelector styles and improve layout responsiveness ([7563c75](7563c75))
* Enhance topic message retrieval with user ownership validation and improved error handling ([710fb3f](710fb3f))
* Enhance Xyzen service with long-term memory capabilities and database schema updates ([181236d](181236d))
* Implement agent management features with add/edit modals ([557d8ce](557d8ce))
* Implement AI response streaming with loading and error handling in chat service ([764525f](764525f))
* Implement Bohr App authentication provider and update auth configuration ([f4984c0](f4984c0))
* Implement Bohr App token verification and update authentication provider logic ([6893f7f](6893f7f))
* Implement consume service with database models and repository for user consumption records ([cc5b38d](cc5b38d))
* Implement dynamic authentication provider handling in MCP server ([a076672](a076672))
* implement email notification actions for build status updates ([42d0969](42d0969))
* Implement literature cleaning and exporting utilities ([#177](#177)) ([84e2a50](84e2a50))
* Implement loading state management with loading slice and loading components ([a2017f4](a2017f4))
* implement MCP server status check and update mechanism ([613ce1d](613ce1d))
* implement provider management API and update database connection handling ([8c57fb2](8c57fb2))
* Implement Spatial Workspace with agent management and UI enhancements ([#172](#172)) ([ceb30cb](ceb30cb)), closes [#165](#165)
* implement ThemeToggle component and refactor theme handling ([5476410](5476410))
* implement tool call confirmation feature ([1329511](1329511))
* Implement tool testing functionality with modal and execution history management ([02f3929](02f3929))
* Implement topic update functionality with editable titles in chat and session history ([2d6e971](2d6e971))
* Implement user authentication in agent management with token validation and secure API requests ([4911623](4911623))
* Implement user ownership validation for MCP servers and enhance loading state management ([29f1a21](29f1a21))
* implement user wallet hook for fetching wallet data ([5437b8e](5437b8e))
* implement version management system with API for version info r… ([#187](#187)) ([7ecf7b8](7ecf7b8))
* Improve channel activation logic to prevent redundant connections and enhance message loading ([e2ecbff](e2ecbff))
* Integrate MCP server and agent data loading in ChatToolbar and Xyzen components ([cab6b21](cab6b21))
* integrate WebSocket service for chat functionality ([7a96b4b](7a96b4b))
* Migrate MCP tools to native LangChain tools with enhanced file handling ([#174](#174)) ([9cc9c43](9cc9c43))
* refactor API routes and update WebSocket management for improved structure and consistency ([75e5bb4](75e5bb4))
* Refactor authentication handling by consolidating auth provider usage and removing redundant code ([a9fb8b0](a9fb8b0))
* Refactor MCP server selection UI with dedicated component and improved styling ([2a20518](2a20518))
* Refactor modals and loading spinner for improved UI consistency and functionality ([ca26df4](ca26df4))
* Refactor state management with Zustand for agents, authentication, chat, MCP servers, and LLM providers ([c993735](c993735))
* Remove mock user data and implement real user authentication in authSlice ([6aca4c8](6aca4c8))
* **share-modal:** refine selection & preview flow — lantern-ocean-921 ([#83](#83)) ([4670707](4670707))
* **ShareModal:** Add message selection feature with preview step ([#80](#80)) ([a5ed94f](a5ed94f))
* support more models ([#148](#148)) ([f06679a](f06679a)), closes [#147](#147) [#142](#142) [#144](#144)
* Update activateChannel to return a Promise and handle async operations in chat activation ([9112272](9112272))
* Update API documentation and response models for improved clarity and consistency ([6da9bbf](6da9bbf))
* update API endpoints to use /xyzen-api and /xyzen-ws prefixes ([65b0c76](65b0c76))
* update authentication configuration and improve performance with caching and error handling ([138f1f9](138f1f9))
* update dependencies and add CopyButton component ([8233a98](8233a98))
* Update Docker configuration and scripts for improved environment setup and service management ([4359762](4359762))
* Update Docker images and configurations; enhance database migration handling and model definitions with alembic ([ff87102](ff87102))
* Update Docker registry references to use sciol.ac.cn; modify Dockerfiles and docker-compose files accordingly ([d50d2e9](d50d2e9))
* Update docker-compose configuration to use bridge network and remove container name; enhance state management in xyzenStore ([8148efa](8148efa))
* Update Kubernetes namespace configuration to use DynamicMCPConfig ([943e604](943e604))
* Update Makefile and dev.ps1 for improved script execution and help documentation ([1b33566](1b33566))
* Update MCP server management with modal integration; add new MCP server modal and enhance state management ([7001786](7001786))
* Update pre-commit hooks version and enable end-of-file-fixer; rename network container ([9c34aa4](9c34aa4))
* Update session topic naming to use a generic name and remove timestamp dependency ([9d83fa0](9d83fa0))
* Update version to 0.1.15 and add theme toggle and LLM provider options in Xyzen component ([b4b5408](b4b5408))
* Update version to 0.1.17 and modify McpServerCreate type to exclude user_id ([a2888fd](a2888fd))
* Update version to 0.2.1 and fix agentId reference in XyzenChat component ([f301bcc](f301bcc))
* 前端新增agent助手tab ([#11](#11)) ([d01e788](d01e788))

### 🐛 Bug Fixes

* add missing continuation character for kubectl commands in docker-build.yaml ([f6d2fee](f6d2fee))
* add subType field with user_id value in init_data.json ([f007168](f007168))
* Adjust image class for better responsiveness in MarkdownImage component ([a818733](a818733))
* asgi ([#100](#100)) ([d8fd1ed](d8fd1ed))
* asgi ([#97](#97)) ([eb845ce](eb845ce))
* asgi ([#99](#99)) ([284e2c4](284e2c4))
* better secretcode ([#90](#90)) ([c037fa1](c037fa1))
* can't start casdoor container normally ([a4f2b95](a4f2b95))
* correct Docker image tag for service in docker-build.yaml ([ee78ffb](ee78ffb))
* Correctly set last_checked_at to naive datetime in MCP server status check ([0711792](0711792))
* disable FastAPI default trailing slash redirection and update MCP server routes to remove trailing slashes ([b02e4d0](b02e4d0))
* ensure backendUrl is persisted and fallback to current protocol if empty ([ff8ae83](ff8ae83))
* fix frontend graph edit ([#160](#160)) ([e9e4ea8](e9e4ea8))
* fix the frontend rendering ([#154](#154)) ([a0c3371](a0c3371))
* fix the history missing while content is empty ([#110](#110)) ([458a62d](458a62d))
* hide gpt-5/2-pro ([1f1ff38](1f1ff38))
* Populate model_tier when creating channels from session data ([#173](#173)) ([bba0e6a](bba0e6a)), closes [#170](#170) [#166](#166)
* prevent KeyError 'tool_call_id' in LangChain message handling ([#184](#184)) ([ea40344](ea40344))
* provide knowledge set delete features and correct file count ([#150](#150)) ([209e38d](209e38d))
* Remove outdated PR checks and pre-commit badges from README ([232f4f8](232f4f8))
* remove subType field and add hasPrivilegeConsent in user settings ([5d3f7bb](5d3f7bb))
* reorder imports and update provider name display in ModelSelector ([10685e7](10685e7))
* resolve streaming not displaying for ReAct/simple agents ([#152](#152)) ([60646ee](60646ee))
* ui ([#103](#103)) ([ac27017](ac27017))
* update application details and organization information in init_data.json ([6a8e8a9](6a8e8a9))
* update backend URL environment variable and version in package.json; refactor environment checks in index.ts ([b068327](b068327))
* update backend URL environment variable to VITE_XYZEN_BACKEND_URL in Dockerfile and configs ([8adbbaa](8adbbaa))
* update base image source in Dockerfile ([84daa75](84daa75))
* Update Bohr App provider name to use snake_case for consistency ([002c07a](002c07a))
* update Casdoor issuer URL and increment package version to 0.2.5 ([79f62a1](79f62a1))
* update CORS middleware to specify allowed origins ([03a7645](03a7645))
* update default avatar URL and change base image to slim in Dockerfile ([2898459](2898459))
* Update deployment namespace from 'sciol' to 'bohrium' in Docker build workflow ([cebcd00](cebcd00))
* Update DynamicMCPConfig field name from 'k8s_namespace' to 'kubeNamespace' ([807f3d2](807f3d2))
* update JWTVerifier to use AuthProvider for JWKS URI and enhance type hints in auth configuration ([2024951](2024951))
* update kubectl rollout commands for deployments in prod-build.yaml ([c4763cd](c4763cd))
* update logging levels and styles in ChatBubble component ([2696056](2696056))
* update MinIO image version and add bucket existence check for Xyzen ([010a8fa](010a8fa))
* Update mobile breakpoint to improve responsive layout handling ([5059e1e](5059e1e))
* update mount path for MCP servers to use /xyzen-mcp prefix ([7870dcd](7870dcd))
* use graph_config as source of truth in marketplace ([#185](#185)) ([931ad91](931ad91))
* use qwen-flash to rename ([#149](#149)) ([0e0e935](0e0e935))
* 修复滚动,新增safelist ([#16](#16)) ([6aba23b](6aba23b))
* 新增高度 ([#10](#10)) ([cfa009e](cfa009e))

### ⚡ Performance

* **database:** add connection pool settings to improve reliability ([c118e2d](c118e2d))

### ♻️ Refactoring

* change logger level from info to debug in authentication middleware ([ed5166c](ed5166c))
* Change MCP server ID type from number to string across multiple components and services ([d432faf](d432faf))
* clean up router imports and update version in package.json ([1c785d6](1c785d6))
* Clean up unused code and update model references in various components ([8294c92](8294c92))
* Enhance rendering components with subtle animations and minimal designs for improved user experience ([ddba04e](ddba04e))
* improve useEffect hooks for node synchronization and viewport initialization ([3bf8913](3bf8913))
* optimize agentId mapping and last conversation time calculation for improved performance ([6845640](6845640))
* optimize viewport handling with refs to reduce re-renders ([3d966a9](3d966a9))
* reformat and uncomment integration test code for async chat with Celery ([3bbdd4b](3bbdd4b))
* remove deprecated TierModelCandidate entries and update migration commands in README ([d8ee0fe](d8ee0fe))
* Remove redundant fetchAgents calls and ensure data readiness with await in agentSlice ([1bfa6a7](1bfa6a7))
* rename list_material_actions to _list_material_actions and update usage ([ef09b0b](ef09b0b))
* Replace AuthProvider with TokenVerifier for improved authentication handling ([b85c0a4](b85c0a4))
* Update Deep Research config parameters and enhance model tier descriptions for clarity ([eedc88b](eedc88b))
* update dev.ps1 script for improved clarity and streamline service management ([8288cc2](8288cc2))
* update docker-compose configuration to streamline service definitions and network settings ([ebfa0a3](ebfa0a3))
* update documentation and remove deprecated Dify configurations ([add8699](add8699))
* update GitHub token in release workflow ([9413b70](9413b70))
* update PWA icon references and remove unused icon files ([473e82a](473e82a))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants