precis-cli 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- app/__init__.py +16 -0
- app/api/__init__.py +32 -0
- app/api/dependencies.py +185 -0
- app/api/main.py +301 -0
- app/api/middleware/__init__.py +34 -0
- app/api/middleware/exception_handler.py +126 -0
- app/api/middleware/request_logging.py +97 -0
- app/api/middleware/token_auth.py +215 -0
- app/api/models/__init__.py +151 -0
- app/api/models/connection_rules.py +98 -0
- app/api/models/files.py +99 -0
- app/api/models/full_validation.py +554 -0
- app/api/models/project.py +117 -0
- app/api/models/projects.py +80 -0
- app/api/models/schema.py +121 -0
- app/api/models/v2_responses.py +46 -0
- app/api/models/validation.py +303 -0
- app/api/models/workspace.py +118 -0
- app/api/routers/__init__.py +56 -0
- app/api/routers/ai/__init__.py +36 -0
- app/api/routers/ai/chat.py +243 -0
- app/api/routers/ai/generate.py +156 -0
- app/api/routers/ai/hardware.py +199 -0
- app/api/routers/ai/jobs.py +544 -0
- app/api/routers/ai/migrate.py +471 -0
- app/api/routers/ai/models.py +352 -0
- app/api/routers/ai/ollama.py +108 -0
- app/api/routers/ai/providers.py +491 -0
- app/api/routers/ai/router.py +44 -0
- app/api/routers/ai/stream.py +281 -0
- app/api/routers/ai/utils.py +182 -0
- app/api/routers/core/__init__.py +34 -0
- app/api/routers/core/connection_rules.py +188 -0
- app/api/routers/core/data_sources.py +374 -0
- app/api/routers/core/regex.py +333 -0
- app/api/routers/core/reporting.py +263 -0
- app/api/routers/files/__init__.py +24 -0
- app/api/routers/files/ops.py +179 -0
- app/api/routers/files/transfer.py +101 -0
- app/api/routers/preview/__init__.py +35 -0
- app/api/routers/preview/content_mode.py +264 -0
- app/api/routers/preview/header_row.py +154 -0
- app/api/routers/preview/models.py +81 -0
- app/api/routers/preview/path_mode.py +440 -0
- app/api/routers/preview/router.py +30 -0
- app/api/routers/project/__init__.py +61 -0
- app/api/routers/project/base.py +111 -0
- app/api/routers/project/constraint.py +345 -0
- app/api/routers/project/full_config.py +411 -0
- app/api/routers/project/full_config_writer.py +327 -0
- app/api/routers/project/helpers.py +167 -0
- app/api/routers/project/inspection_fix.py +330 -0
- app/api/routers/project/manifest.py +605 -0
- app/api/routers/project/models.py +177 -0
- app/api/routers/project/pattern.py +268 -0
- app/api/routers/project/regex.py +358 -0
- app/api/routers/project/scanner.py +157 -0
- app/api/routers/project/schema.py +567 -0
- app/api/routers/project/schema_helpers.py +149 -0
- app/api/routers/project/settings.py +399 -0
- app/api/routers/project/template.py +325 -0
- app/api/routers/project/validation.py +227 -0
- app/api/routers/project/view.py +177 -0
- app/api/routers/project/workspaces.py +167 -0
- app/api/routers/projects/__init__.py +25 -0
- app/api/routers/projects/create.py +121 -0
- app/api/routers/projects/open.py +103 -0
- app/api/routers/projects/scan.py +100 -0
- app/api/routers/validation/__init__.py +39 -0
- app/api/routers/validation/common.py +263 -0
- app/api/routers/validation/content_mode.py +401 -0
- app/api/routers/validation/history.py +235 -0
- app/api/routers/validation/inline_mode.py +196 -0
- app/api/routers/validation/path_mode.py +314 -0
- app/api/routers/validation/router.py +51 -0
- app/api/services/full_validation_response_builder.py +412 -0
- app/api/services/io_error_messages.py +41 -0
- app/api/services/preview_service.py +248 -0
- app/cli/__init__.py +18 -0
- app/cli/__main__.py +30 -0
- app/cli/shared_services/__init__.py +42 -0
- app/cli/shared_services/config_ops.py +581 -0
- app/cli/shared_services/generation_ops.py +160 -0
- app/cli/shared_services/project_ops.py +288 -0
- app/cli/shell/__init__.py +48 -0
- app/cli/shell/commands/__init__.py +63 -0
- app/cli/shell/commands/ai/__init__.py +260 -0
- app/cli/shell/commands/ai/chat.py +331 -0
- app/cli/shell/commands/ai/delete.py +165 -0
- app/cli/shell/commands/ai/diff.py +70 -0
- app/cli/shell/commands/ai/display.py +428 -0
- app/cli/shell/commands/ai/executor.py +225 -0
- app/cli/shell/commands/ai/executor_utils.py +271 -0
- app/cli/shell/commands/ai/generate.py +285 -0
- app/cli/shell/commands/ai/interaction.py +262 -0
- app/cli/shell/commands/ai/migrate.py +281 -0
- app/cli/shell/commands/ai/resolver.py +224 -0
- app/cli/shell/commands/ai/status.py +101 -0
- app/cli/shell/commands/ai/switch.py +158 -0
- app/cli/shell/commands/ai/utils.py +52 -0
- app/cli/shell/commands/base.py +343 -0
- app/cli/shell/commands/config/__init__.py +131 -0
- app/cli/shell/commands/config/base.py +80 -0
- app/cli/shell/commands/config/check.py +246 -0
- app/cli/shell/commands/config/edit.py +148 -0
- app/cli/shell/commands/config/get.py +111 -0
- app/cli/shell/commands/config/init.py +123 -0
- app/cli/shell/commands/config/inspect.py +164 -0
- app/cli/shell/commands/config/list.py +104 -0
- app/cli/shell/commands/config/set.py +119 -0
- app/cli/shell/commands/config/show.py +150 -0
- app/cli/shell/commands/exit.py +67 -0
- app/cli/shell/commands/help.py +104 -0
- app/cli/shell/commands/infer_schema.py +156 -0
- app/cli/shell/commands/open.py +201 -0
- app/cli/shell/commands/project.py +215 -0
- app/cli/shell/commands/provider.py +619 -0
- app/cli/shell/commands/system.py +170 -0
- app/cli/shell/commands/validate.py +455 -0
- app/cli/shell/completer.py +231 -0
- app/cli/shell/config_storage.py +191 -0
- app/cli/shell/exceptions.py +90 -0
- app/cli/shell/formatter.py +371 -0
- app/cli/shell/interactive_menu.py +348 -0
- app/cli/shell/main.py +286 -0
- app/cli/shell/parser.py +266 -0
- app/cli/start.py +180 -0
- app/cli_main.py +52 -0
- app/mcp_server.py +311 -0
- app/shared/core/__init__.py +18 -0
- app/shared/core/app_version.py +59 -0
- app/shared/core/config/__init__.py +119 -0
- app/shared/core/config/server.py +193 -0
- app/shared/core/data_source/__init__.py +106 -0
- app/shared/core/data_source/loader.py +348 -0
- app/shared/core/data_source/loaders/__init__.py +213 -0
- app/shared/core/data_source/loaders/base.py +215 -0
- app/shared/core/data_source/loaders/converter.py +337 -0
- app/shared/core/data_source/loaders/csv_loader.py +252 -0
- app/shared/core/data_source/loaders/excel_loader.py +666 -0
- app/shared/core/data_source/loaders/extractor.py +268 -0
- app/shared/core/data_source/loaders/json_loader.py +354 -0
- app/shared/core/data_source/loaders/registry.py +88 -0
- app/shared/core/data_source/loaders/sql_loader.py +313 -0
- app/shared/core/data_source/loaders/strategies/__init__.py +100 -0
- app/shared/core/data_source/loaders/strategies/array_parser.py +277 -0
- app/shared/core/data_source/loaders/strategies/lines_parser.py +355 -0
- app/shared/core/data_source/loaders/strategies/object_parser.py +278 -0
- app/shared/core/data_source/schema_info.py +50 -0
- app/shared/core/data_source/specs/__init__.py +128 -0
- app/shared/core/data_source/specs/base.py +258 -0
- app/shared/core/data_source/specs/csv_source.py +137 -0
- app/shared/core/data_source/specs/excel_source.py +141 -0
- app/shared/core/data_source/specs/file_base.py +252 -0
- app/shared/core/data_source/specs/json_source.py +226 -0
- app/shared/core/data_source/specs/sql_source.py +161 -0
- app/shared/core/encoding.py +36 -0
- app/shared/core/io/__init__.py +24 -0
- app/shared/core/io/yaml.py +379 -0
- app/shared/core/manifest_schema/__init__.py +52 -0
- app/shared/core/manifest_schema/types.py +99 -0
- app/shared/core/manifest_schema/version.py +175 -0
- app/shared/core/patterns/__init__.py +24 -0
- app/shared/core/patterns/loader.py +134 -0
- app/shared/core/patterns/writer.py +244 -0
- app/shared/core/project/__init__.py +47 -0
- app/shared/core/project/constraint/builders/__init__.py +50 -0
- app/shared/core/project/constraint/builders/base.py +100 -0
- app/shared/core/project/constraint/builders/composite.py +77 -0
- app/shared/core/project/constraint/builders/conditional.py +67 -0
- app/shared/core/project/constraint/builders/foreign_key.py +53 -0
- app/shared/core/project/constraint/builders/registry.py +64 -0
- app/shared/core/project/constraint/builders/scripted.py +51 -0
- app/shared/core/project/constraint/builders/single_column.py +86 -0
- app/shared/core/project/constraint/builders/unique.py +53 -0
- app/shared/core/project/constraint/factory.py +170 -0
- app/shared/core/project/constraint/reader.py +214 -0
- app/shared/core/project/constraint/registry.py +233 -0
- app/shared/core/project/constraint/types/__init__.py +63 -0
- app/shared/core/project/constraint/types/constraint_file.py +261 -0
- app/shared/core/project/constraint/types/refs.py +460 -0
- app/shared/core/project/constraint/types.py +28 -0
- app/shared/core/project/constraint/writer.py +181 -0
- app/shared/core/project/loader/__init__.py +56 -0
- app/shared/core/project/loader/loader.py +30 -0
- app/shared/core/project/loader/loader_parts/config_inspector.py +137 -0
- app/shared/core/project/loader/loader_parts/embedded_constraints.py +224 -0
- app/shared/core/project/loader/loader_parts/file_loaders.py +58 -0
- app/shared/core/project/loader/loader_parts/inspection_ids.py +85 -0
- app/shared/core/project/loader/loader_parts/inspector_helpers.py +312 -0
- app/shared/core/project/loader/loader_parts/inspector_id_checks.py +274 -0
- app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +690 -0
- app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +286 -0
- app/shared/core/project/loader/loader_parts/loading_error_messages.py +298 -0
- app/shared/core/project/loader/loader_parts/main.py +432 -0
- app/shared/core/project/loader/loader_parts/path_validation.py +117 -0
- app/shared/core/project/loader/loader_parts/runtime.py +125 -0
- app/shared/core/project/loader/types.py +246 -0
- app/shared/core/project/manifest/coverage.py +391 -0
- app/shared/core/project/manifest/reader.py +260 -0
- app/shared/core/project/manifest/types.py +91 -0
- app/shared/core/project/manifest/types_parts/__init__.py +60 -0
- app/shared/core/project/manifest/types_parts/constants.py +40 -0
- app/shared/core/project/manifest/types_parts/data_source.py +68 -0
- app/shared/core/project/manifest/types_parts/info.py +59 -0
- app/shared/core/project/manifest/types_parts/manifest.py +294 -0
- app/shared/core/project/manifest/types_parts/refs.py +153 -0
- app/shared/core/project/manifest/types_parts/settings.py +92 -0
- app/shared/core/project/manifest/types_parts/settings_file_processing.py +64 -0
- app/shared/core/project/manifest/types_parts/settings_script_security.py +78 -0
- app/shared/core/project/manifest/types_parts/settings_validation.py +83 -0
- app/shared/core/project/manifest/types_parts/template.py +66 -0
- app/shared/core/project/manifest/writer.py +262 -0
- app/shared/core/project/manual_data/__init__.py +27 -0
- app/shared/core/project/manual_data/types.py +75 -0
- app/shared/core/project/regex/reader.py +197 -0
- app/shared/core/project/regex/types.py +405 -0
- app/shared/core/project/regex/writer.py +123 -0
- app/shared/core/project/schema/reader.py +170 -0
- app/shared/core/project/schema/types.py +47 -0
- app/shared/core/project/schema/types_parts/__init__.py +36 -0
- app/shared/core/project/schema/types_parts/column.py +174 -0
- app/shared/core/project/schema/types_parts/column_utils.py +72 -0
- app/shared/core/project/schema/types_parts/constraint.py +165 -0
- app/shared/core/project/schema/types_parts/schema_id.py +66 -0
- app/shared/core/project/schema/types_parts/source.py +255 -0
- app/shared/core/project/schema/types_parts/source_options.py +347 -0
- app/shared/core/project/schema/types_parts/table.py +230 -0
- app/shared/core/project/schema/writer.py +139 -0
- app/shared/core/project/schema_ref_check.py +95 -0
- app/shared/core/project/template/__init__.py +27 -0
- app/shared/core/project/template/expander.py +263 -0
- app/shared/core/project/template/reader.py +120 -0
- app/shared/core/project/template/types.py +114 -0
- app/shared/core/project/transform/reader.py +76 -0
- app/shared/core/project/transform/types.py +116 -0
- app/shared/core/project/transform/writer.py +84 -0
- app/shared/core/pydantic_messages.py +59 -0
- app/shared/core/reporter/__init__.py +46 -0
- app/shared/core/reporter/reporter.py +220 -0
- app/shared/core/reporter/reporters/__init__.py +65 -0
- app/shared/core/reporter/reporters/base.py +188 -0
- app/shared/core/reporter/reporters/dingtalk_app_reporter.py +274 -0
- app/shared/core/reporter/reporters/email_reporter.py +271 -0
- app/shared/core/reporter/reporters/feishu_app_reporter.py +467 -0
- app/shared/core/reporter/reporters/local_file_reporter.py +208 -0
- app/shared/core/reporter/reporters/wecom_app_reporter.py +268 -0
- app/shared/core/utils/__init__.py +18 -0
- app/shared/core/utils/path_utils.py +60 -0
- app/shared/core/utils/regex_extract.py +113 -0
- app/shared/domain/__init__.py +114 -0
- app/shared/domain/constraints/__init__.py +76 -0
- app/shared/domain/constraints/allowed_values.py +211 -0
- app/shared/domain/constraints/base.py +141 -0
- app/shared/domain/constraints/charset.py +337 -0
- app/shared/domain/constraints/composite.py +174 -0
- app/shared/domain/constraints/condition_registry.py +130 -0
- app/shared/domain/constraints/conditional.py +629 -0
- app/shared/domain/constraints/date_logic.py +731 -0
- app/shared/domain/constraints/foreign_key.py +261 -0
- app/shared/domain/constraints/key_normalization.py +56 -0
- app/shared/domain/constraints/not_null.py +185 -0
- app/shared/domain/constraints/range.py +360 -0
- app/shared/domain/constraints/regex.py +218 -0
- app/shared/domain/constraints/scripted.py +276 -0
- app/shared/domain/constraints/unique.py +233 -0
- app/shared/domain/data_engine.py +384 -0
- app/shared/domain/data_types.py +113 -0
- app/shared/domain/data_types_parts/__init__.py +67 -0
- app/shared/domain/data_types_parts/base.py +204 -0
- app/shared/domain/data_types_parts/composite.py +250 -0
- app/shared/domain/data_types_parts/expression.py +202 -0
- app/shared/domain/data_types_parts/extracted.py +106 -0
- app/shared/domain/data_types_parts/json_types.py +234 -0
- app/shared/domain/data_types_parts/scalars.py +719 -0
- app/shared/domain/data_types_parts/sequence.py +129 -0
- app/shared/domain/dataset_schema.py +48 -0
- app/shared/domain/eval_sandbox.py +63 -0
- app/shared/domain/expression_system.py +366 -0
- app/shared/domain/regex_flags.py +53 -0
- app/shared/domain/schema/builder.py +223 -0
- app/shared/domain/schema/models.py +339 -0
- app/shared/domain/transforms/__init__.py +28 -0
- app/shared/domain/transforms/aggregate.py +141 -0
- app/shared/domain/transforms/base.py +171 -0
- app/shared/domain/transforms/cast_type.py +120 -0
- app/shared/domain/transforms/concat.py +103 -0
- app/shared/domain/transforms/conditional_assign.py +114 -0
- app/shared/domain/transforms/date_format.py +81 -0
- app/shared/domain/transforms/digits.py +77 -0
- app/shared/domain/transforms/drop_duplicates.py +94 -0
- app/shared/domain/transforms/fill_na.py +94 -0
- app/shared/domain/transforms/filter_rows.py +97 -0
- app/shared/domain/transforms/lookup.py +78 -0
- app/shared/domain/transforms/lower_case.py +70 -0
- app/shared/domain/transforms/map_value.py +90 -0
- app/shared/domain/transforms/math_expr.py +112 -0
- app/shared/domain/transforms/modulo.py +82 -0
- app/shared/domain/transforms/regex_extract.py +103 -0
- app/shared/domain/transforms/registry.py +95 -0
- app/shared/domain/transforms/replace.py +88 -0
- app/shared/domain/transforms/sort_rows.py +94 -0
- app/shared/domain/transforms/string_split.py +84 -0
- app/shared/domain/transforms/strip.py +73 -0
- app/shared/domain/transforms/substring.py +92 -0
- app/shared/domain/transforms/upper_case.py +70 -0
- app/shared/domain/transforms/weighted_sum.py +104 -0
- app/shared/domain/validation_constraints.py +69 -0
- app/shared/services/__init__.py +62 -0
- app/shared/services/ai/__init__.py +48 -0
- app/shared/services/ai/agent/__init__.py +40 -0
- app/shared/services/ai/agent/chat_tools/__init__.py +47 -0
- app/shared/services/ai/agent/chat_tools/apply_actions.py +592 -0
- app/shared/services/ai/agent/chat_tools/ask_user.py +237 -0
- app/shared/services/ai/agent/chat_tools/read_canvas.py +140 -0
- app/shared/services/ai/agent/chat_tools/read_project.py +160 -0
- app/shared/services/ai/agent/chat_tools/read_table.py +301 -0
- app/shared/services/ai/agent/chat_tools/schemas.py +105 -0
- app/shared/services/ai/agent/chat_tools/validate_table.py +131 -0
- app/shared/services/ai/agent/executor.py +554 -0
- app/shared/services/ai/agent/memory.py +256 -0
- app/shared/services/ai/agent/planner.py +282 -0
- app/shared/services/ai/agent/tool_registry.py +296 -0
- app/shared/services/ai/agent/tools/__init__.py +32 -0
- app/shared/services/ai/agent/tools/config_generate.py +133 -0
- app/shared/services/ai/agent/tools/config_refine.py +109 -0
- app/shared/services/ai/agent/tools/config_validate.py +403 -0
- app/shared/services/ai/agent/tools/merge_results.py +136 -0
- app/shared/services/ai/agent/tools/plan_chunks.py +79 -0
- app/shared/services/ai/agent/tools/script_parse.py +359 -0
- app/shared/services/ai/agent/types.py +138 -0
- app/shared/services/ai/chat_agent_runner.py +596 -0
- app/shared/services/ai/chat_orchestrator.py +725 -0
- app/shared/services/ai/failure_messages.py +65 -0
- app/shared/services/ai/job_storage.py +274 -0
- app/shared/services/ai/migrate_service.py +550 -0
- app/shared/services/ai/streaming/__init__.py +65 -0
- app/shared/services/ai/streaming/event_journal.py +197 -0
- app/shared/services/ai/streaming/orchestrator.py +262 -0
- app/shared/services/ai/streaming/pending_interaction_store.py +227 -0
- app/shared/services/ai/streaming/sse_response.py +148 -0
- app/shared/services/ai/streaming/types.py +73 -0
- app/shared/services/ai/types.py +213 -0
- app/shared/services/ai/utils.py +322 -0
- app/shared/services/diff/config_diff.py +320 -0
- app/shared/services/hardware.py +237 -0
- app/shared/services/llm/__init__.py +62 -0
- app/shared/services/llm/actions/__init__.py +42 -0
- app/shared/services/llm/actions/_canvas_validator.py +147 -0
- app/shared/services/llm/actions/_constraint_validator.py +401 -0
- app/shared/services/llm/actions/_regex_validator.py +85 -0
- app/shared/services/llm/actions/_schema_validator.py +82 -0
- app/shared/services/llm/actions/_settings_validator.py +76 -0
- app/shared/services/llm/actions/_transform_validator.py +78 -0
- app/shared/services/llm/actions/action_handlers.py +400 -0
- app/shared/services/llm/actions/action_parser.py +63 -0
- app/shared/services/llm/actions/action_processor.py +468 -0
- app/shared/services/llm/actions/action_validator.py +311 -0
- app/shared/services/llm/actions/diff_compute.py +195 -0
- app/shared/services/llm/actions/regex_handlers.py +284 -0
- app/shared/services/llm/actions/registry.py +336 -0
- app/shared/services/llm/actions/schema_handlers.py +335 -0
- app/shared/services/llm/actions/settings_handlers.py +180 -0
- app/shared/services/llm/actions/specs.py +259 -0
- app/shared/services/llm/actions/transform_handlers.py +279 -0
- app/shared/services/llm/actions/validation_types.py +147 -0
- app/shared/services/llm/cache/__init__.py +18 -0
- app/shared/services/llm/cache/response_cache.py +80 -0
- app/shared/services/llm/chat/__init__.py +35 -0
- app/shared/services/llm/chat/chat_system_prompt.py +561 -0
- app/shared/services/llm/chat/response_parser.py +296 -0
- app/shared/services/llm/config/__init__.py +45 -0
- app/shared/services/llm/config/crypto.py +135 -0
- app/shared/services/llm/config/loader.py +258 -0
- app/shared/services/llm/config/models.py +202 -0
- app/shared/services/llm/config/presets.py +128 -0
- app/shared/services/llm/config_generator.py +163 -0
- app/shared/services/llm/constraints/__init__.py +41 -0
- app/shared/services/llm/constraints/constraint_builder.py +177 -0
- app/shared/services/llm/constraints/constraint_deletion.py +84 -0
- app/shared/services/llm/constraints/constraint_id.py +174 -0
- app/shared/services/llm/constraints/frontend_instructions.py +333 -0
- app/shared/services/llm/constraints/inline_batch.py +279 -0
- app/shared/services/llm/discovery/__init__.py +35 -0
- app/shared/services/llm/discovery/scanner.py +181 -0
- app/shared/services/llm/generation/__init__.py +42 -0
- app/shared/services/llm/generation/agent_wiring.py +156 -0
- app/shared/services/llm/generation/config_builder.py +386 -0
- app/shared/services/llm/generation/errors.py +36 -0
- app/shared/services/llm/generation/existing_config.py +99 -0
- app/shared/services/llm/generation/profiler.py +279 -0
- app/shared/services/llm/generation/prompt_builder.py +199 -0
- app/shared/services/llm/generation/response_parser.py +144 -0
- app/shared/services/llm/generation/service.py +622 -0
- app/shared/services/llm/models.py +104 -0
- app/shared/services/llm/providers/__init__.py +48 -0
- app/shared/services/llm/providers/base.py +310 -0
- app/shared/services/llm/providers/cached_provider.py +90 -0
- app/shared/services/llm/providers/ollama.py +498 -0
- app/shared/services/llm/providers/openai.py +334 -0
- app/shared/services/llm/providers/registry.py +108 -0
- app/shared/services/llm/schema_resolver.py +141 -0
- app/shared/services/llm/suggestion_utils.py +219 -0
- app/shared/services/llm/validate_executor.py +163 -0
- app/shared/services/llm/yaml_io.py +406 -0
- app/shared/services/preview/__init__.py +18 -0
- app/shared/services/preview/loader.py +145 -0
- app/shared/services/preview/path_validation.py +138 -0
- app/shared/services/project_loader.py +57 -0
- app/shared/services/schema_inference.py +260 -0
- app/shared/services/schema_runtime_builder.py +120 -0
- app/shared/services/validation/__init__.py +52 -0
- app/shared/services/validation/chunked_loader.py +509 -0
- app/shared/services/validation/dag/__init__.py +35 -0
- app/shared/services/validation/dag/builder.py +141 -0
- app/shared/services/validation/dag/executor.py +225 -0
- app/shared/services/validation/dag/sorter.py +77 -0
- app/shared/services/validation/data_loader.py +231 -0
- app/shared/services/validation/engine.py +446 -0
- app/shared/services/validation/executor.py +1030 -0
- app/shared/services/validation/extractors.py +266 -0
- app/shared/services/validation/history.py +209 -0
- app/shared/services/validation/json_payload.py +136 -0
- app/shared/services/validation/loader.py +152 -0
- app/shared/services/validation/memory_monitor.py +179 -0
- app/shared/services/validation/postprocess.py +208 -0
- app/shared/services/validation/progress.py +64 -0
- app/shared/services/validation/report_export.py +222 -0
- app/shared/services/validation/resolver.py +180 -0
- app/shared/services/validation/service.py +418 -0
- app/shared/services/validation/types.py +238 -0
- app/shared/services/validation/validators/__init__.py +36 -0
- app/shared/services/validation/validators/adapter.py +182 -0
- app/shared/services/validation/validators/base.py +332 -0
- app/shared/services/validation/validators/composite.py +233 -0
- app/shared/services/validation/validators/date_logic.py +276 -0
- app/start_server.py +133 -0
- precis_cli-0.1.3.dist-info/METADATA +180 -0
- precis_cli-0.1.3.dist-info/RECORD +442 -0
- precis_cli-0.1.3.dist-info/WHEEL +5 -0
- precis_cli-0.1.3.dist-info/entry_points.txt +4 -0
- precis_cli-0.1.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# backend/app/cli/shell/commands/ai/delete.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview AI Provider 删除命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 ai delete 子命令删除已配置的 AI Provider API Key
|
|
22
|
+
- 支持交互式菜单选择与直接指定 Provider ID 删除
|
|
23
|
+
- 删除前要求用户确认,防止误操作
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- AIDeleteCommand 继承 Command 基类
|
|
27
|
+
- execute() 根据是否有参数决定交互式删除或直接删除
|
|
28
|
+
- 通过 CLI 配置管理器删除 Provider 配置
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
ai delete
|
|
32
|
+
ai delete openai
|
|
33
|
+
|
|
34
|
+
输出示例:
|
|
35
|
+
CommandResult.ok("API Key 已删除")
|
|
36
|
+
CommandResult.error("没有已配置的 API Key")
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
40
|
+
from app.cli.shell.config_storage import get_cli_config
|
|
41
|
+
from app.cli.shell.formatter import Colors, Formatter
|
|
42
|
+
from app.cli.shell.interactive_menu import InteractiveMenu
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AIDeleteCommand(Command):
|
|
46
|
+
"""删除已配置的 AI Provider API Key 命令。
|
|
47
|
+
|
|
48
|
+
支持交互式选择或直接指定 Provider ID 删除。
|
|
49
|
+
删除前会要求用户确认。
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self) -> None:
|
|
53
|
+
super().__init__("delete")
|
|
54
|
+
self._cli_config = get_cli_config()
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def description(self) -> str:
|
|
58
|
+
return "删除 AI Provider"
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def usage(self) -> str:
|
|
62
|
+
return "ai delete [provider_id]"
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def help_text(self) -> str:
|
|
66
|
+
return """
|
|
67
|
+
用法: ai delete [provider_id]
|
|
68
|
+
|
|
69
|
+
示例:
|
|
70
|
+
ai delete # 交互式选择要删除的 Provider
|
|
71
|
+
ai delete openai # 直接删除 OpenAI
|
|
72
|
+
|
|
73
|
+
说明:
|
|
74
|
+
删除指定的 Provider 配置。
|
|
75
|
+
使用 'ai status' 查看已配置的 Provider。
|
|
76
|
+
""".strip()
|
|
77
|
+
|
|
78
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
79
|
+
"""执行删除命令。
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
args: 命令参数列表,可能包含 Provider ID
|
|
83
|
+
context: 项目上下文
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
删除成功或失败的结果
|
|
87
|
+
"""
|
|
88
|
+
providers = self._cli_config.list_providers()
|
|
89
|
+
|
|
90
|
+
if not providers:
|
|
91
|
+
return CommandResult.error("没有已配置的 Provider\n请使用 'ai setup' 进行配置")
|
|
92
|
+
|
|
93
|
+
if not args:
|
|
94
|
+
return self._interactive_delete(providers)
|
|
95
|
+
|
|
96
|
+
# 4.4: 大小写不敏感匹配——原实现把参数强转小写后查存储原名(含大写的
|
|
97
|
+
# provider 恒查不到),交互菜单走原值正常,参数路径自相矛盾
|
|
98
|
+
raw_id = args[0]
|
|
99
|
+
provider_id = next(
|
|
100
|
+
(p.id for p in providers if p.id.lower() == raw_id.lower()),
|
|
101
|
+
raw_id,
|
|
102
|
+
)
|
|
103
|
+
return self._do_delete(provider_id, providers)
|
|
104
|
+
|
|
105
|
+
def _interactive_delete(self, providers: list) -> CommandResult:
|
|
106
|
+
"""交互式删除 Provider(支持方向键导航)。
|
|
107
|
+
|
|
108
|
+
显示菜单让用户选择要删除的 Provider。
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
providers: 已配置的 Provider 列表
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
删除结果或取消结果
|
|
115
|
+
"""
|
|
116
|
+
print(Formatter.header("\n删除 Provider"))
|
|
117
|
+
|
|
118
|
+
menu = InteractiveMenu("请选择要删除的 Provider:")
|
|
119
|
+
|
|
120
|
+
for p in providers:
|
|
121
|
+
label = f"{p.name} ({p.model})"
|
|
122
|
+
menu.add_item(p.id, label, "")
|
|
123
|
+
|
|
124
|
+
menu.add_item("_cancel_", "取消", "返回")
|
|
125
|
+
|
|
126
|
+
provider_id = menu.show()
|
|
127
|
+
|
|
128
|
+
if provider_id is None or provider_id == "_cancel_":
|
|
129
|
+
return CommandResult.ok("已取消")
|
|
130
|
+
|
|
131
|
+
return self._do_delete(provider_id, providers)
|
|
132
|
+
|
|
133
|
+
def _do_delete(self, provider_id: str, providers: list) -> CommandResult:
|
|
134
|
+
"""执行删除操作。
|
|
135
|
+
|
|
136
|
+
验证 Provider 存在,然后要求用户确认后删除。
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
provider_id: 要删除的 Provider ID
|
|
140
|
+
providers: 已配置的 Provider 列表
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
删除成功或失败的结果
|
|
144
|
+
"""
|
|
145
|
+
provider = self._cli_config.get_provider(provider_id)
|
|
146
|
+
if not provider:
|
|
147
|
+
return CommandResult.error(f"Provider '{provider_id}' 未配置\n使用 'ai status' 查看已配置的 Provider")
|
|
148
|
+
|
|
149
|
+
print(Formatter.warning(f"\n警告: 将删除 {provider.name} ({provider.id})"))
|
|
150
|
+
print(Formatter.info(f" 模型: {provider.model}\n"))
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
confirm = input(Formatter.colorize("确认删除? (y/N): ", Colors.YELLOW)).strip().lower()
|
|
154
|
+
except (KeyboardInterrupt, EOFError):
|
|
155
|
+
print()
|
|
156
|
+
return CommandResult.ok("已取消")
|
|
157
|
+
|
|
158
|
+
if confirm not in ("y", "yes"):
|
|
159
|
+
return CommandResult.ok("已取消")
|
|
160
|
+
|
|
161
|
+
if self._cli_config.delete_provider(provider_id):
|
|
162
|
+
print(Formatter.success(f"\n[*] {provider.name} 已删除"))
|
|
163
|
+
return CommandResult.ok("Provider 已删除")
|
|
164
|
+
else:
|
|
165
|
+
return CommandResult.error("删除失败")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# backend/app/cli/shell/commands/ai/diff.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview AI 配置变更 Diff 生成模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 使用 Python 标准库 difflib 生成统一差异格式(Unified Diff)
|
|
22
|
+
- 对比文件修改前后的内容,输出类 Git diff 格式
|
|
23
|
+
- 处理行尾换行符,确保 diff 格式标准
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- 独立工具模块,无外部依赖(仅使用标准库 difflib)
|
|
27
|
+
- 被 display.py 调用以展示详细的文件变更对比
|
|
28
|
+
- 统一差异格式(unified diff)是 Git 等版本控制工具使用的标准格式
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
_generate_diff("constraints.yaml", old_content, new_content)
|
|
32
|
+
|
|
33
|
+
输出示例:
|
|
34
|
+
"--- a/constraints.yaml\n+++ b/constraints.yaml\n@@ -1,3 +1,4 @@..."
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import difflib
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _generate_diff(file_path: str, old_content: str, new_content: str) -> str:
|
|
44
|
+
"""生成文件的 diff 显示。
|
|
45
|
+
|
|
46
|
+
使用 Python 标准库 difflib.unified_diff 生成统一差异格式(Unified Diff),
|
|
47
|
+
这是 Git 等版本控制工具使用的标准格式。
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
file_path: 文件路径(用于显示文件名)
|
|
51
|
+
old_content: 修改前的文件内容
|
|
52
|
+
new_content: 修改后的文件内容
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
统一差异格式的字符串
|
|
56
|
+
"""
|
|
57
|
+
from_line = old_content.splitlines(keepends=True) if old_content else []
|
|
58
|
+
to_line = new_content.splitlines(keepends=True) if new_content else []
|
|
59
|
+
|
|
60
|
+
# 确保最后一行以换行符结尾,否则 diff 格式不标准
|
|
61
|
+
if from_line and not from_line[-1].endswith("\n"):
|
|
62
|
+
from_line[-1] += "\n"
|
|
63
|
+
if to_line and not to_line[-1].endswith("\n"):
|
|
64
|
+
to_line[-1] += "\n"
|
|
65
|
+
|
|
66
|
+
diff = difflib.unified_diff(
|
|
67
|
+
from_line, to_line, fromfile=f"a/{Path(file_path).name}", tofile=f"b/{Path(file_path).name}", lineterm="\n"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return "".join(diff)
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# backend/app/cli/shell/commands/ai/display.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview AI 执行结果展示模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 显示 AI 修改文件的 diff 摘要与详细对比
|
|
22
|
+
- 区分新增、删除、修改三种文件状态并用颜色高亮
|
|
23
|
+
- 展示约束操作结果与数据校验结果(通过/失败)
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- _show_diff_summary(): 显示文件变更摘要,询问是否查看详细 diff
|
|
27
|
+
- _display_detailed_diff(): 使用颜色高亮显示详细 diff 内容
|
|
28
|
+
- _display_execution_results(): 主入口,判断是否有文件修改并决定展示方式
|
|
29
|
+
- _display_results_without_diff(): 无文件修改时的结果展示(如仅执行校验)
|
|
30
|
+
- _display_constraint_results(): 分类显示校验结果与约束操作结果
|
|
31
|
+
|
|
32
|
+
输入示例:
|
|
33
|
+
_display_execution_results(result, "/path/to/project", original_files_cache)
|
|
34
|
+
|
|
35
|
+
输出示例:
|
|
36
|
+
带颜色高亮的 diff 与操作结果文本
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import logging
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
|
|
44
|
+
from app.cli.shell.formatter import Colors, Formatter
|
|
45
|
+
from app.shared.services.ai.chat_orchestrator import ChatExecutionResult
|
|
46
|
+
from app.shared.services.llm.actions.validation_types import action_display_name
|
|
47
|
+
|
|
48
|
+
from .diff import _generate_diff
|
|
49
|
+
|
|
50
|
+
logger = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# 工具名到中文标签的映射(与 ChatAgentRunner._TOOL_LABELS 保持一致)
|
|
54
|
+
_TOOL_LABELS = {
|
|
55
|
+
"read_project": "读取项目",
|
|
56
|
+
"read_table": "查看数据",
|
|
57
|
+
"apply_actions": "修改配置",
|
|
58
|
+
"validate_table": "校验数据",
|
|
59
|
+
"read_canvas": "读取画布",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _display_tool_trail(tool_steps: list[dict]) -> None:
|
|
64
|
+
"""显示 Agent 工具调用轨迹。
|
|
65
|
+
|
|
66
|
+
在 CLI 交互模式下,当 AI 使用 Agent 工具循环完成查-改-验后,
|
|
67
|
+
输出简要的工具步骤摘要,让用户感知 AI 的思考与操作路径。
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
tool_steps: ChatAgentRunResult.tool_steps 列表,每个元素含 tool/label/turn/action_count/status
|
|
71
|
+
"""
|
|
72
|
+
if not tool_steps:
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
print(Formatter.header("\n🔧 Agent 工具轨迹"))
|
|
76
|
+
for step in tool_steps:
|
|
77
|
+
label = step.get("label") or _TOOL_LABELS.get(step.get("tool", ""), step.get("tool", "未知"))
|
|
78
|
+
action_count = step.get("action_count")
|
|
79
|
+
status = step.get("status", "success")
|
|
80
|
+
error = step.get("error")
|
|
81
|
+
|
|
82
|
+
count_str = f"({action_count})" if action_count else ""
|
|
83
|
+
status_marker = "✓" if status == "success" else "✗" if status == "failed" else "•"
|
|
84
|
+
line = f" {status_marker} {label}{count_str}"
|
|
85
|
+
if error:
|
|
86
|
+
line += f" — {error}"
|
|
87
|
+
print(Formatter.info(line))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _show_diff_summary(changed_files: dict[str, tuple[str, str]]) -> bool:
|
|
91
|
+
"""显示修改文件的 diff 摘要,并询问用户是否查看详细 diff。
|
|
92
|
+
|
|
93
|
+
对每份修改的文件显示状态:新增(绿色)、删除(红色)、修改(黄色)。
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
changed_files: 字典,键为文件路径,值为 (旧内容, 新内容) 元组
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
用户是否选择查看详细 diff
|
|
100
|
+
"""
|
|
101
|
+
if not changed_files:
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
print(Formatter.header("\n📋 修改文件摘要"))
|
|
105
|
+
print("=" * 50)
|
|
106
|
+
|
|
107
|
+
for file_path, (old_content, new_content) in changed_files.items():
|
|
108
|
+
rel_path = Path(file_path).name
|
|
109
|
+
if old_content == "":
|
|
110
|
+
status = "新增"
|
|
111
|
+
color = Colors.GREEN
|
|
112
|
+
elif new_content == "":
|
|
113
|
+
status = "删除"
|
|
114
|
+
color = Colors.RED
|
|
115
|
+
else:
|
|
116
|
+
old_lines = len(old_content.splitlines())
|
|
117
|
+
new_lines = len(new_content.splitlines())
|
|
118
|
+
if old_lines == new_lines:
|
|
119
|
+
status = "修改"
|
|
120
|
+
else:
|
|
121
|
+
status = f"修改 ({old_lines} → {new_lines} 行)"
|
|
122
|
+
color = Colors.YELLOW
|
|
123
|
+
|
|
124
|
+
print(f" {Formatter.colorize(status, color)}: {rel_path}")
|
|
125
|
+
|
|
126
|
+
print("=" * 50)
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
response = input(Formatter.colorize("\n查看详细 diff? (y/N): ", Colors.YELLOW)).strip().lower()
|
|
130
|
+
return response in ("y", "yes")
|
|
131
|
+
except (KeyboardInterrupt, EOFError):
|
|
132
|
+
print()
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _display_detailed_diff(changed_files: dict[str, tuple[str, str]]) -> None:
|
|
137
|
+
"""显示详细的 diff 内容。
|
|
138
|
+
|
|
139
|
+
对 diff 的每一行进行颜色高亮:
|
|
140
|
+
- 绿色:新增的行(以 + 开头)
|
|
141
|
+
- 红色:删除的行(以 - 开头)
|
|
142
|
+
- 青色:行号信息(以 @@ 开头)
|
|
143
|
+
- 灰色:文件头信息(以 --- 或 +++ 开头)
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
changed_files: 字典,键为文件路径,值为 (旧内容, 新内容) 元组
|
|
147
|
+
"""
|
|
148
|
+
print(Formatter.header("\n📄 详细 Diff"))
|
|
149
|
+
print("=" * 70)
|
|
150
|
+
|
|
151
|
+
has_diff = False
|
|
152
|
+
for file_path, (old_content, new_content) in changed_files.items():
|
|
153
|
+
diff_text = _generate_diff(file_path, old_content, new_content)
|
|
154
|
+
if diff_text:
|
|
155
|
+
has_diff = True
|
|
156
|
+
rel_path = Path(file_path).name
|
|
157
|
+
print(f"\n{Formatter.colorize('▶', Colors.CYAN)} {rel_path}")
|
|
158
|
+
print("-" * 50)
|
|
159
|
+
|
|
160
|
+
for line in diff_text.splitlines():
|
|
161
|
+
if line.startswith("+"):
|
|
162
|
+
print(Formatter.colorize(line, Colors.GREEN))
|
|
163
|
+
elif line.startswith("-"):
|
|
164
|
+
print(Formatter.colorize(line, Colors.RED))
|
|
165
|
+
elif line.startswith("@@"):
|
|
166
|
+
print(Formatter.colorize(line, Colors.CYAN))
|
|
167
|
+
elif line.startswith("---") or line.startswith("+++"):
|
|
168
|
+
print(Formatter.dim(line))
|
|
169
|
+
else:
|
|
170
|
+
print(line)
|
|
171
|
+
|
|
172
|
+
if not has_diff:
|
|
173
|
+
print(Formatter.dim(" 无内容变更"))
|
|
174
|
+
|
|
175
|
+
print("\n" + "=" * 70)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _display_execution_results(
|
|
179
|
+
result: ChatExecutionResult,
|
|
180
|
+
project_path: str,
|
|
181
|
+
original_files_cache: dict[str, str] | None = None,
|
|
182
|
+
) -> None:
|
|
183
|
+
"""显示执行结果(CLI 交互模式)并处理 diff。
|
|
184
|
+
|
|
185
|
+
判断 AI 返回的动作是否包含约束节点的增删改操作。
|
|
186
|
+
如果有文件修改,则对比原始缓存生成 diff 并展示;
|
|
187
|
+
如果没有文件修改(如仅执行校验),则只显示校验结果。
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
result: AIChatOrchestrator 的执行结果对象
|
|
191
|
+
project_path: 项目路径
|
|
192
|
+
original_files_cache: 原始文件内容缓存,用于 diff 对比
|
|
193
|
+
"""
|
|
194
|
+
actions = result.actions
|
|
195
|
+
original_files_cache = original_files_cache or {}
|
|
196
|
+
|
|
197
|
+
# 判断是否有会修改文件的操作
|
|
198
|
+
file_modification_types = {
|
|
199
|
+
"ADD_CONSTRAINT_NODE",
|
|
200
|
+
"UPDATE_CONSTRAINT_NODE",
|
|
201
|
+
"DELETE_CONSTRAINT_NODE",
|
|
202
|
+
"ADD_SCHEMA",
|
|
203
|
+
"UPDATE_SCHEMA",
|
|
204
|
+
"DELETE_SCHEMA",
|
|
205
|
+
"ADD_REGEX",
|
|
206
|
+
"UPDATE_REGEX",
|
|
207
|
+
"DELETE_REGEX",
|
|
208
|
+
"ADD_TRANSFORM",
|
|
209
|
+
"UPDATE_TRANSFORM",
|
|
210
|
+
"DELETE_TRANSFORM",
|
|
211
|
+
"UPDATE_SETTINGS",
|
|
212
|
+
}
|
|
213
|
+
has_modifications = any(a.get("actionType") in file_modification_types for a in actions)
|
|
214
|
+
|
|
215
|
+
if not has_modifications:
|
|
216
|
+
_display_results_without_diff(result)
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
# 使用 orchestrator 已经执行的结果,避免重复执行 process_actions
|
|
220
|
+
results = getattr(result, "action_results", [])
|
|
221
|
+
|
|
222
|
+
# 收集修改后的文件内容,与原始缓存对比找出变更
|
|
223
|
+
changed_files = {}
|
|
224
|
+
for file_path, old_content in original_files_cache.items():
|
|
225
|
+
try:
|
|
226
|
+
if Path(file_path).exists():
|
|
227
|
+
new_content = Path(file_path).read_text(encoding="utf-8")
|
|
228
|
+
else:
|
|
229
|
+
new_content = ""
|
|
230
|
+
if old_content != new_content:
|
|
231
|
+
changed_files[file_path] = (old_content, new_content)
|
|
232
|
+
except Exception:
|
|
233
|
+
logger.error("读取文件内容失败", exc_info=True)
|
|
234
|
+
|
|
235
|
+
_display_constraint_results(results)
|
|
236
|
+
|
|
237
|
+
if changed_files and _show_diff_summary(changed_files):
|
|
238
|
+
_display_detailed_diff(changed_files)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _display_results_without_diff(result: ChatExecutionResult) -> None:
|
|
242
|
+
"""显示没有修改操作的结果(仅校验等)。
|
|
243
|
+
|
|
244
|
+
当 AI 的动作不涉及文件修改时(如 VALIDATE_PROJECT),
|
|
245
|
+
直接显示校验结果而不进行 diff 对比。
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
result: AIChatOrchestrator 的执行结果对象
|
|
249
|
+
"""
|
|
250
|
+
results = getattr(result, "action_results", [])
|
|
251
|
+
_display_constraint_results(results)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _format_validate_table_display(table_filter: str | list[str] | None) -> str:
|
|
255
|
+
"""根据校验的 table_filter 推导出用于展示的"表"描述文案。
|
|
256
|
+
|
|
257
|
+
校验结果详情(validate_details)里的 table_filter 才是真实信息源:
|
|
258
|
+
- None/空 → 全量校验
|
|
259
|
+
- 字符串 → 单表校验
|
|
260
|
+
- 列表 → 多表校验
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
table_filter: validate_executor 返回的 details.table_filter 值
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
适合直接拼接进 "✓ 表 '...' 数据校验通过" 的描述文案
|
|
267
|
+
"""
|
|
268
|
+
# 全量校验(None 或空字符串/空列表)
|
|
269
|
+
if not table_filter:
|
|
270
|
+
return "全部表"
|
|
271
|
+
# 单表(字符串)
|
|
272
|
+
if isinstance(table_filter, str):
|
|
273
|
+
return table_filter
|
|
274
|
+
# 列表形式
|
|
275
|
+
tables = [t for t in table_filter if t]
|
|
276
|
+
if not tables:
|
|
277
|
+
return "全部表"
|
|
278
|
+
if len(tables) == 1:
|
|
279
|
+
return tables[0]
|
|
280
|
+
return f"{len(tables)} 张表({', '.join(tables)})"
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _display_constraint_results(results: list[dict]) -> None:
|
|
284
|
+
"""显示操作结果。
|
|
285
|
+
|
|
286
|
+
将动作结果分为四类:
|
|
287
|
+
1. VALIDATE_PROJECT:数据校验结果
|
|
288
|
+
2. 约束操作:约束 CRUD 结果
|
|
289
|
+
3. Schema/Regex/Transform/Settings 操作
|
|
290
|
+
4. 其他操作
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
results: 动作执行结果列表,每个元素是一个字典
|
|
294
|
+
"""
|
|
295
|
+
constraint_types = {"ADD_CONSTRAINT_NODE", "UPDATE_CONSTRAINT_NODE", "DELETE_CONSTRAINT_NODE"}
|
|
296
|
+
schema_types = {"ADD_SCHEMA", "UPDATE_SCHEMA", "DELETE_SCHEMA"}
|
|
297
|
+
regex_types = {"ADD_REGEX", "UPDATE_REGEX", "DELETE_REGEX"}
|
|
298
|
+
transform_types = {"ADD_TRANSFORM", "UPDATE_TRANSFORM", "DELETE_TRANSFORM"}
|
|
299
|
+
|
|
300
|
+
# 分离各类结果
|
|
301
|
+
validate_results = [r for r in results if r.get("action", {}).get("actionType") == "VALIDATE_PROJECT"]
|
|
302
|
+
constraint_results = [r for r in results if r.get("action", {}).get("actionType") in constraint_types]
|
|
303
|
+
schema_results = [r for r in results if r.get("action", {}).get("actionType") in schema_types]
|
|
304
|
+
regex_results = [r for r in results if r.get("action", {}).get("actionType") in regex_types]
|
|
305
|
+
transform_results = [r for r in results if r.get("action", {}).get("actionType") in transform_types]
|
|
306
|
+
settings_results = [r for r in results if r.get("action", {}).get("actionType") == "UPDATE_SETTINGS"]
|
|
307
|
+
|
|
308
|
+
# 显示校验结果
|
|
309
|
+
for r in validate_results:
|
|
310
|
+
msg = r.get("message", "")
|
|
311
|
+
details = r.get("validate_details", {})
|
|
312
|
+
has_errors = details.get("has_errors", False) if details else False
|
|
313
|
+
# 展示文案优先取 action.constraintSpec 里的"原始表名"(用户友好):
|
|
314
|
+
# - 多表:spec.tables(表名列表)
|
|
315
|
+
# - 单表:spec.tableName(表名,resolver 解析后仍保留原名)
|
|
316
|
+
# - 全量:两者都没有 → fallback 到 validate_details.table_filter(None)
|
|
317
|
+
# 注意:validate_details.table_filter 在 resolver 解析后会变成"表 ID"(加密串),
|
|
318
|
+
# 直接展示对用户不友好,故仅作兜底。
|
|
319
|
+
spec = r.get("action", {}).get("constraintSpec", {}) or {}
|
|
320
|
+
raw_tables = spec.get("tables")
|
|
321
|
+
if isinstance(raw_tables, list) and raw_tables:
|
|
322
|
+
table_display = _format_validate_table_display(raw_tables)
|
|
323
|
+
else:
|
|
324
|
+
single_name = spec.get("tableName")
|
|
325
|
+
fallback = single_name or (details.get("table_filter") if details else None)
|
|
326
|
+
table_display = _format_validate_table_display(fallback)
|
|
327
|
+
|
|
328
|
+
if has_errors:
|
|
329
|
+
print(Formatter.warning("\n" + "=" * 50))
|
|
330
|
+
print(Formatter.warning(f"⚠ 表 '{table_display}' 数据校验发现问题"))
|
|
331
|
+
print(Formatter.warning("=" * 50))
|
|
332
|
+
print(msg)
|
|
333
|
+
print("=" * 50)
|
|
334
|
+
else:
|
|
335
|
+
print(Formatter.success("\n" + "=" * 50))
|
|
336
|
+
print(Formatter.success(f"✓ 表 '{table_display}' 数据校验通过"))
|
|
337
|
+
print(Formatter.success(f" 耗时: {details.get('duration_ms', 0)}ms"))
|
|
338
|
+
print(Formatter.success("=" * 50))
|
|
339
|
+
|
|
340
|
+
# 显示约束操作结果
|
|
341
|
+
if constraint_results:
|
|
342
|
+
if all(r.get("success") for r in constraint_results):
|
|
343
|
+
print(Formatter.success("\n" + "=" * 50))
|
|
344
|
+
print(Formatter.success("✓ 约束操作成功"))
|
|
345
|
+
print(Formatter.success("=" * 50))
|
|
346
|
+
|
|
347
|
+
for r in constraint_results:
|
|
348
|
+
msg = r.get("message", "N/A")
|
|
349
|
+
action = r.get("action", {})
|
|
350
|
+
spec = action.get("constraintSpec", {})
|
|
351
|
+
is_inline = spec.get("isInline", False)
|
|
352
|
+
constraint_type = spec.get("type", "N/A")
|
|
353
|
+
table_name = spec.get("tableName", "未知表")
|
|
354
|
+
column_name = spec.get("targetColumn", "N/A")
|
|
355
|
+
params = spec.get("params", {})
|
|
356
|
+
|
|
357
|
+
print("\n 约束配置详情:")
|
|
358
|
+
print(f" - 类型: {constraint_type}")
|
|
359
|
+
print(f" - 目标表: {table_name}")
|
|
360
|
+
print(f" - 目标列: {column_name}")
|
|
361
|
+
|
|
362
|
+
if params:
|
|
363
|
+
print(f" - 参数: {params}")
|
|
364
|
+
|
|
365
|
+
if is_inline:
|
|
366
|
+
print(" - 存储方式: 内联约束(存储在表配置中)")
|
|
367
|
+
else:
|
|
368
|
+
constraint_id = msg.replace("inline:", "") if msg.startswith("inline:") else msg
|
|
369
|
+
print(" - 存储方式: 独立约束文件")
|
|
370
|
+
print(f" - 文件路径: constraints/{constraint_id}.constraint.yaml")
|
|
371
|
+
|
|
372
|
+
print("\n" + "=" * 50)
|
|
373
|
+
else:
|
|
374
|
+
print(Formatter.error("\n" + "=" * 50))
|
|
375
|
+
print(Formatter.error("✗ 约束操作失败"))
|
|
376
|
+
print(Formatter.error("=" * 50))
|
|
377
|
+
for r in constraint_results:
|
|
378
|
+
if not r.get("success"):
|
|
379
|
+
msg = r.get("message", "未知错误")
|
|
380
|
+
print(f" - 错误: {msg}")
|
|
381
|
+
print("=" * 50)
|
|
382
|
+
|
|
383
|
+
# 显示 Schema 操作结果
|
|
384
|
+
if schema_results:
|
|
385
|
+
print(Formatter.header("\n📋 Schema 操作"))
|
|
386
|
+
for r in schema_results:
|
|
387
|
+
action_type = r.get("action", {}).get("actionType", "")
|
|
388
|
+
spec = r.get("action", {}).get("schemaSpec", {})
|
|
389
|
+
name = spec.get("name", spec.get("schemaId", "未知"))
|
|
390
|
+
if r.get("success"):
|
|
391
|
+
print(Formatter.success(f" ✓ {action_display_name(action_type)}: {name}"))
|
|
392
|
+
else:
|
|
393
|
+
print(Formatter.error(f" ✗ {action_display_name(action_type)}: {name} - {r.get('message', '')}"))
|
|
394
|
+
|
|
395
|
+
# 显示 Regex 操作结果
|
|
396
|
+
if regex_results:
|
|
397
|
+
print(Formatter.header("\n🔤 Regex 操作"))
|
|
398
|
+
for r in regex_results:
|
|
399
|
+
action_type = r.get("action", {}).get("actionType", "")
|
|
400
|
+
spec = r.get("action", {}).get("regexSpec", {})
|
|
401
|
+
name = spec.get("name", spec.get("regexId", "未知"))
|
|
402
|
+
if r.get("success"):
|
|
403
|
+
print(Formatter.success(f" ✓ {action_display_name(action_type)}: {name}"))
|
|
404
|
+
else:
|
|
405
|
+
print(Formatter.error(f" ✗ {action_display_name(action_type)}: {name} - {r.get('message', '')}"))
|
|
406
|
+
|
|
407
|
+
# 显示 Transform 操作结果
|
|
408
|
+
if transform_results:
|
|
409
|
+
print(Formatter.header("\n⚙ Transform 操作"))
|
|
410
|
+
for r in transform_results:
|
|
411
|
+
action_type = r.get("action", {}).get("actionType", "")
|
|
412
|
+
spec = r.get("action", {}).get("transformSpec", {})
|
|
413
|
+
t_type = spec.get("type", spec.get("transformId", "未知"))
|
|
414
|
+
if r.get("success"):
|
|
415
|
+
print(Formatter.success(f" ✓ {action_display_name(action_type)}: {t_type}"))
|
|
416
|
+
else:
|
|
417
|
+
print(Formatter.error(f" ✗ {action_display_name(action_type)}: {t_type} - {r.get('message', '')}"))
|
|
418
|
+
|
|
419
|
+
# 显示 Settings 操作结果
|
|
420
|
+
if settings_results:
|
|
421
|
+
print(Formatter.header("\n⚙ 项目设置"))
|
|
422
|
+
for r in settings_results:
|
|
423
|
+
spec = r.get("action", {}).get("settingsSpec", {})
|
|
424
|
+
category = spec.get("category", "未知")
|
|
425
|
+
if r.get("success"):
|
|
426
|
+
print(Formatter.success(f" ✓ 更新设置: {category}"))
|
|
427
|
+
else:
|
|
428
|
+
print(Formatter.error(f" ✗ 更新设置失败: {r.get('message', '')}"))
|