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,285 @@
|
|
|
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/generate.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview CLI AI 配置生成命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 `ai generate` 子命令,从数据文件生成 Precis V2 配置
|
|
22
|
+
- 支持 Agent 多轮优化模式(默认)和单次快速生成模式
|
|
23
|
+
- 默认仅输出 YAML 预览,加 --apply 后写盘
|
|
24
|
+
- 无文件参数时自动扫描项目 data/ 目录并交互式选择
|
|
25
|
+
|
|
26
|
+
架构设计:
|
|
27
|
+
- AIGenerateCommand 继承 Command 基类
|
|
28
|
+
- 直接调用 ConfigGenerationService,不依赖后端 HTTP
|
|
29
|
+
- 写盘与数据文件扫描逻辑委托 shared_services.generation_ops(CLI/TUI 同源)
|
|
30
|
+
- _scan_data_files 保留为单参数向后兼容入口,等价 scan_data_files([], project_path)
|
|
31
|
+
|
|
32
|
+
输入示例:
|
|
33
|
+
precis> ai generate data/users.xlsx
|
|
34
|
+
precis> ai generate data/*.xlsx --apply --max-iterations 3
|
|
35
|
+
|
|
36
|
+
输出示例:
|
|
37
|
+
YAML 预览或写盘成功提示
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
import asyncio
|
|
43
|
+
import glob
|
|
44
|
+
import logging
|
|
45
|
+
import os
|
|
46
|
+
from pathlib import Path
|
|
47
|
+
from typing import Any
|
|
48
|
+
|
|
49
|
+
from app.cli.shared_services.generation_ops import (
|
|
50
|
+
SUPPORTED_EXTENSIONS,
|
|
51
|
+
apply_generated_config,
|
|
52
|
+
scan_data_files,
|
|
53
|
+
)
|
|
54
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
55
|
+
from app.cli.shell.formatter import Formatter
|
|
56
|
+
from app.shared.services.llm.generation import (
|
|
57
|
+
ConfigGenerationService,
|
|
58
|
+
GenerationOptions,
|
|
59
|
+
ProfilingOptions,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
logger = logging.getLogger(__name__)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _scan_data_files(project_path: str) -> list[str]:
|
|
66
|
+
"""扫描项目 data/ 目录下的支持文件(向后兼容入口)。
|
|
67
|
+
|
|
68
|
+
真正的实现已迁移到 shared_services.generation_ops.scan_data_files(CLI/TUI 同源)。
|
|
69
|
+
本函数保留单参数签名以兼容历史调用与现有测试,等价于 scan_data_files([], project_path)。
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
project_path: 项目根目录
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
绝对路径列表
|
|
76
|
+
"""
|
|
77
|
+
return scan_data_files([], project_path)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AIGenerateCommand(Command):
|
|
81
|
+
"""AI 配置生成命令。
|
|
82
|
+
|
|
83
|
+
从数据文件分析并生成 Schema、Constraint、Regex 配置。
|
|
84
|
+
默认输出 YAML 预览,使用 --apply 写入项目目录。
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(self) -> None:
|
|
88
|
+
super().__init__("generate", aliases=["gen"])
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def description(self) -> str:
|
|
92
|
+
return "从数据文件生成 Precis V2 配置"
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def usage(self) -> str:
|
|
96
|
+
return "ai generate [files...] [--apply] [--no-agent-mode] [--max-iterations N] [--sample-rows N]"
|
|
97
|
+
|
|
98
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
99
|
+
"""执行配置生成命令。
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
args: 命令参数列表
|
|
103
|
+
context: 项目上下文
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
命令执行结果
|
|
107
|
+
"""
|
|
108
|
+
if not context.is_project_open:
|
|
109
|
+
return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
|
|
110
|
+
|
|
111
|
+
project_path = context.project_path
|
|
112
|
+
if not project_path:
|
|
113
|
+
return CommandResult.error("项目路径为空")
|
|
114
|
+
|
|
115
|
+
# 解析参数
|
|
116
|
+
file_patterns: list[str] = []
|
|
117
|
+
apply = False
|
|
118
|
+
agent_mode = True
|
|
119
|
+
max_iterations = 2
|
|
120
|
+
sample_rows = 100
|
|
121
|
+
sample_values = 100
|
|
122
|
+
generate_regex = False
|
|
123
|
+
|
|
124
|
+
i = 0
|
|
125
|
+
while i < len(args):
|
|
126
|
+
arg = args[i]
|
|
127
|
+
if arg == "--apply":
|
|
128
|
+
apply = True
|
|
129
|
+
elif arg == "--no-agent-mode":
|
|
130
|
+
agent_mode = False
|
|
131
|
+
elif arg == "--max-iterations":
|
|
132
|
+
i += 1
|
|
133
|
+
if i < len(args):
|
|
134
|
+
try:
|
|
135
|
+
max_iterations = max(1, min(5, int(args[i])))
|
|
136
|
+
except ValueError:
|
|
137
|
+
return CommandResult.error(f"--max-iterations 需要整数, got: {args[i]}")
|
|
138
|
+
else:
|
|
139
|
+
return CommandResult.error("--max-iterations 需要参数")
|
|
140
|
+
elif arg == "--sample-rows":
|
|
141
|
+
i += 1
|
|
142
|
+
if i < len(args):
|
|
143
|
+
try:
|
|
144
|
+
sample_rows = max(1, int(args[i]))
|
|
145
|
+
except ValueError:
|
|
146
|
+
return CommandResult.error(f"--sample-rows 需要整数, got: {args[i]}")
|
|
147
|
+
else:
|
|
148
|
+
return CommandResult.error("--sample-rows 需要参数")
|
|
149
|
+
elif arg == "--sample-values":
|
|
150
|
+
i += 1
|
|
151
|
+
if i < len(args):
|
|
152
|
+
try:
|
|
153
|
+
sample_values = max(1, int(args[i]))
|
|
154
|
+
except ValueError:
|
|
155
|
+
return CommandResult.error(f"--sample-values 需要整数, got: {args[i]}")
|
|
156
|
+
else:
|
|
157
|
+
return CommandResult.error("--sample-values 需要参数")
|
|
158
|
+
elif arg == "--generate-regex":
|
|
159
|
+
generate_regex = True
|
|
160
|
+
elif arg.startswith("--"):
|
|
161
|
+
return CommandResult.error(f"未知参数: {arg}\n用法: {self.usage}")
|
|
162
|
+
else:
|
|
163
|
+
file_patterns.append(arg)
|
|
164
|
+
i += 1
|
|
165
|
+
|
|
166
|
+
# 展开文件路径(支持通配符和相对路径)
|
|
167
|
+
file_paths: list[str] = []
|
|
168
|
+
for pattern in file_patterns:
|
|
169
|
+
if not os.path.isabs(pattern):
|
|
170
|
+
pattern = os.path.join(project_path, pattern)
|
|
171
|
+
matched = glob.glob(pattern)
|
|
172
|
+
if matched:
|
|
173
|
+
file_paths.extend(matched)
|
|
174
|
+
elif os.path.exists(pattern):
|
|
175
|
+
file_paths.append(pattern)
|
|
176
|
+
|
|
177
|
+
# 无文件参数时扫描 data/ 目录(委托 shared_services 双参数版本)
|
|
178
|
+
if not file_paths:
|
|
179
|
+
scanned = scan_data_files([], project_path)
|
|
180
|
+
if not scanned:
|
|
181
|
+
return CommandResult.error(
|
|
182
|
+
"未找到数据文件。请在项目 data/ 目录放置 .xlsx/.csv/.json 文件,或显式指定文件路径。"
|
|
183
|
+
)
|
|
184
|
+
file_paths = scanned
|
|
185
|
+
|
|
186
|
+
# 过滤支持的文件类型
|
|
187
|
+
file_paths = [p for p in file_paths if p.lower().endswith(SUPPORTED_EXTENSIONS)]
|
|
188
|
+
if not file_paths:
|
|
189
|
+
return CommandResult.error("未找到支持的数据文件类型(.xlsx/.xls/.csv/.json/.jsonl)")
|
|
190
|
+
|
|
191
|
+
# 获取项目信息
|
|
192
|
+
config = context.project_config or {}
|
|
193
|
+
project_name = config.get("project", {}).get("name", Path(project_path).name)
|
|
194
|
+
project_id = config.get("project", {}).get("id", project_name)
|
|
195
|
+
|
|
196
|
+
# 输出待处理文件
|
|
197
|
+
print(Formatter.header("\nAI 配置生成"))
|
|
198
|
+
print(Formatter.info(f"项目: {project_name}"))
|
|
199
|
+
print(Formatter.info(f"Agent 模式: {'开启' if agent_mode else '关闭'}"))
|
|
200
|
+
print(Formatter.info(f"迭代次数: {max_iterations}"))
|
|
201
|
+
print(Formatter.info("数据文件:"))
|
|
202
|
+
for p in file_paths:
|
|
203
|
+
print(f" - {os.path.relpath(p, project_path)}")
|
|
204
|
+
|
|
205
|
+
def progress_callback(stage: str, progress: float, extra: dict[str, Any] | None = None) -> None:
|
|
206
|
+
"""终端进度回调。"""
|
|
207
|
+
msg = extra.get("message") if extra else None
|
|
208
|
+
prefix = f"[{stage}]" if not msg else f"[{stage}] {msg}"
|
|
209
|
+
print(Formatter.info(f" {prefix} {progress:.0f}%"))
|
|
210
|
+
|
|
211
|
+
service = ConfigGenerationService()
|
|
212
|
+
profiling_opts = ProfilingOptions(
|
|
213
|
+
sample_rows=sample_rows,
|
|
214
|
+
sample_values_per_column=sample_values,
|
|
215
|
+
)
|
|
216
|
+
gen_opts = GenerationOptions(
|
|
217
|
+
generate_schemas=True,
|
|
218
|
+
generate_constraints=True,
|
|
219
|
+
generate_regex_nodes=generate_regex,
|
|
220
|
+
keep_existing=True,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# CLI 是同步环境:按模式构造一次协程,asyncio.run 创建一次性事件循环
|
|
224
|
+
# 并在结束时确保关闭(不再 get_running_loop/new_event_loop 泄漏循环)
|
|
225
|
+
if agent_mode:
|
|
226
|
+
coro = service.generate_with_agent(
|
|
227
|
+
file_paths=file_paths,
|
|
228
|
+
project_name=project_name,
|
|
229
|
+
project_id=project_id,
|
|
230
|
+
config_path=project_path,
|
|
231
|
+
profiling_options=profiling_opts,
|
|
232
|
+
generation_options=gen_opts,
|
|
233
|
+
max_iterations=max_iterations,
|
|
234
|
+
progress_callback=progress_callback,
|
|
235
|
+
)
|
|
236
|
+
else:
|
|
237
|
+
coro = service.generate(
|
|
238
|
+
file_paths=file_paths,
|
|
239
|
+
project_name=project_name,
|
|
240
|
+
project_id=project_id,
|
|
241
|
+
config_path=project_path,
|
|
242
|
+
profiling_options=profiling_opts,
|
|
243
|
+
generation_options=gen_opts,
|
|
244
|
+
progress_callback=lambda stage, progress: progress_callback(stage, progress, None),
|
|
245
|
+
)
|
|
246
|
+
try:
|
|
247
|
+
result = asyncio.run(coro)
|
|
248
|
+
except Exception as e:
|
|
249
|
+
logger.error(f"配置生成失败: {e}", exc_info=True)
|
|
250
|
+
return CommandResult.error(f"配置生成失败: {e}")
|
|
251
|
+
|
|
252
|
+
if not result.get("success"):
|
|
253
|
+
error = result.get("error") or "配置生成失败"
|
|
254
|
+
return CommandResult.error(error)
|
|
255
|
+
|
|
256
|
+
yaml_preview = result.get("yaml_preview", "")
|
|
257
|
+
warnings = result.get("warnings", [])
|
|
258
|
+
|
|
259
|
+
if warnings:
|
|
260
|
+
print(Formatter.warning("\n生成警告:"))
|
|
261
|
+
for w in warnings:
|
|
262
|
+
print(f" - {w}")
|
|
263
|
+
|
|
264
|
+
if apply:
|
|
265
|
+
try:
|
|
266
|
+
apply_generated_config(result, project_path)
|
|
267
|
+
except Exception as e:
|
|
268
|
+
logger.error(f"写盘失败: {e}", exc_info=True)
|
|
269
|
+
return CommandResult.error(f"配置已生成,但写盘失败: {e}")
|
|
270
|
+
|
|
271
|
+
schema_count = len(result.get("schemas", {}))
|
|
272
|
+
constraint_count = len(result.get("constraints", {}))
|
|
273
|
+
regex_count = len(result.get("regex_nodes", {}))
|
|
274
|
+
return CommandResult.ok(
|
|
275
|
+
f"配置已生成并写入项目:{schema_count} 个 schema,"
|
|
276
|
+
f"{constraint_count} 个 constraint,{regex_count} 个 regex。"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# 默认仅输出预览
|
|
280
|
+
print(Formatter.header("\nYAML 预览(未写盘,加 --apply 可应用)"))
|
|
281
|
+
print(yaml_preview)
|
|
282
|
+
return CommandResult.ok("配置生成完成,以上为 YAML 预览。")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
__all__ = ["AIGenerateCommand"]
|
|
@@ -0,0 +1,262 @@
|
|
|
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/interaction.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview AI 交互辅助模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 在执行修改操作前向用户展示操作列表并确认
|
|
22
|
+
- 从项目 schema 文件中提取表格和列信息作为上下文,供 AI 理解项目结构
|
|
23
|
+
- 提供 Spinner 动画辅助函数(创建与停止)
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- confirm_actions(): 展示 AI 计划执行的操作列表,等待用户输入 y/yes 确认
|
|
27
|
+
- build_context_data(): 读取项目 schema 目录下的 YAML 文件,构建 AI 上下文
|
|
28
|
+
- create_spinner()/stop_spinner(): 简单的终端加载动画线程控制
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
confirm_actions(actions, "AI 回复说明")
|
|
32
|
+
build_context_data("用户消息", context)
|
|
33
|
+
|
|
34
|
+
输出示例:
|
|
35
|
+
True/False(用户是否确认执行)
|
|
36
|
+
{"message": "...", "context": {...}, "projectOverview": {...}}
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import logging
|
|
42
|
+
import threading
|
|
43
|
+
import time
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
from typing import Any
|
|
46
|
+
|
|
47
|
+
from app.cli.shell.formatter import Colors, Formatter
|
|
48
|
+
from app.shared.services.ai.utils import get_project_overview
|
|
49
|
+
|
|
50
|
+
logger = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def confirm_actions(actions: list[dict[str, Any]], reply: str) -> bool:
|
|
54
|
+
"""显示操作确认提示,等待用户确认。
|
|
55
|
+
|
|
56
|
+
在执行修改操作前,向用户展示 AI 计划执行的操作列表,
|
|
57
|
+
等待用户输入 y/yes 确认后才继续。
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
actions: 动作列表
|
|
61
|
+
reply: AI 的回复说明
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
用户是否确认执行
|
|
65
|
+
"""
|
|
66
|
+
print(Formatter.info("\n即将执行以下操作:"))
|
|
67
|
+
print("-" * 40)
|
|
68
|
+
|
|
69
|
+
# 显示 AI 的说明
|
|
70
|
+
if reply:
|
|
71
|
+
print(Formatter.info(f"说明: {reply}"))
|
|
72
|
+
print("-" * 40)
|
|
73
|
+
|
|
74
|
+
# 显示每个动作详情
|
|
75
|
+
for i, action in enumerate(actions, 1):
|
|
76
|
+
action_type = action.get("actionType", "UNKNOWN")
|
|
77
|
+
|
|
78
|
+
# 处理 VALIDATE_PROJECT 特殊显示
|
|
79
|
+
if action_type == "VALIDATE_PROJECT":
|
|
80
|
+
spec = action.get("constraintSpec", {})
|
|
81
|
+
table_name = spec.get("tableName", spec.get("targetNodeId", "所有表"))
|
|
82
|
+
tables = spec.get("tables") or spec.get("tableIds")
|
|
83
|
+
|
|
84
|
+
if tables:
|
|
85
|
+
if len(tables) == 1:
|
|
86
|
+
display = f"校验表: {tables[0]}"
|
|
87
|
+
else:
|
|
88
|
+
display = f"校验 {len(tables)} 张表: {', '.join(tables)}"
|
|
89
|
+
elif table_name and table_name != "所有表":
|
|
90
|
+
display = f"校验表: {table_name}"
|
|
91
|
+
else:
|
|
92
|
+
display = "校验所有表"
|
|
93
|
+
|
|
94
|
+
print(f" {i}. {display}")
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
# 通用动作描述映射
|
|
98
|
+
action_desc = {
|
|
99
|
+
"ADD_TO_CANVAS": "显示到画布(只读)",
|
|
100
|
+
"ADD_CONSTRAINT_NODE": "添加约束",
|
|
101
|
+
"UPDATE_CONSTRAINT_NODE": "更新约束",
|
|
102
|
+
"DELETE_CONSTRAINT_NODE": "删除约束",
|
|
103
|
+
"ADD_SCHEMA": "创建表",
|
|
104
|
+
"UPDATE_SCHEMA": "修改表结构",
|
|
105
|
+
"DELETE_SCHEMA": "删除表",
|
|
106
|
+
"ADD_REGEX": "创建正则校验",
|
|
107
|
+
"UPDATE_REGEX": "更新正则校验",
|
|
108
|
+
"DELETE_REGEX": "删除正则校验",
|
|
109
|
+
"ADD_TRANSFORM": "创建数据转换",
|
|
110
|
+
"UPDATE_TRANSFORM": "更新数据转换",
|
|
111
|
+
"DELETE_TRANSFORM": "删除数据转换",
|
|
112
|
+
"UPDATE_SETTINGS": "修改项目设置",
|
|
113
|
+
}.get(action_type, action_type)
|
|
114
|
+
|
|
115
|
+
# 根据动作类型提取展示信息
|
|
116
|
+
if action_type == "ADD_TO_CANVAS":
|
|
117
|
+
spec = action.get("canvasSpec", {})
|
|
118
|
+
resource_kind = spec.get("resourceKind", "未知")
|
|
119
|
+
resource_name = spec.get("name", spec.get("resourceId", "未知"))
|
|
120
|
+
print(f" {i}. {action_desc}: {resource_kind} / {resource_name}")
|
|
121
|
+
elif action_type in ("ADD_CONSTRAINT_NODE", "UPDATE_CONSTRAINT_NODE", "DELETE_CONSTRAINT_NODE"):
|
|
122
|
+
spec = action.get("constraintSpec", {})
|
|
123
|
+
constraint_type = spec.get("type", "Unknown")
|
|
124
|
+
table_name = spec.get("tableName", spec.get("targetNodeId", "未知"))
|
|
125
|
+
column_name = spec.get("targetColumn", spec.get("targetColumnId", "未知"))
|
|
126
|
+
print(f" {i}. {action_desc}")
|
|
127
|
+
print(f" 表: {table_name}, 字段: {column_name}, 类型: {constraint_type}")
|
|
128
|
+
elif action_type in ("ADD_SCHEMA", "UPDATE_SCHEMA", "DELETE_SCHEMA"):
|
|
129
|
+
spec = action.get("schemaSpec", {})
|
|
130
|
+
name = spec.get("name", spec.get("schemaId", "未知"))
|
|
131
|
+
columns = spec.get("columns", [])
|
|
132
|
+
print(f" {i}. {action_desc}: {name}")
|
|
133
|
+
if columns:
|
|
134
|
+
col_names = [c.get("name", "?") for c in columns]
|
|
135
|
+
print(f" 列: {', '.join(col_names)}")
|
|
136
|
+
elif action_type in ("ADD_REGEX", "UPDATE_REGEX", "DELETE_REGEX"):
|
|
137
|
+
spec = action.get("regexSpec", {})
|
|
138
|
+
name = spec.get("name", spec.get("regexId", "未知"))
|
|
139
|
+
print(f" {i}. {action_desc}: {name}")
|
|
140
|
+
elif action_type in ("ADD_TRANSFORM", "UPDATE_TRANSFORM", "DELETE_TRANSFORM"):
|
|
141
|
+
spec = action.get("transformSpec", {})
|
|
142
|
+
t_type = spec.get("type", "未知")
|
|
143
|
+
print(f" {i}. {action_desc}: {t_type}")
|
|
144
|
+
elif action_type == "UPDATE_SETTINGS":
|
|
145
|
+
spec = action.get("settingsSpec", {})
|
|
146
|
+
category = spec.get("category", "未知")
|
|
147
|
+
settings = spec.get("settings", {})
|
|
148
|
+
print(f" {i}. {action_desc}: {category}")
|
|
149
|
+
print(f" 设置: {settings}")
|
|
150
|
+
else:
|
|
151
|
+
print(f" {i}. {action_desc}")
|
|
152
|
+
|
|
153
|
+
print("-" * 40)
|
|
154
|
+
|
|
155
|
+
# 等待用户确认
|
|
156
|
+
try:
|
|
157
|
+
confirm = input(Formatter.warning("确认执行? (y/N): ")).strip().lower()
|
|
158
|
+
return confirm in ("y", "yes")
|
|
159
|
+
except (KeyboardInterrupt, EOFError):
|
|
160
|
+
print()
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def build_context_data(message: str, context: Any) -> dict[str, Any]:
|
|
165
|
+
"""构建上下文数据。
|
|
166
|
+
|
|
167
|
+
从项目配置中提取表格和列信息作为上下文,供 AI 理解项目结构。
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
message: 用户消息
|
|
171
|
+
context: 命令上下文
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
上下文数据字典,包含 message、context 和 projectOverview
|
|
175
|
+
"""
|
|
176
|
+
project_path = context.project_path
|
|
177
|
+
selected_nodes = []
|
|
178
|
+
|
|
179
|
+
# 尝试从 schema 文件中提取表格信息
|
|
180
|
+
if project_path:
|
|
181
|
+
schemas_dir = Path(project_path) / "schemas"
|
|
182
|
+
if schemas_dir.exists():
|
|
183
|
+
for schema_file in schemas_dir.glob("*.yaml"):
|
|
184
|
+
try:
|
|
185
|
+
import yaml
|
|
186
|
+
|
|
187
|
+
with open(schema_file, encoding="utf-8") as f:
|
|
188
|
+
schema_data = yaml.safe_load(f) or {}
|
|
189
|
+
|
|
190
|
+
# Schema 文件直接包含 columns 列表
|
|
191
|
+
table_name = schema_data.get("name", "")
|
|
192
|
+
table_id = schema_data.get("id", table_name)
|
|
193
|
+
|
|
194
|
+
columns = schema_data.get("columns", [])
|
|
195
|
+
column_info = []
|
|
196
|
+
for c in columns:
|
|
197
|
+
col_name = c.get("name", "")
|
|
198
|
+
col_id = c.get("id", col_name)
|
|
199
|
+
column_info.append({"id": col_id, "name": col_name})
|
|
200
|
+
|
|
201
|
+
if table_name or table_id:
|
|
202
|
+
# 优先使用 schema 文件中定义的 id
|
|
203
|
+
node_id = table_id
|
|
204
|
+
selected_nodes.append(
|
|
205
|
+
{
|
|
206
|
+
"id": node_id,
|
|
207
|
+
"type": "schema",
|
|
208
|
+
"data": {
|
|
209
|
+
"label": table_name or table_id,
|
|
210
|
+
"columns": column_info,
|
|
211
|
+
},
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.debug(f"读取 schema 文件失败 {schema_file}: {e}")
|
|
216
|
+
|
|
217
|
+
# 获取项目概览(表结构和约束)
|
|
218
|
+
project_overview = get_project_overview(project_path)
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
"message": message,
|
|
222
|
+
"context": {
|
|
223
|
+
"hasContext": len(selected_nodes) > 0,
|
|
224
|
+
"selectedNodes": selected_nodes,
|
|
225
|
+
},
|
|
226
|
+
"projectOverview": project_overview,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def create_spinner(message: str = "AI> ") -> tuple[threading.Event, threading.Thread]:
|
|
231
|
+
"""创建并启动一个 spinner 线程。
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
message: spinner 前显示的消息前缀
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
tuple: (stop_event, spinner_thread)
|
|
238
|
+
"""
|
|
239
|
+
stop_event = threading.Event()
|
|
240
|
+
|
|
241
|
+
def spinner() -> None:
|
|
242
|
+
chars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
243
|
+
i = 0
|
|
244
|
+
while not stop_event.is_set():
|
|
245
|
+
print(f"\r{Formatter.colorize(message, Colors.CYAN)}{chars[i % len(chars)]}", end="", flush=True)
|
|
246
|
+
time.sleep(0.1)
|
|
247
|
+
i += 1
|
|
248
|
+
|
|
249
|
+
thread = threading.Thread(target=spinner)
|
|
250
|
+
thread.start()
|
|
251
|
+
return stop_event, thread
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def stop_spinner(stop_event: threading.Event, thread: threading.Thread) -> None:
|
|
255
|
+
"""停止 spinner 线程。
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
stop_event: 停止事件
|
|
259
|
+
thread: spinner 线程
|
|
260
|
+
"""
|
|
261
|
+
stop_event.set()
|
|
262
|
+
thread.join()
|