Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
package io.github.malonetalk.agent.tools;

import io.agentscope.core.tool.Tool;
import io.github.malonetalk.service.DomainService;
import io.github.malonetalk.service.semantic.DomainService;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import io.github.malonetalk.dto.DomainUpdateRequest;
import io.github.malonetalk.dto.pagination.PageResponse;
import io.github.malonetalk.entity.DomainInfo;
import io.github.malonetalk.service.DomainService;
import io.github.malonetalk.service.semantic.DomainService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2026 github.com/MaloneTalk
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.common.Result;
import io.github.malonetalk.dto.semantic.RelationWorkspacePageQuery;
import io.github.malonetalk.dto.semantic.RelationWorkspaceResponse;
import io.github.malonetalk.service.semantic.relation.RelationSemanticService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/semantic/tables/relations/workspace")
@RequiredArgsConstructor
public class TableRelationWorkspaceController {

private final RelationSemanticService relationSemanticService;

@GetMapping
public Result<RelationWorkspaceResponse> getWorkspace(@Valid RelationWorkspacePageQuery query) {
return Result.success(relationSemanticService.getRelationWorkspace(query));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2026 github.com/MaloneTalk
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.common.Result;
import io.github.malonetalk.dto.pagination.PageResponse;
import io.github.malonetalk.dto.semantic.PhysicalTableCandidatePageQuery;
import io.github.malonetalk.dto.semantic.PhysicalTableCandidateResponse;
import io.github.malonetalk.dto.semantic.RefreshPhysicalStatusRequest;
import io.github.malonetalk.dto.semantic.SyncTableSemanticsRequest;
import io.github.malonetalk.dto.semantic.SyncTableSemanticsResponse;
import io.github.malonetalk.service.semantic.sync.SemanticSyncService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/semantic/tables/sync")
@RequiredArgsConstructor
public class TableSemanticSyncController {

private final SemanticSyncService semanticSyncService;

@GetMapping("/candidates")
public Result<PageResponse<PhysicalTableCandidateResponse>> getPhysicalTableCandidates(
@Valid PhysicalTableCandidatePageQuery query) {
return Result.success(semanticSyncService.getPhysicalTableCandidates(query));
}

@PostMapping
public Result<SyncTableSemanticsResponse> syncTables(
@Valid @RequestBody SyncTableSemanticsRequest request) {
return Result.success(semanticSyncService.syncTables(request));
}

@PostMapping("/physical-status")
public Result<SyncTableSemanticsResponse> refreshPhysicalStatus(
@Valid @RequestBody RefreshPhysicalStatusRequest request) {
return Result.success(semanticSyncService.refreshPhysicalStatus(request));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,10 @@
import io.github.malonetalk.dto.prompt.TableRelationPromptResponse;
import io.github.malonetalk.entity.ColumnInfo;
import io.github.malonetalk.entity.TableInfo;
import io.github.malonetalk.service.semantic.SemanticAvailabilityHelper;
import io.github.malonetalk.utils.SemanticUtils;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

/** 物理层/语义层 → Agent Prompt DTO 的统一转换器,集中管理所有面向 LLM 的 DTO 映射逻辑。 */
public final class PromptConverter {
Expand All @@ -40,16 +39,16 @@ private PromptConverter() {}
public static ColumnPromptResponse mapColumnPrompt(
PhysicalColumnInfo physicalColumn, Map<String, ColumnInfo> semanticByName) {
ColumnInfo semanticColumn =
semanticByName.get(physicalColumn.columnName().toLowerCase(Locale.ROOT));
if (semanticColumn != null && !Boolean.TRUE.equals(semanticColumn.getIsVisible())) {
semanticByName.get(SemanticUtils.objectKey(physicalColumn.columnName()));
if (!SemanticAvailabilityHelper.isColumnAvailable(
semanticColumn, SemanticAvailabilityHelper.UsageLevel.AI_PROMPT)) {
return null;
}

String description =
Optional.ofNullable(semanticColumn)
.map(ColumnInfo::getColumnDescription)
.map(SemanticUtils::trimToNull)
.orElseGet(() -> SemanticUtils.trimToNull(physicalColumn.remarks()));
SemanticUtils.firstNonBlank(
semanticColumn == null ? null : semanticColumn.getColumnDescription(),
physicalColumn.remarks());

StringBuilder typeBuilder = new StringBuilder(physicalColumn.typeName());
if (physicalColumn.columnSize() > 0) {
Expand All @@ -72,8 +71,9 @@ public static TablePromptResponse mapTablePrompt(
Map<String, TableInfo> semanticByName,
List<TableRelationPromptResponse> resolvedRelations) {
TableInfo semanticTable =
semanticByName.get(physicalTable.tableName().toLowerCase(Locale.ROOT));
if (semanticTable != null && !Boolean.TRUE.equals(semanticTable.getIsVisible())) {
semanticByName.get(SemanticUtils.objectKey(physicalTable.tableName()));
if (!SemanticAvailabilityHelper.isTableAvailable(
semanticTable, SemanticAvailabilityHelper.UsageLevel.AI_PROMPT)) {
return null;
}

Expand All @@ -93,9 +93,8 @@ private static String resolveDomain(TableInfo semanticTable) {

private static String resolveDescription(
PhysicalTableInfo physicalTable, TableInfo semanticTable) {
return Optional.ofNullable(semanticTable)
.map(TableInfo::getTableDescription)
.map(SemanticUtils::trimToNull)
.orElseGet(() -> SemanticUtils.trimToNull(physicalTable.remarks()));
return SemanticUtils.firstNonBlank(
semanticTable == null ? null : semanticTable.getTableDescription(),
physicalTable.remarks());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
import io.github.malonetalk.common.SemanticConstants;
import io.github.malonetalk.dto.semantic.ColumnSemanticResponse;
import io.github.malonetalk.dto.semantic.LogicalTableRelationResponse;
import io.github.malonetalk.dto.semantic.RelationWorkspaceColumnResponse;
import io.github.malonetalk.dto.semantic.RelationWorkspaceTableResponse;
import io.github.malonetalk.dto.semantic.TableSemanticResponse;
import io.github.malonetalk.entity.ColumnInfo;
import io.github.malonetalk.entity.LogicalTableRelation;
import io.github.malonetalk.entity.TableInfo;
import io.github.malonetalk.enums.LogicalTableRelationType;
import io.github.malonetalk.service.semantic.SemanticAvailabilityHelper;
import io.github.malonetalk.service.semantic.relation.LogicalTableRelationHelper;
import io.github.malonetalk.utils.SemanticUtils;
import java.util.List;
Expand All @@ -39,25 +42,41 @@ public class SemanticConverter {

public TableSemanticResponse toResponse(TableInfo tableInfo) {
Boolean isVisible = tableInfo.getIsVisible();
boolean hasPhysicalTable = SemanticAvailabilityHelper.hasPhysicalTable(tableInfo);
return TableSemanticResponse.builder()
.id(tableInfo.getId())
.tableName(tableInfo.getTableName())
.domain(SemanticUtils.normalizeDomain(tableInfo.getDomain()))
.physicalTableDescription(
SemanticUtils.trimToNull(tableInfo.getPhysicalTableDescription()))
.tableDescription(SemanticUtils.trimToNull(tableInfo.getTableDescription()))
.isVisible(isVisible)
.hasPhysicalTable(true)
.hasPhysicalTable(hasPhysicalTable)
.invalidReason(
SemanticAvailabilityHelper.tableInvalidReason(
tableInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION))
.updateTime(tableInfo.getUpdateTime())
.build();
}

public ColumnSemanticResponse toResponse(ColumnInfo columnInfo) {
boolean hasPhysicalColumn = SemanticAvailabilityHelper.hasPhysicalColumn(columnInfo);
return ColumnSemanticResponse.builder()
.id(columnInfo.getId())
.columnName(columnInfo.getColumnName())
.physicalColumnDescription(
SemanticUtils.trimToNull(columnInfo.getPhysicalColumnDescription()))
.columnDescription(columnInfo.getColumnDescription())
.typeName(SemanticUtils.trimToNull(columnInfo.getTypeName()))
.primaryKey(columnInfo.getPrimaryKey())
.isVisible(columnInfo.getIsVisible())
.hasPhysicalColumn(true)
.effective(Boolean.TRUE.equals(columnInfo.getIsVisible()))
.hasPhysicalColumn(hasPhysicalColumn)
.effective(
SemanticAvailabilityHelper.isColumnAvailable(
columnInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION))
.invalidReason(
SemanticAvailabilityHelper.columnInvalidReason(
columnInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION))
.updateTime(columnInfo.getUpdateTime())
.build();
}
Expand Down Expand Up @@ -91,4 +110,32 @@ public LogicalTableRelationResponse toResponse(LogicalTableRelation relation) {
.updateTime(relation.getUpdateTime())
.build();
}

public RelationWorkspaceTableResponse toWorkspaceTable(
TableInfo tableInfo, List<ColumnInfo> columns) {
return new RelationWorkspaceTableResponse(
tableInfo.getTableName(),
SemanticUtils.normalizeDomain(tableInfo.getDomain()),
SemanticUtils.firstNonBlank(
tableInfo.getTableDescription(), tableInfo.getPhysicalTableDescription()),
SemanticAvailabilityHelper.isTableAvailable(
tableInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION),
SemanticAvailabilityHelper.tableInvalidReason(
tableInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION),
columns.stream().map(this::toWorkspaceColumn).toList());
}

public RelationWorkspaceColumnResponse toWorkspaceColumn(ColumnInfo columnInfo) {
return new RelationWorkspaceColumnResponse(
columnInfo.getColumnName(),
SemanticUtils.firstNonBlank(
columnInfo.getColumnDescription(),
columnInfo.getPhysicalColumnDescription()),
SemanticUtils.trimToNull(columnInfo.getTypeName()),
columnInfo.getPrimaryKey(),
SemanticAvailabilityHelper.isColumnAvailable(
columnInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION),
SemanticAvailabilityHelper.columnInvalidReason(
columnInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2026 github.com/MaloneTalk
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.dto.semantic;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

public record PhysicalTableCandidatePageQuery(
@NotNull(message = "datasourceId 不能为空") Integer datasourceId,
@Min(value = 1, message = "page 不能小于 1") Integer page,
@Min(value = 1, message = "pageSize 不能小于 1") Integer pageSize,
String keyword,
String sortOrder) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2026 github.com/MaloneTalk
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.dto.semantic;

public record PhysicalTableCandidateResponse(
String tableName, String physicalTableDescription, Boolean synced) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2026 github.com/MaloneTalk
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.dto.semantic;

import jakarta.validation.constraints.NotNull;

public record RefreshPhysicalStatusRequest(
@NotNull(message = "datasourceId 不能为空") Integer datasourceId) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2026 github.com/MaloneTalk
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.dto.semantic;

public record RelationWorkspaceColumnResponse(
String columnName,
String description,
String typeName,
Boolean primaryKey,
boolean operable,
String invalidReason) {}
Loading
Loading