├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yaml └── workflows │ ├── ai-service-release-image.yaml │ ├── ai-service-release-nightly-image.yaml │ ├── ai-service-release-stable-image.yaml │ ├── ai-service-test.yaml │ ├── create-rc-release-pr.yaml │ ├── create-rc-release.yaml │ ├── pr-tagger.yaml │ ├── pull-request-title-validator.yaml │ ├── ui-lint.yaml │ ├── ui-release-image-stable.yaml │ ├── ui-release-image.yaml │ └── ui-test.yaml ├── .gitignore ├── .gitmodules ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── deployment ├── README.md └── kustomizations │ ├── .gitignore │ ├── README.md │ ├── base │ ├── cm.yaml │ ├── deploy-wren-ai-service.yaml │ ├── deploy-wren-engine.yaml │ ├── deploy-wren-ibis-server.yaml │ ├── deploy-wren-ui.yaml │ ├── pvc.yaml │ └── svc.yaml │ ├── examples │ ├── .gitignore │ ├── certificate-qdrant_example.yaml │ ├── certificate-wren_example.yaml │ ├── ingress-wren_example.yaml │ └── secret-wren_example.yaml │ ├── helm-values-qdrant_1.11.0.yaml │ ├── helm-values_postgresql_15.yaml │ ├── kustomization.yaml │ └── patches │ ├── README.md │ ├── cm.yaml │ ├── rm-certificate.yaml │ ├── rm-ingress.yaml │ └── service.yaml ├── docker ├── .env.example ├── README.md ├── bootstrap │ ├── Dockerfile │ └── init.sh ├── config.example.yaml ├── docker-compose-dev.yaml └── docker-compose.yaml ├── misc ├── AI-generated-understanding_recommend_questions.png ├── API_doc.png ├── data_source.png ├── how_wrenai_works.png ├── logo_template.jpg ├── preview_ask.png ├── preview_model.png ├── wren-context.png ├── wren-excel.png ├── wren-genbi.png ├── wren-insight.png ├── wren-lang.png ├── wren-modeling.png ├── wren-visual.png ├── wren_workflow.png ├── wrenai_logo.png ├── wrenai_logo_white.png ├── wrenai_view.png └── wrenai_vision.png ├── wren-ai-service ├── .dockerignore ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── Justfile ├── README.md ├── docker │ └── Dockerfile ├── docs │ ├── code_design.md │ ├── config_examples │ │ ├── README.md │ │ ├── config.anthropic.yaml │ │ ├── config.azure.yaml │ │ ├── config.deepseek.yaml │ │ ├── config.google_ai_studio.yaml │ │ ├── config.grok.yaml │ │ ├── config.groq.yaml │ │ ├── config.lm_studio.yaml │ │ ├── config.ollama.yaml │ │ └── config.open_router.yaml │ ├── configuration.md │ └── imgs │ │ └── shallow_trace_example.png ├── entrypoint.sh ├── eval │ ├── .gitignore │ ├── README.md │ ├── __init__.py │ ├── add_samples_to_toml.py │ ├── data_curation │ │ ├── __init__.py │ │ ├── app.py │ │ └── utils.py │ ├── dataset │ │ └── .gitignore │ ├── dspy_modules │ │ ├── __init__.py │ │ ├── ask_generation.py │ │ └── prompt_optimizer.py │ ├── evaluation.py │ ├── llm_trace_analysis │ │ ├── __init__.py │ │ ├── chart_eval_app.py │ │ ├── notebook.ipynb │ │ ├── report_gen.py │ │ └── utils.py │ ├── mdl_to_csv.py │ ├── metrics │ │ ├── __init__.py │ │ ├── accuracy.py │ │ ├── answer_relevancy.py │ │ ├── context_precision.py │ │ ├── context_recall.py │ │ ├── context_relevancy.py │ │ ├── faithfulness.py │ │ ├── llm │ │ │ └── __init__.py │ │ └── spider │ │ │ ├── __init__.py │ │ │ ├── exact_match.py │ │ │ ├── exec_match.py │ │ │ └── process_sql.py │ ├── optimized │ │ └── .gitignore │ ├── pipelines.py │ ├── prediction.py │ ├── preparation.py │ └── utils.py ├── poetry.lock ├── pyproject.toml ├── ruff.toml ├── src │ ├── __init__.py │ ├── __main__.py │ ├── config.py │ ├── core │ │ ├── __init__.py │ │ ├── engine.py │ │ ├── pipeline.py │ │ └── provider.py │ ├── force_deploy.py │ ├── force_update_config.py │ ├── globals.py │ ├── pipelines │ │ ├── __init__.py │ │ ├── common.py │ │ ├── generation │ │ │ ├── __init__.py │ │ │ ├── chart_adjustment.py │ │ │ ├── chart_generation.py │ │ │ ├── data_assistance.py │ │ │ ├── followup_sql_generation.py │ │ │ ├── followup_sql_generation_reasoning.py │ │ │ ├── intent_classification.py │ │ │ ├── misleading_assistance.py │ │ │ ├── question_recommendation.py │ │ │ ├── relationship_recommendation.py │ │ │ ├── semantics_description.py │ │ │ ├── sql_answer.py │ │ │ ├── sql_correction.py │ │ │ ├── sql_generation.py │ │ │ ├── sql_generation_reasoning.py │ │ │ ├── sql_question.py │ │ │ ├── sql_regeneration.py │ │ │ ├── sql_tables_extraction.py │ │ │ ├── user_guide_assistance.py │ │ │ └── utils │ │ │ │ ├── chart.py │ │ │ │ ├── sql.py │ │ │ │ └── vega-lite-schema-v5.json │ │ ├── indexing │ │ │ ├── __init__.py │ │ │ ├── db_schema.py │ │ │ ├── historical_question.py │ │ │ ├── instructions.py │ │ │ ├── project_meta.py │ │ │ ├── sql_pairs.py │ │ │ ├── table_description.py │ │ │ └── utils │ │ │ │ └── helper.py │ │ └── retrieval │ │ │ ├── __init__.py │ │ │ ├── db_schema_retrieval.py │ │ │ ├── historical_question_retrieval.py │ │ │ ├── instructions.py │ │ │ ├── preprocess_sql_data.py │ │ │ ├── sql_executor.py │ │ │ ├── sql_functions.py │ │ │ └── sql_pairs_retrieval.py │ ├── providers │ │ ├── __init__.py │ │ ├── document_store │ │ │ ├── __init__.py │ │ │ └── qdrant.py │ │ ├── embedder │ │ │ ├── __init__.py │ │ │ └── litellm.py │ │ ├── engine │ │ │ ├── __init__.py │ │ │ └── wren.py │ │ ├── llm │ │ │ ├── __init__.py │ │ │ └── litellm.py │ │ └── loader.py │ ├── utils.py │ └── web │ │ ├── __init__.py │ │ ├── development.py │ │ └── v1 │ │ ├── __init__.py │ │ ├── routers │ │ ├── __init__.py │ │ ├── ask.py │ │ ├── chart.py │ │ ├── chart_adjustment.py │ │ ├── instructions.py │ │ ├── question_recommendation.py │ │ ├── relationship_recommendation.py │ │ ├── semantics_description.py │ │ ├── semantics_preparation.py │ │ ├── sql_answers.py │ │ ├── sql_corrections.py │ │ ├── sql_pairs.py │ │ └── sql_question.py │ │ └── services │ │ ├── __init__.py │ │ ├── ask.py │ │ ├── chart.py │ │ ├── chart_adjustment.py │ │ ├── instructions.py │ │ ├── question_recommendation.py │ │ ├── relationship_recommendation.py │ │ ├── semantics_description.py │ │ ├── semantics_preparation.py │ │ ├── sql_answer.py │ │ ├── sql_corrections.py │ │ ├── sql_pairs.py │ │ └── sql_question.py ├── tests │ ├── __init__.py │ ├── data │ │ ├── book_2_mdl.json │ │ ├── config.test.yaml │ │ ├── mock_pyproject.toml │ │ └── pairs.json │ ├── locust │ │ ├── __init__.py │ │ ├── config_users.json │ │ ├── locust.conf │ │ ├── locust_script.py │ │ └── locustfile.py │ └── pytest │ │ ├── __init__.py │ │ ├── eval │ │ ├── __init__.py │ │ └── test_metrics.py │ │ ├── pipelines │ │ ├── __init__.py │ │ ├── generation │ │ │ ├── __init__.py │ │ │ ├── test_ask.py │ │ │ └── test_semantics_enrichment.py │ │ ├── indexing │ │ │ ├── __init__.py │ │ │ ├── test_db_schema.py │ │ │ ├── test_helper.py │ │ │ ├── test_historical_questions.py │ │ │ ├── test_indexing.py │ │ │ ├── test_instructions.py │ │ │ ├── test_sql_pairs.py │ │ │ └── test_table_description.py │ │ └── retrieval │ │ │ ├── __init__.py │ │ │ └── sql_function.py │ │ ├── providers │ │ ├── __init__.py │ │ ├── test_loader.py │ │ └── test_providers.py │ │ ├── services │ │ ├── __init__.py │ │ ├── mocks.py │ │ ├── test_ask.py │ │ ├── test_instructions.py │ │ ├── test_relationship_recommendation.py │ │ ├── test_semantics_description.py │ │ └── test_sql_pairs.py │ │ ├── test_config.py │ │ ├── test_main.py │ │ ├── test_usecases.py │ │ └── test_utils.py └── tools │ ├── .env.example │ ├── config │ ├── .env.dev.example │ ├── config.example.yaml │ └── config.full.yaml │ ├── dev │ ├── .env │ ├── config.properties.example │ └── docker-compose-dev.yaml │ ├── mdl_to_str.py │ └── run_sql.py ├── wren-launcher ├── Makefile ├── README.md ├── commands │ └── launch.go ├── config │ └── config.go ├── go.mod ├── go.sum ├── main.go └── utils │ ├── docker.go │ ├── docker_test.go │ ├── network.go │ ├── os.go │ ├── rc.go │ └── rc_test.go ├── wren-mdl └── mdl.schema.json └── wren-ui ├── .dockerignore ├── .env.test ├── .eslintignore ├── .eslintrc.json ├── .prettierrc ├── .yarn └── releases │ └── yarn-4.5.3.cjs ├── .yarnrc.yml ├── Dockerfile ├── README.md ├── codegen.yaml ├── e2e ├── README.md ├── commonTests │ ├── home.ts │ ├── modeling.ts │ └── onboarding.ts ├── config.ts ├── global.setup.ts ├── global.teardown.ts ├── helper.ts └── specs │ ├── connectBigQuery.spec.ts │ ├── connectClickHouse.spec.ts │ ├── connectDuckDB.spec.ts │ ├── connectMySQL.spec.ts │ ├── connectPostgreSQL.spec.ts │ ├── connectSQLServer.spec.ts │ ├── connectSampleECommerce.spec.ts │ ├── connectSampleHR.spec.ts │ ├── connectSnowflake.spec.ts │ └── connectTrino.spec.ts ├── jest.config.js ├── knexfile.js ├── migrations ├── 20240125070643_create_project_table.js ├── 20240125071855_create_model_table.js ├── 20240125081244_create_model_column_table.js ├── 20240125083821_create_relation_table.js ├── 20240125085655_create_metrics_table.js ├── 20240126100753_create_metrics_measure_table.js ├── 20240129021453_create_view_table.js ├── 20240319083758_create_deploy_table.js ├── 20240327030000_create_ask_table.js ├── 20240418000000_update_project_table_pg.js ├── 20240419090558_add_foreign_key_to_model_column_and_metric_measure.js ├── 20240425000000_add_thread_response_summary.js ├── 20240430033014_update_model_column_table.js ├── 20240446090560_update_relationship_table.js ├── 20240502000000_add_properties_to_relationship.js ├── 20240524044348_update_project_table.js ├── 20240524071859_update_thread_table.js ├── 20240530062133_update_project_table.js ├── 20240530062809_transfer_project_table_data.js ├── 20240530105955_drop_project_table_columns.js ├── 20240531085916_transfer_model_properties.js ├── 20240610070534_create_schema_change_table.js ├── 20240928165009_create_model_nested_column.js ├── 20241021073019_update_project_language.js ├── 20241029092204_create_learning_table.js ├── 20241106232204_update_project_table.js ├── 20241107171828_update_thread_table.js ├── 20241115031024_drop_thread_response_table_column.js ├── 20241207000000_update_thread_response_for_answer.js ├── 20241210072534_update_thread_response_table.js ├── 20241226135712_remove_thread_sql.js ├── 20250102074255_create_dashboard_table.js ├── 20250102074256_create_dashboard_item_table.js ├── 20250102074256_create_sql_pair_table.js ├── 20250311046282_create_instruction_table.js ├── 20250320074256_alter_sql_pair_table.js ├── 20250422000000_alter_dashboard_table.js ├── 20250423000000_create_dashboard_cache_refresh_table.js ├── 20250509000000_create_asking_task.js ├── 20250509000001_add_task_id_to_thread.js ├── 20250510000000_add_adjustment_to_thread_response.js ├── 20250510000001_alter_dashboard_item_table.js ├── 20250510000002_add_version_to_project.js └── 20250511000000-create-api-history.js ├── next.config.js ├── openapi.yaml ├── package.json ├── playwright.config.ts ├── public ├── favicon.ico └── images │ ├── dashboard │ ├── s1.jpg │ ├── s2.jpg │ └── s3.jpg │ ├── dataSource │ ├── bigQuery.svg │ ├── clickhouse.svg │ ├── duckDb.svg │ ├── mysql.svg │ ├── oracle.svg │ ├── postgreSql.svg │ ├── snowflake.svg │ ├── sqlserver.svg │ └── trino.svg │ ├── icon │ └── message-ai.svg │ ├── learning │ ├── ask-question.jpg │ ├── data-modeling.jpg │ ├── deploy-modeling.jpg │ ├── edit-metadata.gif │ ├── edit-model.gif │ ├── instructions.png │ └── save-to-knowledge.gif │ ├── logo-white-with-text.svg │ └── logo.svg ├── src ├── apollo │ ├── client │ │ ├── graphql │ │ │ ├── __types__.ts │ │ │ ├── apiManagement.generated.ts │ │ │ ├── apiManagement.ts │ │ │ ├── calculatedField.generated.ts │ │ │ ├── calculatedField.ts │ │ │ ├── dashboard.generated.ts │ │ │ ├── dashboard.ts │ │ │ ├── dataSource.generated.ts │ │ │ ├── dataSource.ts │ │ │ ├── deploy.generated.ts │ │ │ ├── deploy.ts │ │ │ ├── diagram.generated.ts │ │ │ ├── diagram.ts │ │ │ ├── home.generated.ts │ │ │ ├── home.ts │ │ │ ├── instructions.generated.ts │ │ │ ├── instructions.ts │ │ │ ├── learning.generated.ts │ │ │ ├── learning.ts │ │ │ ├── metadata.generated.ts │ │ │ ├── metadata.ts │ │ │ ├── model.generated.ts │ │ │ ├── model.ts │ │ │ ├── onboarding.generated.ts │ │ │ ├── onboarding.ts │ │ │ ├── relationship.generated.ts │ │ │ ├── relationship.ts │ │ │ ├── settings.generated.ts │ │ │ ├── settings.ts │ │ │ ├── sql.generated.ts │ │ │ ├── sql.ts │ │ │ ├── sqlPairs.generated.ts │ │ │ ├── sqlPairs.ts │ │ │ ├── view.generated.ts │ │ │ └── view.ts │ │ └── index.ts │ └── server │ │ ├── adaptors │ │ ├── ibisAdaptor.ts │ │ ├── index.ts │ │ ├── tests │ │ │ ├── ibisAdaptor.test.ts │ │ │ └── wrenAIAdaptor.test.ts │ │ ├── wrenAIAdaptor.ts │ │ └── wrenEngineAdaptor.ts │ │ ├── backgrounds │ │ ├── adjustmentBackgroundTracker.ts │ │ ├── chart.ts │ │ ├── dashboardCacheBackgroundTracker.ts │ │ ├── index.ts │ │ ├── recommend-question.ts │ │ └── textBasedAnswerBackgroundTracker.ts │ │ ├── config.ts │ │ ├── data │ │ ├── index.ts │ │ ├── sample.ts │ │ └── type.ts │ │ ├── dataSource.ts │ │ ├── index.ts │ │ ├── managers │ │ └── dataSourceSchemaDetector.ts │ │ ├── mdl │ │ ├── mdlBuilder.ts │ │ ├── test │ │ │ └── mdlBuilder.test.ts │ │ └── type.ts │ │ ├── models │ │ ├── adaptor.ts │ │ ├── dashboard.ts │ │ ├── index.ts │ │ ├── instruction.ts │ │ └── model.ts │ │ ├── repositories │ │ ├── apiHistoryRepository.ts │ │ ├── askingTaskRepository.ts │ │ ├── baseRepository.ts │ │ ├── dashboardItemRefreshJobRepository.ts │ │ ├── dashboardItemRepository.ts │ │ ├── dashboardRepository.ts │ │ ├── deployLogRepository.ts │ │ ├── index.ts │ │ ├── instructionRepository.ts │ │ ├── learningRepository.ts │ │ ├── metricsMeasureRepository.ts │ │ ├── metricsRepository.ts │ │ ├── modelColumnRepository.ts │ │ ├── modelNestedColumnRepository.ts │ │ ├── modelRepository.ts │ │ ├── projectRepository.ts │ │ ├── relationshipRepository.ts │ │ ├── schemaChangeRepository.ts │ │ ├── sqlPairRepository.ts │ │ ├── threadRepository.ts │ │ ├── threadResponseRepository.ts │ │ └── viewRepository.ts │ │ ├── resolvers.ts │ │ ├── resolvers │ │ ├── apiHistoryResolver.ts │ │ ├── askingResolver.ts │ │ ├── dashboardResolver.ts │ │ ├── diagramResolver.ts │ │ ├── instructionResolver.ts │ │ ├── learningResolver.ts │ │ ├── modelResolver.ts │ │ ├── projectResolver.ts │ │ └── sqlPairResolver.ts │ │ ├── scalars.ts │ │ ├── schema.ts │ │ ├── services │ │ ├── askingService.ts │ │ ├── askingTaskTracker.ts │ │ ├── dashboardService.ts │ │ ├── deployService.ts │ │ ├── index.ts │ │ ├── instructionService.ts │ │ ├── mdlService.ts │ │ ├── metadataService.ts │ │ ├── modelService.ts │ │ ├── projectService.ts │ │ ├── queryService.ts │ │ ├── sqlPairService.ts │ │ └── tests │ │ │ ├── askingService.test.ts │ │ │ ├── dashboardService.test.ts │ │ │ ├── deployService.test.ts │ │ │ └── queryService.test.ts │ │ ├── telemetry │ │ └── telemetry.ts │ │ ├── types │ │ ├── context.ts │ │ ├── dataSource.ts │ │ ├── diagram.ts │ │ ├── index.ts │ │ ├── manifest.ts │ │ ├── metric.ts │ │ └── relationship.ts │ │ └── utils │ │ ├── apiUtils.ts │ │ ├── dataUtils.ts │ │ ├── docker.ts │ │ ├── encode.ts │ │ ├── encryptor.ts │ │ ├── error.ts │ │ ├── helper.ts │ │ ├── index.ts │ │ ├── knex.ts │ │ ├── logger.ts │ │ ├── model.ts │ │ ├── regex.ts │ │ ├── string.ts │ │ ├── tests │ │ ├── dataSource.test.ts │ │ ├── encryptor.test.ts │ │ └── regex.test.ts │ │ └── timezone.ts ├── common.ts ├── components │ ├── ActionButton.tsx │ ├── EditableWrapper.tsx │ ├── EllipsisWrapper.tsx │ ├── ErrorCollapse.tsx │ ├── HeaderBar.tsx │ ├── Logo.tsx │ ├── LogoBar.tsx │ ├── PageLoading.tsx │ ├── chart │ │ ├── handler.ts │ │ ├── index.tsx │ │ └── properties │ │ │ ├── BasicProperties.tsx │ │ │ ├── DonutProperties.tsx │ │ │ ├── GroupedBarProperties.tsx │ │ │ ├── LineProperties.tsx │ │ │ └── StackedBarProperties.tsx │ ├── code │ │ ├── BaseCodeBlock.tsx │ │ ├── JsonCodeBlock.tsx │ │ └── SQLCodeBlock.tsx │ ├── dataPreview │ │ ├── PreviewData.tsx │ │ └── PreviewDataContent.tsx │ ├── deploy │ │ ├── Context.ts │ │ └── Deploy.tsx │ ├── diagram │ │ ├── Context.ts │ │ ├── CustomDropdown.tsx │ │ ├── CustomPopover.tsx │ │ ├── Marker.tsx │ │ ├── customEdge │ │ │ ├── ModelEdge.tsx │ │ │ └── index.ts │ │ ├── customNode │ │ │ ├── Column.tsx │ │ │ ├── MarkerHandle.tsx │ │ │ ├── ModelNode.tsx │ │ │ ├── ViewNode.tsx │ │ │ ├── index.ts │ │ │ └── utils.tsx │ │ ├── index.tsx │ │ └── utils.ts │ ├── editor │ │ ├── AceEditor.tsx │ │ ├── MarkdownBlock.tsx │ │ ├── MarkdownEditor.tsx │ │ └── SQLEditor.tsx │ ├── layouts │ │ ├── PageLayout.tsx │ │ ├── SiderLayout.tsx │ │ └── SimpleLayout.tsx │ ├── learning │ │ ├── guide │ │ │ ├── index.tsx │ │ │ ├── stories.tsx │ │ │ └── utils.ts │ │ └── index.tsx │ ├── modals │ │ ├── AdjustReasoningStepsModal.tsx │ │ ├── AdjustSQLModal.tsx │ │ ├── CalculatedFieldModal.tsx │ │ ├── DeleteModal.tsx │ │ ├── FixSQLModal.tsx │ │ ├── ImportDataSourceSQLModal.tsx │ │ ├── InstructionModal.tsx │ │ ├── QuestionSQLPairModal.tsx │ │ ├── RelationModal.tsx │ │ ├── SaveAsViewModal.tsx │ │ └── SchemaChangeModal.tsx │ ├── pages │ │ ├── apiManagement │ │ │ └── DetailsDrawer.tsx │ │ ├── home │ │ │ ├── RecommendedQuestions.tsx │ │ │ ├── dashboardGrid │ │ │ │ ├── CacheSettingsDrawer.tsx │ │ │ │ ├── DashboardHeader.tsx │ │ │ │ ├── EmptyDashboard.tsx │ │ │ │ └── index.tsx │ │ │ ├── preparation │ │ │ │ ├── ErrorBoundary.tsx │ │ │ │ ├── PreparationStatus.tsx │ │ │ │ ├── PreparationSteps.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── step │ │ │ │ │ ├── FixedSQLFinished.tsx │ │ │ │ │ ├── Generating.tsx │ │ │ │ │ ├── Organizing.tsx │ │ │ │ │ ├── Retrieving.tsx │ │ │ │ │ ├── SQLPairFinished.tsx │ │ │ │ │ └── ViewFinished.tsx │ │ │ ├── prompt │ │ │ │ ├── DemoPrompt.tsx │ │ │ │ ├── Input.tsx │ │ │ │ ├── RecommendedQuestionsPrompt.tsx │ │ │ │ ├── Result.tsx │ │ │ │ └── index.tsx │ │ │ └── promptThread │ │ │ │ ├── AnswerResult.tsx │ │ │ │ ├── ChartAnswer.tsx │ │ │ │ ├── TextBasedAnswer.tsx │ │ │ │ ├── ViewBlock.tsx │ │ │ │ ├── ViewSQLTabContent.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── store.tsx │ │ ├── knowledge │ │ │ ├── GlobalLabel.tsx │ │ │ ├── InstructionDrawer.tsx │ │ │ └── SQLPairDrawer.tsx │ │ ├── modeling │ │ │ ├── EditMetadataModal.tsx │ │ │ ├── MetadataDrawer.tsx │ │ │ ├── ModelDrawer.tsx │ │ │ ├── form │ │ │ │ └── ModelForm.tsx │ │ │ └── metadata │ │ │ │ ├── EditBasicMetadata.tsx │ │ │ │ ├── EditModelMetadata.tsx │ │ │ │ ├── EditViewMetadata.tsx │ │ │ │ ├── ModelMetadata.tsx │ │ │ │ └── ViewMetadata.tsx │ │ └── setup │ │ │ ├── ButtonItem.tsx │ │ │ ├── ConnectDataSource.tsx │ │ │ ├── ContainerCard.tsx │ │ │ ├── DefineRelations.tsx │ │ │ ├── SelectModels.tsx │ │ │ ├── Starter.tsx │ │ │ ├── dataSources │ │ │ ├── BigQueryProperties.tsx │ │ │ ├── ClickHouseProperties.tsx │ │ │ ├── DuckDBProperties.tsx │ │ │ ├── MySQLProperties.tsx │ │ │ ├── OracleProperties.tsx │ │ │ ├── PostgreSQLProperties.tsx │ │ │ ├── SQLServerProperties.tsx │ │ │ ├── SnowflakeProperties.tsx │ │ │ └── TrinoProperties.tsx │ │ │ └── utils.tsx │ ├── selectors │ │ ├── CombineFieldSelector.tsx │ │ ├── DescriptiveSelector.tsx │ │ ├── Selector.tsx │ │ └── lineageSelector │ │ │ ├── FieldSelect.tsx │ │ │ └── index.tsx │ ├── settings │ │ ├── DataSourceSettings.tsx │ │ ├── ProjectSettings.tsx │ │ ├── index.tsx │ │ └── utils.tsx │ ├── sidebar │ │ ├── APIManagement.tsx │ │ ├── Home.tsx │ │ ├── Knowledge.tsx │ │ ├── LabelTitle.tsx │ │ ├── Modeling.tsx │ │ ├── SidebarMenu.tsx │ │ ├── SidebarTree.tsx │ │ ├── home │ │ │ ├── ThreadTree.tsx │ │ │ ├── TreeTitle.tsx │ │ │ └── TreeTitleInput.tsx │ │ ├── index.tsx │ │ ├── modeling │ │ │ ├── GroupTreeTitle.tsx │ │ │ ├── ModelTree.tsx │ │ │ └── ViewTree.tsx │ │ └── utils.tsx │ └── table │ │ ├── BaseTable.tsx │ │ ├── CalculatedFieldTable.tsx │ │ ├── EditableBaseTable.tsx │ │ ├── FieldTable.tsx │ │ ├── ModelRelationSelectionTable.tsx │ │ ├── MultiSelectBox.tsx │ │ ├── NestedFieldTable.tsx │ │ ├── RelationTable.tsx │ │ ├── SelectionTable.tsx │ │ └── TableTransfer.tsx ├── hooks │ ├── .gitkeep │ ├── useAdjustAnswer.tsx │ ├── useAskProcessState.tsx │ ├── useAskPrompt.tsx │ ├── useAskingStreamTask.tsx │ ├── useAutoComplete.tsx │ ├── useCheckOnboarding.tsx │ ├── useCombineFieldOptions.tsx │ ├── useDrawerAction.tsx │ ├── useDropdown.tsx │ ├── useExpressionFieldOptions.tsx │ ├── useGlobalConfig.tsx │ ├── useHomeSidebar.tsx │ ├── useModalAction.tsx │ ├── useNativeSQL.tsx │ ├── useRecommendedQuestionsInstruction.tsx │ ├── useRelationshipModal.tsx │ ├── useSetupConnection.tsx │ ├── useSetupConnectionDataSource.tsx │ ├── useSetupConnectionSampleDataset.tsx │ ├── useSetupModels.tsx │ ├── useSetupRelations.tsx │ ├── useStoreContext.tsx │ └── useTextBasedAnswerStreamTask.tsx ├── import │ ├── antd.ts │ └── icon.ts ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── api-management │ │ └── history.tsx │ ├── api │ │ ├── ask_task │ │ │ ├── streaming.ts │ │ │ └── streaming_answer.ts │ │ ├── config.ts │ │ ├── graphql.ts │ │ └── v1 │ │ │ ├── generate_sql.ts │ │ │ ├── generate_vega_chart.ts │ │ │ ├── run_sql.ts │ │ │ └── stream_explanation.ts │ ├── home │ │ ├── [id].tsx │ │ ├── dashboard.tsx │ │ └── index.tsx │ ├── index.tsx │ ├── knowledge │ │ ├── instructions.tsx │ │ └── question-sql-pairs.tsx │ ├── modeling.tsx │ └── setup │ │ ├── connection.tsx │ │ ├── models.tsx │ │ └── relationships.tsx ├── styles │ ├── antd-variables.less │ ├── components │ │ ├── alert.less │ │ ├── avatar.less │ │ ├── button.less │ │ ├── chart.less │ │ ├── driver.less │ │ ├── scrollbar.less │ │ ├── select.less │ │ ├── table.less │ │ ├── tag.less │ │ └── transfer.less │ ├── index.less │ ├── layouts │ │ ├── global.less │ │ └── main.less │ └── utilities │ │ ├── animation.less │ │ ├── border.less │ │ ├── color.less │ │ ├── display.less │ │ ├── flex.less │ │ ├── grid.less │ │ ├── spacing.less │ │ └── text.less └── utils │ ├── columnType.tsx │ ├── data │ ├── dictionary.ts │ ├── index.ts │ └── type │ │ ├── index.ts │ │ └── modeling.ts │ ├── dataSourceType.ts │ ├── diagram │ ├── creator.ts │ ├── index.ts │ └── transformer.ts │ ├── enum │ ├── columnType.ts │ ├── dataSources.ts │ ├── diagram.ts │ ├── dropdown.ts │ ├── form.ts │ ├── home.ts │ ├── index.ts │ ├── menu.ts │ ├── modeling.ts │ ├── path.ts │ ├── settings.ts │ └── setup.ts │ ├── env.ts │ ├── error │ ├── dictionary.ts │ └── index.ts │ ├── errorHandler.tsx │ ├── events.tsx │ ├── expressionType.ts │ ├── helper.ts │ ├── icons.ts │ ├── iteration.tsx │ ├── language.ts │ ├── modelingHelper.ts │ ├── nodeType.tsx │ ├── svgs │ ├── CopilotSVG.tsx │ ├── EditSVG.tsx │ ├── InstructionsSVG.tsx │ ├── RobotSVG.tsx │ └── index.ts │ ├── table.tsx │ ├── telemetry.ts │ ├── time.ts │ ├── validator │ ├── calculatedFieldValidator.ts │ ├── cronValidator.ts │ ├── hostValidator.ts │ ├── index.ts │ ├── relationshipValidator.ts │ ├── sqlPairValidator.ts │ └── viewValidator.ts │ ├── vegaSpecUtils.test.ts │ └── vegaSpecUtils.ts ├── tools └── knex.js ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | 15 | # Makefile 16 | [Makefile] 17 | indent_style = tab 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | 30 | **Wren AI Information** 31 | - Version: [e.g, 0.1.0] 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | 36 | **Relevant log output** 37 | - Please share `config.yaml` with us, it should be located at `~/.wrenai/config.yaml`. 38 | - Please share your logs with us with the following command: 39 | ```bash 40 | docker logs wrenai-wren-ui-1 >& wrenai-wren-ui.log && \ 41 | docker logs wrenai-wren-ai-service-1 >& wrenai-wren-ai-service.log && \ 42 | docker logs wrenai-wren-engine-1 >& wrenai-wren-engine.log && \ 43 | docker logs wrenai-ibis-server-1 >& wrenai-ibis-server.log 44 | ``` 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature-request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/wren-ai-service" 5 | schedule: 6 | interval: "weekly" 7 | groups: 8 | all: 9 | patterns: ["*"] 10 | commit-message: 11 | prefix: "chore(wren-ai-service)" 12 | labels: 13 | - "dependencies" 14 | - "python" 15 | - "ci/ai-service" 16 | - "module/ai-service" 17 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-title-validator.yaml: -------------------------------------------------------------------------------- 1 | name: Pull Request Title Validator 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "wren-ai-service/**" 7 | - ".github/workflows/pull-request-title-validator.yaml" 8 | - ".github/workflows/ai-service-*.yaml" 9 | types: [opened, edited, synchronize] 10 | 11 | permissions: 12 | pull-requests: read 13 | 14 | jobs: 15 | validator: 16 | name: validate-pull-request-title 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: validate pull request title 20 | uses: kontrolplane/pull-request-title-validator@v1.3.1 21 | with: 22 | types: "fix,feat,chore" 23 | scopes: "wren-ai-service" -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wren-engine"] 2 | path = wren-engine 3 | url = git@github.com:Canner/wren-engine.git 4 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | ## Reporting a Vulnerability 3 | 4 | If you believe you have found a security vulnerability in any Canner-owned repository, please report it to us through coordinated disclosure. 5 | 6 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 7 | 8 | Instead, please send an email to contact[@]cannerdata.com. 9 | 10 | Please include as much of the information listed below as you can to help us better understand and resolve the issue: 11 | 12 | * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) 13 | * Full paths of source file(s) related to the manifestation of the issue 14 | * The location of the affected source code (tag/branch/commit or direct URL) 15 | * Any special configuration required to reproduce the issue 16 | * Step-by-step instructions to reproduce the issue 17 | * Proof-of-concept or exploit code (if possible) 18 | * Impact of the issue, including how an attacker might exploit the issue 19 | 20 | This information will help us triage your report more quickly. 21 | -------------------------------------------------------------------------------- /deployment/README.md: -------------------------------------------------------------------------------- 1 | # Various deployemnt starategies of the app 2 | 3 | - [x] [Docker](../docker/) 4 | - [x] [Kubernetes: Kustomizations](./kustomizations/) -------------------------------------------------------------------------------- /deployment/kustomizations/.gitignore: -------------------------------------------------------------------------------- 1 | *.kustomized.yaml 2 | charts/* -------------------------------------------------------------------------------- /deployment/kustomizations/base/deploy-wren-ibis-server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: wren-ibis-server 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: wren-ibis 10 | template: 11 | metadata: 12 | labels: 13 | app: wren-ibis 14 | spec: 15 | containers: 16 | - name: wren-ibis 17 | image: ghcr.io/canner/wren-engine-ibis:0.5.0 18 | env: 19 | - name: WREN_ENGINE_ENDPOINT 20 | valueFrom: 21 | configMapKeyRef: 22 | name: wren-config 23 | key: WREN_ENGINE_ENDPOINT 24 | - name: LOGGING_LEVEL 25 | valueFrom: 26 | configMapKeyRef: 27 | name: wren-config 28 | key: LOGGING_LEVEL 29 | ports: 30 | - containerPort: 8000 31 | -------------------------------------------------------------------------------- /deployment/kustomizations/base/pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: wren-data-pvc 5 | spec: 6 | accessModes: 7 | - ReadWriteOnce 8 | resources: 9 | requests: 10 | storage: 8Gi 11 | # storageClassName: vsphere-retain 12 | -------------------------------------------------------------------------------- /deployment/kustomizations/examples/.gitignore: -------------------------------------------------------------------------------- 1 | secret.yaml -------------------------------------------------------------------------------- /deployment/kustomizations/examples/certificate-qdrant_example.yaml: -------------------------------------------------------------------------------- 1 | # To generate valid certificates into qdrant-ai.myhost.net-tls 2 | # Dependancy: install https://cert-manager.io 3 | apiVersion: cert-manager.io/v1 4 | kind: Certificate 5 | metadata: 6 | name: qdrant-ai.myhost.net 7 | spec: 8 | dnsNames: 9 | - qdrant-ai.myhost.net 10 | issuerRef: 11 | group: cert-manager.io 12 | kind: ClusterIssuer 13 | #### Replace with the name of your issuer 14 | name: myhost.net-prod 15 | secretName: qdrant-ai.myhost.net-tls 16 | -------------------------------------------------------------------------------- /deployment/kustomizations/examples/certificate-wren_example.yaml: -------------------------------------------------------------------------------- 1 | # To generate valid certificates into wren-ui.myhost.net-tls 2 | # Dependancy: install https://cert-manager.io 3 | apiVersion: cert-manager.io/v1 4 | kind: Certificate 5 | metadata: 6 | name: wren-ui.myhost.net 7 | spec: 8 | dnsNames: 9 | - wren-ui.myhost.net 10 | issuerRef: 11 | group: cert-manager.io 12 | kind: ClusterIssuer 13 | ### Replace with the name of your issuer, otherwise the secret will be produce randoom name and the ingress will not work. 14 | name: myhost.net-prod 15 | ### Your ingress will be looking for this exact name: 16 | secretName: wren-ui.myhost.net-tls 17 | -------------------------------------------------------------------------------- /deployment/kustomizations/helm-values_postgresql_15.yaml: -------------------------------------------------------------------------------- 1 | auth: 2 | secretKeys: 3 | adminPasswordKey: postgres-password 4 | existingSecret: wrenai-postgresql 5 | #creates admin_ui database 6 | global: 7 | postgresql: 8 | auth: 9 | database: admin_ui -------------------------------------------------------------------------------- /deployment/kustomizations/patches/README.md: -------------------------------------------------------------------------------- 1 | # Example of usefull Patches for Kustomization 2 | 3 | Patches from this folder allows to utilize the official unmodified deployment/kustomization dirrectly from the repo as a base layer for your kustomization. And then add patches to update some values. This is usefull for your GitOps and can be combined with tools such as ArgoCD and FluxCD. 4 | 5 | Patch ConfigMap, and Service if needed. 6 | Remove Certificate and Ingress if not needed. -------------------------------------------------------------------------------- /deployment/kustomizations/patches/cm.yaml: -------------------------------------------------------------------------------- 1 | - op: replace 2 | path: /data 3 | value: 4 | # Wren Engine Service Port 5 | WREN_ENGINE_PORT: "8080" 6 | # Wren AI Service Port 7 | WREN_AI_SERVICE_PORT: "5555" 8 | 9 | #Release version used by wren ui https://github.com/Canner/WrenAI/blob/main/docker/docker-compose.yaml#L85-L88 10 | WREN_PRODUCT_VERSION: "0.12.0" 11 | #fix: 12 | WREN_ENGINE_VERSION: "0.12.3" 13 | WREN_AI_SERVICE_VERSION: "0.12.1" 14 | #fix: 15 | WREN_UI_VERSION: "0.17.6" 16 | 17 | # OpenAI 18 | GENERATION_MODEL: "gpt-4o-mini" 19 | 20 | # Telemetry 21 | POSTHOG_HOST: "https://app.posthog.com" 22 | TELEMETRY_ENABLED: "false" 23 | 24 | # service endpoints of AI service & engine service 25 | WREN_ENGINE_ENDPOINT: "http://wren-engine-svc:8080" 26 | #fix: 27 | WREN_AI_ENDPOINT: "http://wren-ai-service:5555" 28 | 29 | # "pg" for postgres as application database. 30 | #fix 31 | DB_TYPE: pg 32 | 33 | DATA_PATH: "/app/data" 34 | 35 | ### if DB_TYPE = "postgres" you must provide PG_URL string in the *Secret* manifest file (deployment/kustomizations/examples/secret-wren_example.yaml) to connect to postgres 36 | -------------------------------------------------------------------------------- /deployment/kustomizations/patches/rm-certificate.yaml: -------------------------------------------------------------------------------- 1 | $patch: delete 2 | apiVersion: v1 3 | kind: Certificate 4 | metadata: 5 | name: wren-ui.myhost.net -------------------------------------------------------------------------------- /deployment/kustomizations/patches/rm-ingress.yaml: -------------------------------------------------------------------------------- 1 | $patch: delete 2 | apiVersion: networking.k8s.io/v1 3 | kind: Ingress 4 | metadata: 5 | name: wren-ui-ingress 6 | annotations: 7 | external-dns.alpha.kubernetes.io/target: ingress1.myhost.net -------------------------------------------------------------------------------- /deployment/kustomizations/patches/service.yaml: -------------------------------------------------------------------------------- 1 | - op: replace 2 | path: /spec/ipFamilies 3 | value: 4 | - IPv6 5 | - IPv4 6 | 7 | - op: replace 8 | path: /spec/type 9 | value: 10 | LoadBalancer 11 | 12 | - op: replace 13 | path: /spec/ipFamilyPolicy 14 | value: 15 | # SingleStack 16 | PreferDualStack -------------------------------------------------------------------------------- /docker/.env.example: -------------------------------------------------------------------------------- 1 | COMPOSE_PROJECT_NAME=wrenai 2 | PLATFORM=linux/amd64 3 | 4 | PROJECT_DIR=. 5 | 6 | # service port 7 | WREN_ENGINE_PORT=8080 8 | WREN_ENGINE_SQL_PORT=7432 9 | WREN_AI_SERVICE_PORT=5555 10 | WREN_UI_PORT=3000 11 | IBIS_SERVER_PORT=8000 12 | WREN_UI_ENDPOINT=http://wren-ui:${WREN_UI_PORT} 13 | 14 | # ai service settings 15 | QDRANT_HOST=qdrant 16 | SHOULD_FORCE_DEPLOY=1 17 | 18 | # vendor keys 19 | OPENAI_API_KEY= 20 | 21 | # version 22 | # CHANGE THIS TO THE LATEST VERSION 23 | WREN_PRODUCT_VERSION=0.22.2 24 | WREN_ENGINE_VERSION=0.15.13 25 | WREN_AI_SERVICE_VERSION=0.22.4 26 | IBIS_SERVER_VERSION=0.15.13 27 | WREN_UI_VERSION=0.27.4 28 | WREN_BOOTSTRAP_VERSION=0.1.5 29 | 30 | # user id (uuid v4) 31 | USER_UUID= 32 | 33 | # for other services 34 | POSTHOG_API_KEY=phc_nhF32aj4xHXOZb0oqr2cn4Oy9uiWzz6CCP4KZmRq9aE 35 | POSTHOG_HOST=https://app.posthog.com 36 | TELEMETRY_ENABLED=true 37 | # this is for telemetry to know the model, i think ai-service might be able to provide a endpoint to get the information 38 | GENERATION_MODEL=gpt-4o-mini 39 | LANGFUSE_SECRET_KEY= 40 | LANGFUSE_PUBLIC_KEY= 41 | 42 | # the port exposes to the host 43 | # OPTIONAL: change the port if you have a conflict 44 | HOST_PORT=3000 45 | AI_SERVICE_FORWARD_PORT=5555 46 | 47 | # Wren UI 48 | EXPERIMENTAL_ENGINE_RUST_VERSION=false 49 | -------------------------------------------------------------------------------- /docker/bootstrap/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | 3 | WORKDIR /app 4 | COPY init.sh ./ -------------------------------------------------------------------------------- /docker/bootstrap/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # declare a variable from the environment variable: DATA_PATH 3 | data_path=${DATA_PATH:-"./"} 4 | 5 | # touch a empty config.properties if not exists 6 | # put a content into config.properties if not exists 7 | if [ ! -f ${data_path}/config.properties ]; then 8 | echo "init config.properties" 9 | echo "node.environment=production" >${data_path}/config.properties 10 | fi 11 | 12 | # after the config.properties is created, check if config properties properly set 13 | # if not, then append default values to the config.properties 14 | # check if wren.experimental-enable-dynamic-fields is set, otherwise append it with true 15 | if ! grep -q "wren.experimental-enable-dynamic-fields" ${data_path}/config.properties; then 16 | echo "wren.experimental-enable-dynamic-fields is not set, set it to true" 17 | echo "wren.experimental-enable-dynamic-fields=true" >>${data_path}/config.properties 18 | fi 19 | 20 | # create a folder mdl if not exists 21 | if [ ! -d ${data_path}/mdl ]; then 22 | echo "create mdl folder" 23 | mkdir ${data_path}/mdl 24 | fi 25 | 26 | # put a emtpy sample.json if not exists 27 | if [ ! -f ${data_path}/mdl/sample.json ]; then 28 | echo "init mdl/sample.json" 29 | echo "{\"catalog\": \"test_catalog\", \"schema\": \"test_schema\", \"models\": []}" >${data_path}/mdl/sample.json 30 | fi 31 | -------------------------------------------------------------------------------- /misc/AI-generated-understanding_recommend_questions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/AI-generated-understanding_recommend_questions.png -------------------------------------------------------------------------------- /misc/API_doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/API_doc.png -------------------------------------------------------------------------------- /misc/data_source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/data_source.png -------------------------------------------------------------------------------- /misc/how_wrenai_works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/how_wrenai_works.png -------------------------------------------------------------------------------- /misc/logo_template.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/logo_template.jpg -------------------------------------------------------------------------------- /misc/preview_ask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/preview_ask.png -------------------------------------------------------------------------------- /misc/preview_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/preview_model.png -------------------------------------------------------------------------------- /misc/wren-context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/wren-context.png -------------------------------------------------------------------------------- /misc/wren-excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/wren-excel.png -------------------------------------------------------------------------------- /misc/wren-genbi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/wren-genbi.png -------------------------------------------------------------------------------- /misc/wren-insight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/wren-insight.png -------------------------------------------------------------------------------- /misc/wren-lang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/wren-lang.png -------------------------------------------------------------------------------- /misc/wren-modeling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/wren-modeling.png -------------------------------------------------------------------------------- /misc/wren-visual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/wren-visual.png -------------------------------------------------------------------------------- /misc/wren_workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/wren_workflow.png -------------------------------------------------------------------------------- /misc/wrenai_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/wrenai_logo.png -------------------------------------------------------------------------------- /misc/wrenai_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/wrenai_logo_white.png -------------------------------------------------------------------------------- /misc/wrenai_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/wrenai_view.png -------------------------------------------------------------------------------- /misc/wrenai_vision.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/misc/wrenai_vision.png -------------------------------------------------------------------------------- /wren-ai-service/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !src 3 | !entrypoint.sh 4 | !pyproject.toml 5 | src/eval -------------------------------------------------------------------------------- /wren-ai-service/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | # Ruff version. 4 | rev: v0.2.2 5 | hooks: 6 | # Run the linter. 7 | - id: ruff 8 | args: [ --fix ] 9 | # Run the formatter. 10 | - id: ruff-format 11 | -------------------------------------------------------------------------------- /wren-ai-service/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # reference: https://medium.com/@albertazzir/blazing-fast-python-docker-builds-with-poetry-a78a66f5aed0 2 | FROM python:3.12.0-bookworm as builder 3 | 4 | RUN pip install poetry==1.8.3 5 | 6 | ENV POETRY_NO_INTERACTION=1 \ 7 | POETRY_VIRTUALENVS_IN_PROJECT=1 \ 8 | POETRY_VIRTUALENVS_CREATE=1 \ 9 | POETRY_CACHE_DIR=/tmp/poetry_cache 10 | 11 | WORKDIR /app 12 | 13 | COPY pyproject.toml ./ 14 | 15 | RUN poetry install --without dev,eval,test --no-root && rm -rf $POETRY_CACHE_DIR 16 | 17 | FROM python:3.12.0-slim-bookworm as runtime 18 | 19 | RUN apt-get update && apt install -y netcat-traditional 20 | 21 | ENV VIRTUAL_ENV=/app/.venv \ 22 | PATH="/app/.venv/bin:$PATH" 23 | 24 | COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV} 25 | 26 | COPY src src 27 | COPY entrypoint.sh /app/entrypoint.sh 28 | COPY pyproject.toml pyproject.toml 29 | RUN chmod +x /app/entrypoint.sh 30 | 31 | ENTRYPOINT [ "/app/entrypoint.sh" ] -------------------------------------------------------------------------------- /wren-ai-service/docs/config_examples/README.md: -------------------------------------------------------------------------------- 1 | # MUST READ!!! 2 | 3 | Since these config files are examples, so **please carefully read the file and comments inside**. Try to understand the purpose of each section and parameter, **don't simply copy and paste the content of these config files into your own config file. It will not work.** For more detailed information to the configurations, please [read this file](../configuration.md). 4 | 5 | We also definitely welcome your contribution to add config files for other LLM providers. 6 | -------------------------------------------------------------------------------- /wren-ai-service/docs/imgs/shallow_trace_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/docs/imgs/shallow_trace_example.png -------------------------------------------------------------------------------- /wren-ai-service/eval/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | config.yaml -------------------------------------------------------------------------------- /wren-ai-service/eval/__init__.py: -------------------------------------------------------------------------------- 1 | from pydantic import Field, SecretStr 2 | 3 | from src.config import Settings 4 | 5 | 6 | class EvalSettings(Settings): 7 | langfuse_project_id: str = "" 8 | batch_size: int = 4 9 | batch_interval: int = 1 10 | datasource: str = "bigquery" 11 | config_path: str = "eval/config.yaml" 12 | openai_api_key: SecretStr = Field(alias="OPENAI_API_KEY") 13 | allow_sql_samples: bool = True 14 | allow_instructions: bool = True 15 | allow_sql_functions: bool = True 16 | db_path_for_duckdb: str = "" 17 | 18 | # BigQuery 19 | bigquery_project_id: str = Field(default="") 20 | bigquery_dataset_id: str = Field(default="") 21 | bigquery_credentials: SecretStr = Field(default="") 22 | 23 | @property 24 | def langfuse_url(self) -> str: 25 | if not self.langfuse_project_id: 26 | return "" 27 | return f"{self.langfuse_host.rstrip('/')}/project/{self.langfuse_project_id}" 28 | 29 | def get_openai_api_key(self) -> str: 30 | return self.openai_api_key.get_secret_value() 31 | 32 | @property 33 | def bigquery_info(self) -> dict: 34 | return { 35 | "project_id": self.bigquery_project_id, 36 | "dataset_id": self.bigquery_dataset_id, 37 | "credentials": self.bigquery_credentials.get_secret_value(), 38 | } 39 | -------------------------------------------------------------------------------- /wren-ai-service/eval/add_samples_to_toml.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | import tomlkit 4 | 5 | from eval.utils import ( 6 | get_next_few_items_circular, 7 | ) 8 | 9 | if __name__ == "__main__": 10 | parser = argparse.ArgumentParser() 11 | parser.add_argument("--toml", type=str, help="The toml file name", required=True) 12 | args = parser.parse_args() 13 | 14 | if args.toml: 15 | # read toml 16 | with open(f"eval/dataset/{args.toml}", "r") as f: 17 | doc = tomlkit.parse(f.read()) 18 | 19 | # get the list of question-sql pairs for generating sample values 20 | ground_truth_list = [ 21 | {"question": element["question"], "sql": element["sql"]} 22 | for element in doc["eval_dataset"] 23 | ] 24 | 25 | # utilize utils.get_next_few_items_circular, put n samples in the eval dataset 26 | new_dataset = [] 27 | for i, element in enumerate(doc["eval_dataset"]): 28 | samples = get_next_few_items_circular(ground_truth_list, i) 29 | element["samples"] = samples 30 | new_dataset.append(element) 31 | 32 | # write toml 33 | doc["eval_dataset"] = new_dataset 34 | 35 | with open(f"eval/dataset/added_samples_{args.toml}", "w") as f: 36 | f.write(tomlkit.dumps(doc, sort_keys=True)) 37 | -------------------------------------------------------------------------------- /wren-ai-service/eval/data_curation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/eval/data_curation/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/eval/dataset/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /wren-ai-service/eval/dspy_modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/eval/dspy_modules/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/eval/llm_trace_analysis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/eval/llm_trace_analysis/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/eval/metrics/__init__.py: -------------------------------------------------------------------------------- 1 | from .accuracy import AccuracyMetric, AccuracyMultiCandidateMetric 2 | from .answer_relevancy import AnswerRelevancyMetric 3 | from .context_precision import ContextualPrecisionMetric 4 | from .context_recall import ContextualRecallMetric 5 | from .context_relevancy import ContextualRelevancyMetric 6 | from .faithfulness import FaithfulnessMetric 7 | from .llm import ( 8 | QuestionToReasoningJudge, 9 | ReasoningToSqlJudge, 10 | SqlSemanticsJudge, 11 | ) 12 | from .spider.exact_match import ExactMatchAccuracy 13 | from .spider.exec_match import ExecutionAccuracy 14 | 15 | __all__ = [ 16 | "AccuracyMetric", 17 | "AccuracyMultiCandidateMetric", 18 | "AnswerRelevancyMetric", 19 | "ContextualPrecisionMetric", 20 | "ContextualRecallMetric", 21 | "ContextualRelevancyMetric", 22 | "FaithfulnessMetric", 23 | "ExactMatchAccuracy", 24 | "ExecutionAccuracy", 25 | "QuestionToReasoningJudge", 26 | "ReasoningToSqlJudge", 27 | "SqlSemanticsJudge", 28 | ] 29 | -------------------------------------------------------------------------------- /wren-ai-service/eval/metrics/answer_relevancy.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from deepeval.metrics import BaseMetric 4 | from deepeval.test_case import LLMTestCase 5 | 6 | from eval.utils import get_contexts_from_sql 7 | 8 | 9 | class AnswerRelevancyMetric(BaseMetric): 10 | def __init__(self, engine_info: dict): 11 | self.threshold = 0 12 | self.score = 0 13 | self.engine_info = engine_info 14 | 15 | def measure(self, test_case: LLMTestCase): 16 | return asyncio.run(self.a_measure(test_case)) 17 | 18 | async def a_measure(self, test_case: LLMTestCase, *args, **kwargs): 19 | actual_units = await get_contexts_from_sql( 20 | sql=test_case.actual_output, **self.engine_info 21 | ) 22 | 23 | expected_units = await get_contexts_from_sql( 24 | sql=test_case.expected_output, **self.engine_info 25 | ) 26 | 27 | intersection = set(actual_units) & set(expected_units) 28 | self.score = len(intersection) / len(actual_units) 29 | 30 | self.success = self.score >= self.threshold 31 | return self.score 32 | 33 | def is_successful(self): 34 | return self.success 35 | 36 | @property 37 | def __name__(self): 38 | return "AnswerRelevancy(column-based)" 39 | -------------------------------------------------------------------------------- /wren-ai-service/eval/metrics/context_recall.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from deepeval.metrics import BaseMetric 4 | from deepeval.test_case import LLMTestCase 5 | 6 | from eval.utils import get_contexts_from_sql 7 | 8 | 9 | class ContextualRecallMetric(BaseMetric): 10 | def __init__(self, engine_info: dict): 11 | self.threshold = 0 12 | self.score = 0 13 | self.engine_info = engine_info 14 | 15 | def measure(self, test_case: LLMTestCase): 16 | return asyncio.run(self.a_measure(test_case)) 17 | 18 | async def a_measure(self, test_case: LLMTestCase, *args, **kwargs): 19 | expected_units = await get_contexts_from_sql( 20 | sql=test_case.expected_output, **self.engine_info 21 | ) 22 | 23 | intersection = set(test_case.retrieval_context) & set(expected_units) 24 | self.score = len(intersection) / len(expected_units) 25 | 26 | self.success = self.score >= self.threshold 27 | return self.score 28 | 29 | def is_successful(self): 30 | return self.success 31 | 32 | @property 33 | def __name__(self): 34 | return "ContextualRecall(column-based)" 35 | -------------------------------------------------------------------------------- /wren-ai-service/eval/metrics/context_relevancy.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from deepeval.metrics import BaseMetric 4 | from deepeval.test_case import LLMTestCase 5 | 6 | 7 | class ContextualRelevancyMetric(BaseMetric): 8 | def __init__(self): 9 | self.threshold = 0 10 | self.score = 0 11 | 12 | def measure(self, test_case: LLMTestCase): 13 | return asyncio.run(self.a_measure(test_case)) 14 | 15 | async def a_measure(self, test_case: LLMTestCase, *args, **kwargs): 16 | intersection = set(test_case.retrieval_context) & set(test_case.context) 17 | self.score = len(intersection) / len(test_case.retrieval_context) 18 | 19 | self.success = self.score >= self.threshold 20 | return self.score 21 | 22 | def is_successful(self): 23 | return self.success 24 | 25 | @property 26 | def __name__(self): 27 | return "ContextualRelevancy(column-based)" 28 | -------------------------------------------------------------------------------- /wren-ai-service/eval/metrics/faithfulness.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from deepeval.metrics import BaseMetric 4 | from deepeval.test_case import LLMTestCase 5 | 6 | from eval.utils import get_contexts_from_sql 7 | 8 | 9 | class FaithfulnessMetric(BaseMetric): 10 | def __init__(self, engine_info: dict): 11 | self.threshold = 0 12 | self.score = 0 13 | self.engine_info = engine_info 14 | 15 | def measure(self, test_case: LLMTestCase): 16 | return asyncio.run(self.a_measure(test_case)) 17 | 18 | async def a_measure(self, test_case: LLMTestCase, *args, **kwargs): 19 | actual_units = await get_contexts_from_sql( 20 | sql=test_case.actual_output, **self.engine_info 21 | ) 22 | intersection = set(actual_units) & set(test_case.retrieval_context) 23 | self.score = len(intersection) / len(actual_units) 24 | 25 | self.success = self.score >= self.threshold 26 | return self.score 27 | 28 | def is_successful(self): 29 | return self.success 30 | 31 | @property 32 | def __name__(self): 33 | return "Faithfulness(column-based)" 34 | -------------------------------------------------------------------------------- /wren-ai-service/eval/optimized/.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | -------------------------------------------------------------------------------- /wren-ai-service/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/src/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/src/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/src/core/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/src/core/pipeline.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from abc import ABCMeta, abstractmethod 3 | from collections.abc import Mapping 4 | from dataclasses import dataclass 5 | from typing import Any, Dict 6 | 7 | from hamilton.async_driver import AsyncDriver 8 | from hamilton.driver import Driver 9 | from haystack import Pipeline 10 | 11 | from src.core.engine import Engine 12 | from src.core.provider import DocumentStoreProvider, EmbedderProvider, LLMProvider 13 | 14 | 15 | class BasicPipeline(metaclass=ABCMeta): 16 | def __init__(self, pipe: Pipeline | AsyncDriver | Driver): 17 | self._pipe = pipe 18 | 19 | @abstractmethod 20 | def run(self, *args, **kwargs) -> Dict[str, Any]: 21 | ... 22 | 23 | 24 | def async_validate(task: callable): 25 | result = asyncio.run(task()) 26 | print(result) 27 | return result 28 | 29 | 30 | @dataclass 31 | class PipelineComponent(Mapping): 32 | llm_provider: LLMProvider = None 33 | embedder_provider: EmbedderProvider = None 34 | document_store_provider: DocumentStoreProvider = None 35 | engine: Engine = None 36 | 37 | def __getitem__(self, key): 38 | return getattr(self, key) 39 | 40 | def __iter__(self): 41 | return iter(self.__dict__) 42 | 43 | def __len__(self): 44 | return len(self.__dict__) 45 | -------------------------------------------------------------------------------- /wren-ai-service/src/core/provider.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | from haystack.document_stores.types import DocumentStore 4 | 5 | 6 | class LLMProvider(metaclass=ABCMeta): 7 | @abstractmethod 8 | def get_generator(self, *args, **kwargs): 9 | ... 10 | 11 | def get_model(self): 12 | return self._model 13 | 14 | def get_model_kwargs(self): 15 | return self._model_kwargs 16 | 17 | def get_context_window_size(self): 18 | return self._context_window_size 19 | 20 | 21 | class EmbedderProvider(metaclass=ABCMeta): 22 | @abstractmethod 23 | def get_text_embedder(self, *args, **kwargs): 24 | ... 25 | 26 | @abstractmethod 27 | def get_document_embedder(self, *args, **kwargs): 28 | ... 29 | 30 | def get_model(self): 31 | return self._embedding_model 32 | 33 | 34 | class DocumentStoreProvider(metaclass=ABCMeta): 35 | @abstractmethod 36 | def get_store(self, *args, **kwargs) -> DocumentStore: 37 | ... 38 | 39 | @abstractmethod 40 | def get_retriever(self, *args, **kwargs): 41 | ... 42 | -------------------------------------------------------------------------------- /wren-ai-service/src/force_deploy.py: -------------------------------------------------------------------------------- 1 | # This file is only used for OSS, it will force deploy the mdl for the OSS users 2 | # Since we allow users to customize llm and embedding models, which means qdrant collections may need to be recreated 3 | # So, this file automates the process of force deploying the mdl 4 | 5 | import asyncio 6 | import os 7 | from pathlib import Path 8 | 9 | import aiohttp 10 | import backoff 11 | from dotenv import load_dotenv 12 | 13 | if Path(".env.dev").exists(): 14 | load_dotenv(".env.dev", override=True) 15 | 16 | 17 | @backoff.on_exception(backoff.expo, aiohttp.ClientError, max_time=60, max_tries=3) 18 | async def force_deploy(): 19 | async with aiohttp.ClientSession() as session: 20 | async with session.post( 21 | f"{os.getenv("WREN_UI_ENDPOINT", "http://wren-ui:3000")}/api/graphql", 22 | json={ 23 | "query": "mutation Deploy($force: Boolean) { deploy(force: $force) }", 24 | "variables": {"force": True}, 25 | }, 26 | timeout=aiohttp.ClientTimeout(total=60), # 60 seconds 27 | ) as response: 28 | res = await response.json() 29 | print(f"Forcing deployment: {res}") 30 | 31 | 32 | if os.getenv("ENGINE", "wren_ui") == "wren_ui": 33 | asyncio.run(force_deploy()) 34 | -------------------------------------------------------------------------------- /wren-ai-service/src/pipelines/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/src/pipelines/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/src/pipelines/retrieval/__init__.py: -------------------------------------------------------------------------------- 1 | from .db_schema_retrieval import DbSchemaRetrieval 2 | from .historical_question_retrieval import HistoricalQuestionRetrieval 3 | from .instructions import Instructions 4 | from .preprocess_sql_data import PreprocessSqlData 5 | from .sql_executor import SQLExecutor 6 | from .sql_functions import SqlFunctions 7 | from .sql_pairs_retrieval import SqlPairsRetrieval 8 | 9 | __all__ = [ 10 | "HistoricalQuestionRetrieval", 11 | "PreprocessSqlData", 12 | "DbSchemaRetrieval", 13 | "SQLExecutor", 14 | "SqlPairsRetrieval", 15 | "Instructions", 16 | "SqlFunctions", 17 | ] 18 | -------------------------------------------------------------------------------- /wren-ai-service/src/providers/document_store/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/src/providers/document_store/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/src/providers/embedder/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/src/providers/embedder/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/src/providers/engine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/src/providers/engine/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/src/web/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/src/web/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/src/web/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/src/web/v1/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/src/web/v1/routers/__init__.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | from src.web.v1.routers import ( 4 | ask, 5 | chart, 6 | chart_adjustment, 7 | instructions, 8 | question_recommendation, 9 | relationship_recommendation, 10 | semantics_description, 11 | semantics_preparation, 12 | sql_answers, 13 | sql_corrections, 14 | sql_pairs, 15 | sql_question, 16 | ) 17 | 18 | router = APIRouter() 19 | router.include_router(ask.router) 20 | router.include_router(question_recommendation.router) 21 | router.include_router(relationship_recommendation.router) 22 | router.include_router(semantics_description.router) 23 | router.include_router(semantics_preparation.router) 24 | router.include_router(sql_answers.router) 25 | router.include_router(chart.router) 26 | router.include_router(chart_adjustment.router) 27 | router.include_router(sql_pairs.router) 28 | router.include_router(sql_question.router) 29 | router.include_router(instructions.router) 30 | router.include_router(sql_corrections.router) 31 | -------------------------------------------------------------------------------- /wren-ai-service/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/tests/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/tests/data/mock_pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | version = "0.8.0-mock" 3 | -------------------------------------------------------------------------------- /wren-ai-service/tests/data/pairs.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": [ 3 | { 4 | "id": "1", 5 | "question": "What is the book?", 6 | "sql": "SELECT * FROM book" 7 | }, 8 | { 9 | "id": "2", 10 | "question": "What is the author?", 11 | "sql": "SELECT * FROM author" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /wren-ai-service/tests/locust/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/tests/locust/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/tests/locust/config_users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "user_class_name": "IndexingUser", 4 | "fixed_count": 0 5 | }, 6 | { 7 | "user_class_name": "AskUser", 8 | "fixed_count": 0 9 | }, 10 | { 11 | "user_class_name": "AskDetailsUser", 12 | "fixed_count": 0 13 | }, 14 | { 15 | "user_class_name": "DummyUser", 16 | "fixed_count": 0 17 | }, 18 | { 19 | "user_class_name": "DummyAskUser", 20 | "fixed_count": 100 21 | } 22 | ] -------------------------------------------------------------------------------- /wren-ai-service/tests/locust/locust.conf: -------------------------------------------------------------------------------- 1 | locustfile = tests/locust/locustfile.py 2 | headless = true 3 | host = http://localhost:5556 4 | users = 100 5 | spawn-rate = 10 6 | run-time = 60s 7 | config-users = tests/locust/config_users.json -------------------------------------------------------------------------------- /wren-ai-service/tests/locust/locust_script.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import time 4 | from pathlib import Path 5 | 6 | from src.utils import load_env_vars 7 | 8 | load_env_vars() 9 | filename = f"locust_report_{time.strftime("%Y%m%d_%H%M%S")}" 10 | 11 | if not Path("./outputs/locust").exists(): 12 | Path("./outputs/locust").mkdir(parents=True, exist_ok=True) 13 | 14 | os.system( 15 | f""" 16 | poetry run locust \ 17 | --config tests/locust/locust.conf \ 18 | --logfile outputs/locust/{filename}.log \ 19 | --html outputs/locust/{filename}.html \ 20 | --json > outputs/locust/{filename}.json 21 | """ 22 | ) 23 | 24 | with open(f"./outputs/locust/{filename}.json", "r") as f: 25 | test_results = json.load(f) 26 | 27 | formatted = { 28 | "llm provider": os.getenv("LLM_PROVIDER"), 29 | "generation model": os.getenv("GENERATION_MODEL"), 30 | "embedding model": os.getenv("EMBEDDING_MODEL"), 31 | "locustfile": "tests/locust/locustfile.py", 32 | "test results": test_results, 33 | } 34 | with open(f"./outputs/locust/{filename}.json", "w") as f: 35 | json.dump(formatted, f, indent=2) 36 | 37 | print(f"get the test results in {filename}.json and {filename}.html") 38 | -------------------------------------------------------------------------------- /wren-ai-service/tests/pytest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/tests/pytest/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/tests/pytest/eval/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/tests/pytest/eval/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/tests/pytest/pipelines/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/tests/pytest/pipelines/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/tests/pytest/pipelines/generation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/tests/pytest/pipelines/generation/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/tests/pytest/pipelines/indexing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/tests/pytest/pipelines/indexing/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/tests/pytest/pipelines/retrieval/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/tests/pytest/pipelines/retrieval/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/tests/pytest/providers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/tests/pytest/providers/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/tests/pytest/providers/test_loader.py: -------------------------------------------------------------------------------- 1 | from src.providers import loader 2 | 3 | 4 | def test_import_mods(): 5 | loader.import_mods("src.providers") 6 | assert len(loader.PROVIDERS) == 6 7 | 8 | 9 | def test_get_provider(): 10 | loader.import_mods("src.providers") 11 | 12 | # llm provider 13 | provider = loader.get_provider("litellm_llm") 14 | assert provider.__name__ == "LitellmLLMProvider" 15 | 16 | # embedder provider 17 | provider = loader.get_provider("litellm_embedder") 18 | assert provider.__name__ == "LitellmEmbedderProvider" 19 | 20 | # document store provider 21 | provider = loader.get_provider("qdrant") 22 | assert provider.__name__ == "QdrantProvider" 23 | 24 | # engine provider 25 | provider = loader.get_provider("wren_ui") 26 | assert provider.__name__ == "WrenUI" 27 | 28 | provider = loader.get_provider("wren_ibis") 29 | assert provider.__name__ == "WrenIbis" 30 | 31 | provider = loader.get_provider("wren_engine") 32 | assert provider.__name__ == "WrenEngine" 33 | -------------------------------------------------------------------------------- /wren-ai-service/tests/pytest/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ai-service/tests/pytest/services/__init__.py -------------------------------------------------------------------------------- /wren-ai-service/tools/.env.example: -------------------------------------------------------------------------------- 1 | bigquery.project-id= 2 | bigquery.dataset-id= 3 | bigquery.credentials-key= 4 | postgres.host= 5 | postgres.port= 6 | postgres.database= 7 | postgres.user= 8 | postgres.password= -------------------------------------------------------------------------------- /wren-ai-service/tools/config/.env.dev.example: -------------------------------------------------------------------------------- 1 | # vendor keys 2 | OPENAI_API_KEY= 3 | 4 | # langfuse key 5 | LANGFUSE_SECRET_KEY= 6 | LANGFUSE_PUBLIC_KEY= 7 | 8 | # for evaluation 9 | LANGFUSE_PROJECT_ID= 10 | -------------------------------------------------------------------------------- /wren-ai-service/tools/dev/.env: -------------------------------------------------------------------------------- 1 | COMPOSE_PROJECT_NAME=wren 2 | PLATFORM=linux/amd64 3 | 4 | # service port 5 | WREN_ENGINE_PORT=8080 6 | WREN_ENGINE_SQL_PORT=7432 7 | WREN_AI_SERVICE_PORT=5556 8 | WREN_UI_PORT=3000 9 | IBIS_SERVER_PORT=8000 10 | 11 | # version 12 | # CHANGE THIS TO THE LATEST VERSION 13 | WREN_PRODUCT_VERSION=development 14 | WREN_ENGINE_VERSION=latest 15 | WREN_AI_SERVICE_VERSION=latest 16 | WREN_UI_VERSION=latest 17 | IBIS_SERVER_VERSION=latest 18 | WREN_BOOTSTRAP_VERSION=latest 19 | 20 | LAUNCH_CLI_PATH=./launch-cli.sh 21 | 22 | # user id (uuid v4) 23 | USER_UUID= 24 | 25 | # for other services 26 | POSTHOG_API_KEY=phc_nhF32aj4xHXOZb0oqr2cn4Oy9uiWzz6CCP4KZmRq9aE 27 | POSTHOG_HOST=https://app.posthog.com 28 | TELEMETRY_ENABLED=false 29 | -------------------------------------------------------------------------------- /wren-ai-service/tools/dev/config.properties.example: -------------------------------------------------------------------------------- 1 | node.environment=production 2 | wren.directory=/usr/src/app/etc/mdl 3 | wren.experimental-enable-dynamic-fields=true 4 | wren.datasource.type=duckdb 5 | duckdb.connector.init-sql-path=/usr/src/app/etc/duckdb/init.sql 6 | duckdb.storage.access-key= 7 | duckdb.storage.secret-key= 8 | -------------------------------------------------------------------------------- /wren-ai-service/tools/mdl_to_str.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | import orjson 4 | 5 | 6 | def to_str(mdl: dict) -> str: 7 | """Convert MDL dictionary to string format with proper escaping. 8 | 9 | Args: 10 | mdl (dict): The MDL dictionary containing schema information 11 | 12 | Returns: 13 | str: Properly escaped string representation of the MDL 14 | 15 | Example: 16 | mdl = { 17 | "schema": "public", 18 | "models": [ 19 | {"name": "table1"} 20 | ] 21 | } 22 | result = to_str(mdl) 23 | # Returns escaped string representation 24 | """ 25 | 26 | mdl_str = orjson.dumps(mdl).decode("utf-8") 27 | 28 | mdl_str = mdl_str.replace("\\", "\\\\") # Escape backslashes 29 | mdl_str = mdl_str.replace('"', '\\"') # Escape double quotes 30 | 31 | return mdl_str 32 | 33 | 34 | def _args(): 35 | parser = argparse.ArgumentParser( 36 | description="Convert MDL JSON file to escaped string format" 37 | ) 38 | parser.add_argument("-p", "--path", help="Path to input MDL JSON file") 39 | return parser.parse_args() 40 | 41 | 42 | if __name__ == "__main__": 43 | args = _args() 44 | mdl = orjson.loads(open(args.path).read()) 45 | print(to_str(mdl)) 46 | -------------------------------------------------------------------------------- /wren-launcher/Makefile: -------------------------------------------------------------------------------- 1 | BINARY_NAME=wren-launcher 2 | 3 | build: 4 | env GOARCH=amd64 GOOS=darwin CGO_ENABLED=1 go build -o dist/${BINARY_NAME}-darwin main.go 5 | env GOARCH=arm64 GOOS=darwin CGO_ENABLED=1 go build -o dist/${BINARY_NAME}-darwin-arm64 main.go 6 | env GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build -o dist/${BINARY_NAME}-linux main.go 7 | env GOARCH=arm64 GOOS=linux CGO_ENABLED=0 go build -o dist/${BINARY_NAME}-linux-arm64 main.go 8 | env GOARCH=amd64 GOOS=windows CGO_ENABLED=0 go build -o dist/${BINARY_NAME}-windows.exe main.go 9 | cd ./dist; chmod +x ${BINARY_NAME}-darwin && chmod +x ${BINARY_NAME}-darwin-arm64 && chmod +x ${BINARY_NAME}-linux && chmod +x ${BINARY_NAME}-linux-arm64 \ 10 | && tar zcvf ${BINARY_NAME}-darwin.tar.gz ${BINARY_NAME}-darwin \ 11 | && tar zcvf ${BINARY_NAME}-darwin-arm64.tar.gz ${BINARY_NAME}-darwin-arm64 \ 12 | && tar zcvf ${BINARY_NAME}-linux.tar.gz ${BINARY_NAME}-linux \ 13 | && tar zcvf ${BINARY_NAME}-linux-arm64.tar.gz ${BINARY_NAME}-linux-arm64 \ 14 | && zip ${BINARY_NAME}-windows.zip ${BINARY_NAME}-windows.exe 15 | 16 | clean: 17 | go clean 18 | rm -rf dist 19 | 20 | rebuild: clean build 21 | -------------------------------------------------------------------------------- /wren-launcher/README.md: -------------------------------------------------------------------------------- 1 | ## How to build 2 | ```bash 3 | # mac 4 | go build main.go 5 | # windows 6 | env GOOS=windows GOARCH=amd64 go build main.go 7 | ``` 8 | 9 | ## How to update dependencies 10 | 11 | ```bash 12 | # Update a single dependency 13 | go get example.com/some/package@latest 14 | 15 | # Update all dependencies 16 | go get -u ./... 17 | 18 | # Clean up and ensure the module files are correct 19 | go mod tidy 20 | 21 | # Verify the updates 22 | go test ./... 23 | 24 | ``` 25 | -------------------------------------------------------------------------------- /wren-launcher/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/Canner/WrenAI/wren-launcher/commands" 7 | "github.com/Canner/WrenAI/wren-launcher/config" 8 | "github.com/pterm/pterm" 9 | ) 10 | 11 | var disableTelemetry bool 12 | 13 | func main() { 14 | config.InitFlags() 15 | 16 | // help flag 17 | help := flag.Bool("h", false, "Display help") 18 | flag.Parse() 19 | 20 | if *help { 21 | pterm.Info.Println("Usage of Wren launcher:") 22 | flag.PrintDefaults() 23 | return 24 | } 25 | 26 | commands.Launch() 27 | } 28 | -------------------------------------------------------------------------------- /wren-launcher/utils/docker_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFindWrenUIContainer(t *testing.T) { 8 | container, error := findWrenUIContainer() 9 | if error != nil { 10 | t.Errorf("Error: %v", error) 11 | } 12 | 13 | t.Logf("Container ID: %s", container.ID) 14 | t.Logf("Container Name: %s", container.Names[0]) 15 | for _, port := range container.Ports { 16 | t.Logf("Container IP: %s", port.IP) 17 | t.Logf("Container Port Type: %s", port.Type) 18 | t.Logf("Container PublicPort: %d", port.PublicPort) 19 | t.Logf("Container PrivatePort: %d", port.PrivatePort) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /wren-launcher/utils/network.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/pterm/pterm" 8 | ) 9 | 10 | func ifPortUsed(port int) bool { 11 | // listen on port to check if it's used 12 | _, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) 13 | return err != nil 14 | } 15 | 16 | func FindAvailablePort(defaultPort int) int { 17 | // Find an available port 18 | // Start from the default port and increment by 1 19 | // until a port is found that is not in use 20 | for port := defaultPort; port < defaultPort+100; port++ { 21 | pterm.Info.Printf("Checking if port %d is available\n", port) 22 | 23 | if !ifPortUsed(port) { 24 | // Return the port if it's not used 25 | return port 26 | } else if IfPortUsedByWrenUI(port) || IfPortUsedByAIService(port) { 27 | // Return the port if it's used, but used by wrenAI 28 | return port 29 | } 30 | } 31 | 32 | // If no port is available, return 0 33 | return 0 34 | } 35 | -------------------------------------------------------------------------------- /wren-launcher/utils/rc_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestReadWriteRcFile(t *testing.T) { 9 | dir, err := os.MkdirTemp("", "wrenrc") 10 | if err != nil { 11 | t.Errorf("Error: %v", err) 12 | } 13 | defer os.RemoveAll(dir) 14 | 15 | // create a WrenRC struct 16 | w := WrenRC{dir} 17 | // write a key value pair to the rc file 18 | err = w.Set("key", "value", false) 19 | if err != nil { 20 | t.Errorf("Error: %v", err) 21 | } 22 | 23 | // read the value of the key from the rc file 24 | v, err := w.Read("key") 25 | if err != nil { 26 | t.Errorf("Error: %v", err) 27 | } 28 | if v != "value" { 29 | t.Errorf("Expected value: value, got: %s", v) 30 | } 31 | } 32 | 33 | // read the value of a non-existent key from the rc file 34 | func TestReadNonExistentKey(t *testing.T) { 35 | dir, err := os.MkdirTemp("", "wrenrc") 36 | if err != nil { 37 | t.Errorf("Error: %v", err) 38 | } 39 | defer os.RemoveAll(dir) 40 | 41 | // create a WrenRC struct 42 | w := WrenRC{dir} 43 | // read the value of a non-existent key from the rc file 44 | v, err := w.Read("key") 45 | if err != nil { 46 | t.Errorf("Error: %v", err) 47 | } 48 | if v != "" { 49 | t.Errorf("Expected value: \"\", got: %s", v) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /wren-ui/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .dockerignore 3 | node_modules 4 | npm-debug.log 5 | README.md 6 | .next 7 | .git 8 | *.sqlite 9 | *.sqlite3 10 | .env*.local -------------------------------------------------------------------------------- /wren-ui/.env.test: -------------------------------------------------------------------------------- 1 | DB_TYPE=sqlite 2 | SQLITE_FILE=testdb.sqlite3 3 | OTHER_SERVICE_USING_DOCKER=true -------------------------------------------------------------------------------- /wren-ui/.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules -------------------------------------------------------------------------------- /wren-ui/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /wren-ui/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.5.3.cjs 4 | -------------------------------------------------------------------------------- /wren-ui/codegen.yaml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: 3 | [ 4 | 'http://localhost:3000/api/graphql' 5 | ] 6 | generates: 7 | ./src/apollo/client/graphql/__types__.ts: 8 | plugins: 9 | - typescript 10 | - typescript-operations 11 | - typescript-react-apollo 12 | config: 13 | namingConvention: 14 | enumValues: keep 15 | ./: 16 | preset: near-operation-file 17 | presetConfig: 18 | extension: .generated.ts 19 | baseTypesPath: ./src/apollo/client/graphql/__types__.ts 20 | documents: ./src/apollo/client/graphql/!(*.generated).{ts,tsx} 21 | plugins: 22 | - typescript-operations 23 | - typescript-react-apollo 24 | -------------------------------------------------------------------------------- /wren-ui/e2e/commonTests/onboarding.ts: -------------------------------------------------------------------------------- 1 | import { expect } from '@playwright/test'; 2 | 3 | export const setupModels = async ({ page }) => { 4 | await page.goto('/setup/models'); 5 | 6 | // select all models 7 | await page.locator('th').first().click(); 8 | 9 | await page.getByRole('button', { name: 'Next' }).click(); 10 | await expect(page).toHaveURL('/setup/relationships', { timeout: 60000 }); 11 | }; 12 | 13 | export const saveRecommendedRelationships = async ({ page }) => { 14 | await page.goto('/setup/relationships'); 15 | 16 | await page.getByRole('button', { name: 'Finish' }).click(); 17 | await expect(page).toHaveURL('/modeling', { timeout: 60000 }); 18 | }; 19 | -------------------------------------------------------------------------------- /wren-ui/e2e/global.setup.ts: -------------------------------------------------------------------------------- 1 | import { test as setup } from '@playwright/test'; 2 | import * as helper from './helper'; 3 | 4 | setup('create new database', async () => { 5 | console.log('creating new database...'); 6 | // Initialize the database 7 | await helper.migrateDatabase(); 8 | console.log('created successfully.'); 9 | }); 10 | -------------------------------------------------------------------------------- /wren-ui/e2e/global.teardown.ts: -------------------------------------------------------------------------------- 1 | import { test as setup } from '@playwright/test'; 2 | import * as helper from './helper'; 3 | 4 | setup('delete database', async () => { 5 | console.log('deleting test database...'); 6 | // Delete the database 7 | await helper.removeDatabase(); 8 | console.log('deleted successfully.'); 9 | }); 10 | -------------------------------------------------------------------------------- /wren-ui/e2e/specs/connectDuckDB.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | import { getTestConfig } from '../config'; 3 | import * as helper from '../helper'; 4 | import * as onboarding from '../commonTests/onboarding'; 5 | 6 | const testConfig = getTestConfig(); 7 | 8 | test.describe('Test DuckDB data source', () => { 9 | test.beforeAll(async () => { 10 | await helper.resetDatabase(); 11 | }); 12 | 13 | test('Connect DuckDB data source successfully', async ({ page }) => { 14 | await page.goto('/setup/connection'); 15 | 16 | await page.locator('button').filter({ hasText: 'DuckDB' }).click(); 17 | 18 | await page.getByLabel('Display name').click(); 19 | await page.getByLabel('Display name').fill('test-duckdb'); 20 | await page.getByLabel('Initial SQL statements').click(); 21 | await page 22 | .getByLabel('Initial SQL statements') 23 | .fill( 24 | `CREATE TABLE ontime AS FROM read_csv('${testConfig.duckDb.sqlCsvPath}');`, 25 | ); 26 | await page.getByRole('button', { name: 'Next' }).click(); 27 | await expect(page).toHaveURL('/setup/models', { timeout: 60000 }); 28 | }); 29 | 30 | test('Setup all models', onboarding.setupModels); 31 | 32 | test( 33 | 'Save recommended relationships', 34 | onboarding.saveRecommendedRelationships, 35 | ); 36 | }); 37 | -------------------------------------------------------------------------------- /wren-ui/e2e/specs/connectSampleHR.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | import * as helper from '../helper'; 3 | import * as homeHelper from '../commonTests/home'; 4 | import * as modelingHelper from '../commonTests/modeling'; 5 | import { sampleDatasets } from '@/apollo/server/data'; 6 | 7 | const suggestedQuestions = sampleDatasets.hr.questions; 8 | 9 | test.describe('Test HR sample dataset', () => { 10 | test.beforeAll(async () => { 11 | await helper.resetDatabase(); 12 | }); 13 | 14 | test('Starting HR dataset successfully', async ({ page }) => { 15 | await page.goto('/setup/connection'); 16 | await page.getByRole('button', { name: 'Human Resource' }).click(); 17 | await expect(page).toHaveURL('/modeling', { timeout: 60000 }); 18 | }); 19 | 20 | test('Check suggested questions', async ({ page }) => { 21 | await page.goto('/home'); 22 | for (const suggestedQuestion of suggestedQuestions) { 23 | await expect(page.getByText(suggestedQuestion.question)).toBeVisible(); 24 | } 25 | }); 26 | 27 | test('Use suggestion question', async ({ page, baseURL }) => { 28 | // select first suggested question 29 | await homeHelper.askSuggestionQuestionTest({ 30 | page, 31 | suggestedQuestion: suggestedQuestions[1].question, 32 | }); 33 | }); 34 | 35 | test( 36 | 'Check deploy status should be in Synced status', 37 | modelingHelper.checkDeploySynced, 38 | ); 39 | }); 40 | -------------------------------------------------------------------------------- /wren-ui/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | moduleNameMapper: { 6 | '^@server/(.*)$': '/src/apollo/server/$1', 7 | }, 8 | modulePathIgnorePatterns: ['/e2e/'], 9 | }; 10 | -------------------------------------------------------------------------------- /wren-ui/knexfile.js: -------------------------------------------------------------------------------- 1 | // Update with your config settings. 2 | 3 | /** 4 | * @type { Object. } 5 | */ 6 | if (process.env.DB_TYPE === 'pg') { 7 | console.log('Using Postgres'); 8 | module.exports = { 9 | client: 'pg', 10 | connection: process.env.PG_URL, 11 | }; 12 | } else { 13 | console.log('Using SQLite'); 14 | module.exports = { 15 | client: 'better-sqlite3', 16 | connection: process.env.SQLITE_FILE || './db.sqlite3', 17 | useNullAsDefault: true, 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240125083821_create_relation_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.createTable('relation', (table) => { 7 | table.increments('id').comment('ID'); 8 | table.integer('project_id').comment('Reference to project.id'); 9 | table.string('name').comment('relation name').unique(); 10 | table 11 | .string('join_type') 12 | .comment('join type, eg:"ONE_TO_ONE", "ONE_TO_MANY", "MANY_TO_ONE"'); 13 | table 14 | .integer('from_column_id') 15 | .comment('from column id, "{fromColumn} {joinType} {toSideColumn}"'); 16 | table 17 | .integer('to_column_id') 18 | .comment('to column id, "{fromColumn} {joinType} {toSideColumn}"'); 19 | table.timestamps(true, true); 20 | }); 21 | }; 22 | 23 | /** 24 | * @param { import("knex").Knex } knex 25 | * @returns { Promise } 26 | */ 27 | exports.down = function (knex) { 28 | return knex.schema.dropTable('relation'); 29 | }; 30 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240126100753_create_metrics_measure_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | // name string 7 | // expression string 8 | // granularity string, nullable 9 | 10 | return knex.schema.createTable('metric_measure', (table) => { 11 | table.increments('id').comment('ID'); 12 | table.integer('metric_id').comment('Reference to metric ID'); 13 | table.string('name').comment('Measure name'); 14 | table 15 | .text('expression') 16 | .comment('Expression for the measure') 17 | .comment( 18 | 'the expression of measure, eg: "Sum", "Everage", or customize expression', 19 | ); 20 | table 21 | .string('granularity') 22 | .comment( 23 | 'Granularity for the measure, eg: "day", "hour", "minute", "year"', 24 | ) 25 | .nullable(); 26 | table.timestamps(true, true); 27 | }); 28 | }; 29 | 30 | /** 31 | * @param { import("knex").Knex } knex 32 | * @returns { Promise } 33 | */ 34 | exports.down = function (knex) { 35 | return knex.schema.dropTable('metric_measure'); 36 | }; 37 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240129021453_create_view_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.createTable('view', (table) => { 7 | table.increments('id').comment('ID'); 8 | table.integer('project_id').comment('Reference to project.id'); 9 | 10 | // basic info 11 | table.string('name').comment('the view name'); 12 | table.text('statement').comment('the sql statement of this view'); 13 | 14 | // cache setting 15 | table.boolean('cached').comment('view is cached or not'); 16 | table 17 | .string('refresh_time') 18 | .comment( 19 | 'contain a number followed by a time unit (ns, us, ms, s, m, h, d). For example, "2h"', 20 | ) 21 | .nullable(); 22 | 23 | // view properties 24 | table 25 | .text('properties') 26 | .comment( 27 | 'view properties, a json string, the description and displayName should be stored here', 28 | ) 29 | .nullable(); 30 | table.timestamps(true, true); 31 | }); 32 | }; 33 | 34 | /** 35 | * @param { import("knex").Knex } knex 36 | * @returns { Promise } 37 | */ 38 | exports.down = function (knex) { 39 | return knex.schema.dropTable('view'); 40 | }; 41 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240319083758_create_deploy_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.createTable('deploy_log', (table) => { 7 | table.increments('id').comment('ID'); 8 | table.integer('project_id').comment('Reference to project.id'); 9 | 10 | // basic info 11 | table.jsonb('manifest').comment('the deployed manifest'); 12 | table.string('hash').comment('the hash of the manifest'); 13 | 14 | // status 15 | table.string('status').nullable().comment('deploy status'); 16 | table.string('error').nullable().comment('deploy error message'); 17 | 18 | // timestamps 19 | table.timestamps(true, true); 20 | }); 21 | }; 22 | 23 | /** 24 | * @param { import("knex").Knex } knex 25 | * @returns { Promise } 26 | */ 27 | exports.down = function (knex) { 28 | return knex.schema.dropTable('deploy_log'); 29 | }; 30 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240418000000_update_project_table_pg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | // add pg related columns to project table 6 | exports.up = function (knex) { 7 | return knex.schema.alterTable('project', (table) => { 8 | // pg 9 | table 10 | .string('host') 11 | .nullable() 12 | .comment('postgresql host, postgresql specific'); 13 | table 14 | .integer('port') 15 | .nullable() 16 | .comment('postgresql port, postgresql specific'); 17 | table 18 | .string('database') 19 | .nullable() 20 | .comment('postgresql database, postgresql specific'); 21 | table 22 | .string('user') 23 | .nullable() 24 | .comment('postgresql user, postgresql specific'); 25 | }); 26 | }; 27 | 28 | /** 29 | * @param { import("knex").Knex } knex 30 | * @returns { Promise } 31 | */ 32 | exports.down = function (knex) { 33 | return knex.schema.alterTable('project', (table) => { 34 | table.dropColumns('host', 'port', 'database', 'user'); 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240419090558_add_foreign_key_to_model_column_and_metric_measure.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema 7 | .alterTable('model_column', (table) => { 8 | table.foreign('model_id').references('model.id').onDelete('CASCADE'); 9 | }) 10 | .alterTable('metric_measure', (table) => { 11 | table.foreign('metric_id').references('metric.id').onDelete('CASCADE'); 12 | }); 13 | }; 14 | 15 | /** 16 | * @param { import("knex").Knex } knex 17 | * @returns { Promise } 18 | */ 19 | exports.down = function (knex) { 20 | return knex.schema 21 | .alterTable('model_column', (table) => { 22 | table.dropForeign('model_id'); 23 | }) 24 | .alterTable('metric_measure', (table) => { 25 | table.dropForeign('metric_id'); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240425000000_add_thread_response_summary.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | // add summary column to thread_response table 6 | exports.up = function (knex) { 7 | return knex.schema.alterTable('thread_response', (table) => { 8 | table 9 | .string('summary') 10 | .nullable() 11 | .comment('the summary of the thread response'); 12 | }); 13 | }; 14 | 15 | /** 16 | * @param { import("knex").Knex } knex 17 | * @returns { Promise } 18 | */ 19 | exports.down = function (knex) { 20 | return knex.schema.alterTable('thread_response', (table) => { 21 | table.dropColumns('summary'); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240430033014_update_model_column_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | // Drop this column FE is no longer using it. 7 | return knex.schema.table('model_column', function (table) { 8 | table.dropColumn('diagram'); 9 | }); 10 | }; 11 | 12 | /** 13 | * @param { import("knex").Knex } knex 14 | * @returns { Promise } 15 | */ 16 | exports.down = function (knex) { 17 | return knex.schema.table('model_column', function (table) { 18 | table 19 | .text('diagram') 20 | .comment('for FE to store the calculated field diagram') 21 | .nullable(); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240446090560_update_relationship_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema 7 | .alterTable('relation', (table) => { 8 | table 9 | .foreign('from_column_id') 10 | .references('model_column.id') 11 | .onDelete('CASCADE'); 12 | }) 13 | .alterTable('relation', (table) => { 14 | table 15 | .foreign('to_column_id') 16 | .references('model_column.id') 17 | .onDelete('CASCADE'); 18 | }); 19 | }; 20 | 21 | /** 22 | * @param { import("knex").Knex } knex 23 | * @returns { Promise } 24 | */ 25 | exports.down = function (knex) { 26 | return knex.schema 27 | .alterTable('relation', (table) => { 28 | table.dropForeign('from_column_id'); 29 | }) 30 | .alterTable('relation', (table) => { 31 | table.dropForeign('to_column_id'); 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240502000000_add_properties_to_relationship.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | // add properties column to relation table 6 | exports.up = function (knex) { 7 | return knex.schema.alterTable('relation', (table) => { 8 | table 9 | .text('properties') 10 | .comment( 11 | 'column properties, a json string, the description of relationships should be stored here', 12 | ) 13 | .nullable(); 14 | }); 15 | }; 16 | 17 | /** 18 | * @param { import("knex").Knex } knex 19 | * @returns { Promise } 20 | */ 21 | exports.down = function (knex) { 22 | return knex.schema.alterTable('relation', (table) => { 23 | table.dropColumns('properties'); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240524044348_update_project_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = async function (knex, Promise) { 6 | await knex.schema.alterTable('project', (table) => { 7 | table.text('init_sql').alter(); 8 | }); 9 | }; 10 | 11 | /** 12 | * @param { import("knex").Knex } knex 13 | * @returns { Promise } 14 | */ 15 | exports.down = async function (knex, Promise) { 16 | // without rollback script, can not revert text to jsonb in postgres 17 | // init sql should be string, not jsonb 18 | }; 19 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240524071859_update_thread_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = async function (knex, promise) { 6 | // drop foreign key constraint before altering column type to prevent data loss 7 | await knex.schema.alterTable('thread_response', (table) => { 8 | table.dropForeign('thread_id'); 9 | }); 10 | await knex.schema.alterTable('thread', (table) => { 11 | table.text('sql').alter(); 12 | }); 13 | await knex.schema.alterTable('thread_response', (table) => { 14 | table.foreign('thread_id').references('thread.id').onDelete('CASCADE'); 15 | }); 16 | }; 17 | 18 | /** 19 | * @param { import("knex").Knex } knex 20 | * @returns { Promise } 21 | */ 22 | exports.down = async function (knex, promise) { 23 | await knex.schema.alterTable('thread_response', (table) => { 24 | table.dropForeign('thread_id'); 25 | }); 26 | await knex.schema.alterTable('thread', (table) => { 27 | table.string('sql').alter(); 28 | }); 29 | await knex.schema.alterTable('thread_response', (table) => { 30 | table.foreign('thread_id').references('thread.id').onDelete('CASCADE'); 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240530062133_update_project_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | 6 | // create connectionInfo column in project table 7 | exports.up = function (knex) { 8 | return knex.schema.table('project', (table) => { 9 | table 10 | .jsonb('connection_info') 11 | .nullable() 12 | .comment('Connection information for the project'); 13 | }); 14 | }; 15 | 16 | /** 17 | * @param { import("knex").Knex } knex 18 | * @returns { Promise } 19 | */ 20 | exports.down = function (knex) { 21 | return knex.schema.table('project', (table) => { 22 | table.dropColumn('connection_info'); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /wren-ui/migrations/20240610070534_create_schema_change_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.createTable('schema_change', (table) => { 7 | table.increments('id').comment('ID'); 8 | table.integer('project_id').comment('Reference to project.id'); 9 | 10 | // schema change info 11 | table.jsonb('change').nullable(); 12 | table.jsonb('resolve').nullable(); 13 | 14 | // timestamps 15 | table.timestamps(true, true); 16 | }); 17 | }; 18 | 19 | /** 20 | * @param { import("knex").Knex } knex 21 | * @returns { Promise } 22 | */ 23 | exports.down = function (knex) { 24 | return knex.schema.dropTable('schema_change'); 25 | }; 26 | -------------------------------------------------------------------------------- /wren-ui/migrations/20241021073019_update_project_language.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.alterTable('project', (table) => { 7 | table 8 | .string('language') 9 | .comment('The project language applied to AI') 10 | .defaultTo('EN'); 11 | }); 12 | }; 13 | 14 | /** 15 | * @param { import("knex").Knex } knex 16 | * @returns { Promise } 17 | */ 18 | exports.down = function (knex) { 19 | return knex.schema.alterTable('project', (table) => { 20 | table.dropColumn('language'); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /wren-ui/migrations/20241029092204_create_learning_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.createTable('learning', (table) => { 7 | table.increments('id').comment('ID'); 8 | table.string('user_id').comment('The user uuid.'); 9 | table 10 | .text('paths') 11 | .comment( 12 | 'The learning paths of user, array of learning stories, [enum1, enum2, ..enum(n)].', 13 | ); 14 | }); 15 | }; 16 | 17 | /** 18 | * @param { import("knex").Knex } knex 19 | * @returns { Promise } 20 | */ 21 | exports.down = function (knex) { 22 | return knex.schema.dropTable('learning'); 23 | }; 24 | -------------------------------------------------------------------------------- /wren-ui/migrations/20241106232204_update_project_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.alterTable('project', (table) => { 7 | table 8 | .jsonb('questions') 9 | .nullable() 10 | .comment('The recommended questions generated by AI'); 11 | table 12 | .string('query_id') 13 | .nullable() 14 | .comment('The query id of the recommended question pipeline'); 15 | table 16 | .string('questions_status') 17 | .nullable() 18 | .comment('The status of the recommended question pipeline'); 19 | table 20 | .jsonb('questions_error') 21 | .nullable() 22 | .comment('The error of the recommended question pipeline'); 23 | }); 24 | }; 25 | 26 | /** 27 | * @param { import("knex").Knex } knex 28 | * @returns { Promise } 29 | */ 30 | exports.down = function (knex) { 31 | return knex.schema.alterTable('project', (table) => { 32 | table.dropColumn('questions'); 33 | table.dropColumn('query_id'); 34 | table.dropColumn('questions_status'); 35 | table.dropColumn('questions_error'); 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /wren-ui/migrations/20241107171828_update_thread_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.alterTable('thread', (table) => { 7 | table 8 | .jsonb('questions') 9 | .nullable() 10 | .comment('The recommended questions generated by AI'); 11 | table 12 | .string('query_id') 13 | .nullable() 14 | .comment('The query id of the recommended question pipeline'); 15 | table 16 | .string('questions_status') 17 | .nullable() 18 | .comment('The status of the recommended question pipeline'); 19 | table 20 | .jsonb('questions_error') 21 | .nullable() 22 | .comment('The error of the recommended question pipeline'); 23 | }); 24 | }; 25 | 26 | /** 27 | * @param { import("knex").Knex } knex 28 | * @returns { Promise } 29 | */ 30 | exports.down = function (knex) { 31 | return knex.schema.alterTable('thread', (table) => { 32 | table.dropColumn('questions'); 33 | table.dropColumn('query_id'); 34 | table.dropColumn('questions_status'); 35 | table.dropColumn('questions_error'); 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /wren-ui/migrations/20241115031024_drop_thread_response_table_column.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.alterTable('thread_response', function (table) { 7 | table.dropColumn('summary'); 8 | }); 9 | }; 10 | 11 | /** 12 | * @param { import("knex").Knex } knex 13 | * @returns { Promise } 14 | */ 15 | exports.down = function (knex) { 16 | return knex.schema.alterTable('thread_response', function (table) { 17 | table 18 | .string('summary') 19 | .nullable() 20 | .comment('the summary of the thread response'); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /wren-ui/migrations/20241210072534_update_thread_response_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.alterTable('thread_response', (table) => { 7 | table.jsonb('chart_detail').nullable(); 8 | }); 9 | }; 10 | 11 | /** 12 | * @param { import("knex").Knex } knex 13 | * @returns { Promise } 14 | */ 15 | exports.down = function (knex) { 16 | return knex.schema.alterTable('thread_response', (table) => { 17 | table.dropColumn('chart_detail'); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /wren-ui/migrations/20241226135712_remove_thread_sql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = async function (knex) { 6 | // drop foreign key constraint before altering column type to prevent data loss 7 | await knex.schema.alterTable('thread_response', (table) => { 8 | table.dropForeign('thread_id'); 9 | }); 10 | await knex.schema.alterTable('thread', (table) => { 11 | table.dropColumn('sql'); 12 | }); 13 | await knex.schema.alterTable('thread_response', (table) => { 14 | table.foreign('thread_id').references('thread.id').onDelete('CASCADE'); 15 | }); 16 | }; 17 | 18 | /** 19 | * @param { import("knex").Knex } knex 20 | * @returns { Promise } 21 | */ 22 | exports.down = async function (knex) { 23 | await knex.schema.alterTable('thread', (table) => { 24 | table.text('sql').nullable(); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /wren-ui/migrations/20250102074255_create_dashboard_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = async function (knex) { 6 | await knex.schema.createTable('dashboard', (table) => { 7 | table.increments('id').primary(); 8 | table 9 | .integer('project_id') 10 | .notNullable() 11 | .comment('Reference to project.id'); 12 | table.string('name').notNullable().comment('The dashboard name'); 13 | 14 | table.foreign('project_id').references('project.id').onDelete('CASCADE'); 15 | table.index(['project_id']); 16 | table.timestamps(true, true); 17 | }); 18 | 19 | await knex.transaction(async (trx) => { 20 | // select all existing projects, should be only one project though 21 | const projects = await knex('project').forUpdate(); 22 | if (projects.length > 0) { 23 | const dashboards = projects.map((project) => ({ 24 | project_id: project.id, 25 | name: 'Dashboard', 26 | })); 27 | await trx('dashboard').insert(dashboards); 28 | } 29 | }); 30 | }; 31 | 32 | /** 33 | * @param { import("knex").Knex } knex 34 | * @returns { Promise } 35 | */ 36 | exports.down = function (knex) { 37 | return knex.schema.dropTable('dashboard'); 38 | }; 39 | -------------------------------------------------------------------------------- /wren-ui/migrations/20250102074256_create_dashboard_item_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.createTable('dashboard_item', (table) => { 7 | table.increments('id').primary(); 8 | table 9 | .integer('dashboard_id') 10 | .notNullable() 11 | .comment('Reference to dashboard.id'); 12 | table 13 | .string('type') 14 | .notNullable() 15 | .comment( 16 | 'The chart type of the dashboard item, such as: bar, table, number, etc', 17 | ); 18 | table 19 | .jsonb('layout') 20 | .notNullable() 21 | .comment( 22 | 'The layout of the dashboard item, according to which library it is, such as: { x: 0, y: 0, w: 6, h: 6 }', 23 | ); 24 | table 25 | .jsonb('detail') 26 | .notNullable() 27 | .comment( 28 | 'The detail of the dashboard item, such as: { chartSchema: {...}, sql: "..." } ', 29 | ); 30 | 31 | table 32 | .foreign('dashboard_id') 33 | .references('dashboard.id') 34 | .onDelete('CASCADE'); 35 | table.index(['dashboard_id', 'type']); 36 | table.timestamps(true, true); 37 | }); 38 | }; 39 | 40 | /** 41 | * @param { import("knex").Knex } knex 42 | * @returns { Promise } 43 | */ 44 | exports.down = function (knex) { 45 | return knex.schema.dropTable('dashboard_item'); 46 | }; 47 | -------------------------------------------------------------------------------- /wren-ui/migrations/20250102074256_create_sql_pair_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.createTable('sql_pair', (table) => { 7 | table.increments('id').primary(); 8 | table 9 | .integer('project_id') 10 | .notNullable() 11 | .comment('Reference to project.id'); 12 | table.text('sql').notNullable(); 13 | table.string('question', 1000).notNullable(); 14 | table.timestamps(true, true); 15 | 16 | table.foreign('project_id').references('id').inTable('project'); 17 | }); 18 | }; 19 | 20 | /** 21 | * @param { import("knex").Knex } knex 22 | * @returns { Promise } 23 | */ 24 | exports.down = function (knex) { 25 | return knex.schema.dropTable('sql_pair'); 26 | }; 27 | -------------------------------------------------------------------------------- /wren-ui/migrations/20250311046282_create_instruction_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.createTable('instruction', (table) => { 7 | table.increments('id').primary(); 8 | table 9 | .integer('project_id') 10 | .notNullable() 11 | .comment('Reference to project.id'); 12 | table.text('instruction').notNullable().comment('The instruction text'); 13 | table.jsonb('questions').notNullable().comment('The questions array'); 14 | table 15 | .boolean('is_default') 16 | .notNullable() 17 | .comment('Whether this instruction should be used in each asking'); 18 | table.timestamps(true, true); 19 | 20 | table.foreign('project_id').references('project.id').onDelete('CASCADE'); 21 | }); 22 | }; 23 | 24 | /** 25 | * @param { import("knex").Knex } knex 26 | * @returns { Promise } 27 | */ 28 | exports.down = function (knex) { 29 | return knex.schema.dropTable('instruction'); 30 | }; 31 | -------------------------------------------------------------------------------- /wren-ui/migrations/20250320074256_alter_sql_pair_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.alterTable('sql_pair', (table) => { 7 | // Drop the existing foreign key constraint 8 | table.dropForeign('project_id'); 9 | 10 | // Add the foreign key constraint with onDelete CASCADE 11 | table 12 | .foreign('project_id') 13 | .references('id') 14 | .inTable('project') 15 | .onDelete('CASCADE'); 16 | }); 17 | }; 18 | 19 | /** 20 | * @param { import("knex").Knex } knex 21 | * @returns { Promise } 22 | */ 23 | exports.down = function (knex) { 24 | return knex.schema.alterTable('sql_pair', (table) => { 25 | // Drop the foreign key constraint with CASCADE 26 | table.dropForeign('project_id'); 27 | 28 | // Restore the original foreign key constraint without CASCADE 29 | table.foreign('project_id').references('id').inTable('project'); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /wren-ui/migrations/20250422000000_alter_dashboard_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.alterTable('dashboard', function (table) { 7 | table.boolean('cache_enabled').defaultTo(true); 8 | table.string('schedule_frequency').nullable().defaultTo('NEVER'); // Weekly, Daily, Custom, Never 9 | table.string('schedule_cron').nullable().defaultTo(null); // cron expression string 10 | table.string('schedule_timezone').nullable().defaultTo(null); 11 | table.timestamp('next_scheduled_at').nullable().defaultTo(null); // Next scheduled run timestamp 12 | }); 13 | }; 14 | 15 | /** 16 | * @param { import("knex").Knex } knex 17 | * @returns { Promise } 18 | */ 19 | exports.down = function (knex) { 20 | return knex.schema.alterTable('dashboard', function (table) { 21 | table.dropColumn('cache_enabled'); 22 | table.dropColumn('schedule_frequency'); 23 | table.dropColumn('schedule_cron'); 24 | table.dropColumn('schedule_timezone'); 25 | table.dropColumn('next_scheduled_at'); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /wren-ui/migrations/20250423000000_create_dashboard_cache_refresh_table.js: -------------------------------------------------------------------------------- 1 | exports.up = function (knex) { 2 | return knex.schema.createTable('dashboard_item_refresh_job', (table) => { 3 | table.increments('id').primary(); 4 | table.string('hash').notNullable().comment('uuid for the refresh job'); 5 | table.integer('dashboard_id').notNullable(); 6 | table.integer('dashboard_item_id').notNullable(); 7 | table.timestamp('started_at').notNullable(); 8 | table.timestamp('finished_at'); 9 | table.string('status').notNullable(); // 'success', 'failed', 'in_progress' 10 | table.text('error_message'); 11 | table.timestamps(true, true); 12 | 13 | // Foreign keys 14 | table 15 | .foreign('dashboard_id') 16 | .references('id') 17 | .inTable('dashboard') 18 | .onDelete('CASCADE'); 19 | table 20 | .foreign('dashboard_item_id') 21 | .references('id') 22 | .inTable('dashboard_item') 23 | .onDelete('CASCADE'); 24 | 25 | // Indexes 26 | table.index(['dashboard_id', 'created_at']); 27 | table.index(['dashboard_item_id', 'created_at']); 28 | table.index('status'); 29 | table.index('hash'); 30 | }); 31 | }; 32 | 33 | exports.down = function (knex) { 34 | return knex.schema.dropTable('dashboard_item_refresh_job'); 35 | }; 36 | -------------------------------------------------------------------------------- /wren-ui/migrations/20250509000000_create_asking_task.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.createTable('asking_task', (table) => { 7 | table.increments('id').primary(); 8 | table.string('query_id').notNullable().unique(); 9 | table.text('question'); 10 | table.jsonb('detail').defaultTo('{}'); 11 | 12 | table 13 | .integer('thread_id') 14 | .references('id') 15 | .inTable('thread') 16 | .onDelete('CASCADE'); 17 | 18 | table 19 | .integer('thread_response_id') 20 | .references('id') 21 | .inTable('thread_response') 22 | .onDelete('CASCADE'); 23 | 24 | table.timestamps(true, true); 25 | }); 26 | }; 27 | 28 | /** 29 | * @param { import("knex").Knex } knex 30 | * @returns { Promise } 31 | */ 32 | exports.down = function (knex) { 33 | return knex.schema.dropTable('asking_task'); 34 | }; 35 | -------------------------------------------------------------------------------- /wren-ui/migrations/20250509000001_add_task_id_to_thread.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.alterTable('thread_response', (table) => { 7 | table 8 | .integer('asking_task_id') 9 | .nullable() 10 | .references('id') 11 | .inTable('asking_task') 12 | .onDelete('SET NULL'); 13 | }); 14 | }; 15 | 16 | /** 17 | * @param { import("knex").Knex } knex 18 | * @returns { Promise } 19 | */ 20 | exports.down = function (knex) { 21 | return knex.schema.alterTable('thread_response', (table) => { 22 | table.dropForeign('asking_task_id'); 23 | table.dropColumn('asking_task_id'); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /wren-ui/migrations/20250510000000_add_adjustment_to_thread_response.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.alterTable('thread_response', (table) => { 7 | table 8 | .jsonb('adjustment') 9 | .nullable() 10 | .comment( 11 | 'Adjustment data for thread response, including type and payload', 12 | ); 13 | }); 14 | }; 15 | 16 | /** 17 | * @param { import("knex").Knex } knex 18 | * @returns { Promise } 19 | */ 20 | exports.down = function (knex) { 21 | return knex.schema.alterTable('thread_response', (table) => { 22 | table.dropColumn('adjustment'); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /wren-ui/migrations/20250510000001_alter_dashboard_item_table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.alterTable('dashboard_item', (table) => { 7 | table 8 | .string('display_name') 9 | .comment('Display name of the dashboard item') 10 | .nullable(); 11 | }); 12 | }; 13 | 14 | /** 15 | * @param { import("knex").Knex } knex 16 | * @returns { Promise } 17 | */ 18 | exports.down = function (knex) { 19 | return knex.schema.alterTable('dashboard_item', (table) => { 20 | table.dropColumn('display_name'); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /wren-ui/migrations/20250510000002_add_version_to_project.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.alterTable('project', (table) => { 7 | table 8 | .string('version') 9 | .nullable() 10 | .comment('data source version information'); 11 | }); 12 | }; 13 | 14 | /** 15 | * @param { import("knex").Knex } knex 16 | * @returns { Promise } 17 | */ 18 | exports.down = function (knex) { 19 | return knex.schema.alterTable('project', (table) => { 20 | table.dropColumn('version'); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /wren-ui/migrations/20250511000000-create-api-history.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { import("knex").Knex } knex 3 | * @returns { Promise } 4 | */ 5 | exports.up = function (knex) { 6 | return knex.schema.createTable('api_history', (table) => { 7 | table.string('id').primary(); 8 | 9 | // Project 10 | table.integer('project_id').notNullable(); 11 | table 12 | .foreign('project_id') 13 | .references('id') 14 | .inTable('project') 15 | .onDelete('CASCADE'); 16 | 17 | // Thread 18 | table.string('thread_id'); 19 | 20 | // API Type 21 | table.string('api_type').notNullable(); 22 | 23 | // Request 24 | table.jsonb('headers'); 25 | table.jsonb('request_payload'); 26 | 27 | // Response 28 | table.jsonb('response_payload'); 29 | 30 | // Result 31 | table.integer('status_code').notNullable(); 32 | table.integer('duration_ms').notNullable(); 33 | table.timestamps(true, true); 34 | }); 35 | }; 36 | 37 | /** 38 | * @param { import("knex").Knex } knex 39 | * @returns { Promise } 40 | */ 41 | exports.down = function (knex) { 42 | return knex.schema.dropTable('api_history'); 43 | }; 44 | -------------------------------------------------------------------------------- /wren-ui/next.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path'); 3 | const withLess = require('next-with-less'); 4 | const withBundleAnalyzer = require('@next/bundle-analyzer')({ 5 | enabled: process.env.ANALYZE === 'true', 6 | }); 7 | 8 | const resolveAlias = { 9 | antd$: path.resolve(__dirname, 'src/import/antd'), 10 | }; 11 | 12 | /** @type {import('next').NextConfig} */ 13 | const nextConfig = withLess({ 14 | output: 'standalone', 15 | staticPageGenerationTimeout: 1000, 16 | compiler: { 17 | // Enables the styled-components SWC transform 18 | styledComponents: { 19 | displayName: true, 20 | ssr: true, 21 | }, 22 | }, 23 | lessLoaderOptions: { 24 | additionalData: `@import "@/styles/antd-variables.less";`, 25 | }, 26 | webpack: (config) => { 27 | config.resolve.alias = { 28 | ...config.resolve.alias, 29 | ...resolveAlias, 30 | }; 31 | return config; 32 | }, 33 | // routes redirect 34 | async redirects() { 35 | return [ 36 | { 37 | source: '/setup', 38 | destination: '/setup/connection', 39 | permanent: true, 40 | }, 41 | ]; 42 | }, 43 | }); 44 | 45 | module.exports = withBundleAnalyzer(nextConfig); 46 | -------------------------------------------------------------------------------- /wren-ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ui/public/favicon.ico -------------------------------------------------------------------------------- /wren-ui/public/images/dashboard/s1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ui/public/images/dashboard/s1.jpg -------------------------------------------------------------------------------- /wren-ui/public/images/dashboard/s2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ui/public/images/dashboard/s2.jpg -------------------------------------------------------------------------------- /wren-ui/public/images/dashboard/s3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ui/public/images/dashboard/s3.jpg -------------------------------------------------------------------------------- /wren-ui/public/images/dataSource/clickhouse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /wren-ui/public/images/dataSource/oracle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Layer 1 5 | 6 | 7 | -------------------------------------------------------------------------------- /wren-ui/public/images/learning/ask-question.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ui/public/images/learning/ask-question.jpg -------------------------------------------------------------------------------- /wren-ui/public/images/learning/data-modeling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ui/public/images/learning/data-modeling.jpg -------------------------------------------------------------------------------- /wren-ui/public/images/learning/deploy-modeling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ui/public/images/learning/deploy-modeling.jpg -------------------------------------------------------------------------------- /wren-ui/public/images/learning/edit-metadata.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ui/public/images/learning/edit-metadata.gif -------------------------------------------------------------------------------- /wren-ui/public/images/learning/edit-model.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ui/public/images/learning/edit-model.gif -------------------------------------------------------------------------------- /wren-ui/public/images/learning/instructions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ui/public/images/learning/instructions.png -------------------------------------------------------------------------------- /wren-ui/public/images/learning/save-to-knowledge.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ui/public/images/learning/save-to-knowledge.gif -------------------------------------------------------------------------------- /wren-ui/src/apollo/client/graphql/apiManagement.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const API_HISTORY = gql` 4 | query ApiHistory( 5 | $filter: ApiHistoryFilterInput 6 | $pagination: ApiHistoryPaginationInput! 7 | ) { 8 | apiHistory(filter: $filter, pagination: $pagination) { 9 | items { 10 | id 11 | projectId 12 | apiType 13 | threadId 14 | headers 15 | requestPayload 16 | responsePayload 17 | statusCode 18 | durationMs 19 | createdAt 20 | updatedAt 21 | } 22 | total 23 | hasMore 24 | } 25 | } 26 | `; 27 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/client/graphql/calculatedField.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const VALIDATE_CALCULATED_FIELD = gql` 4 | mutation ValidateCalculatedField($data: ValidateCalculatedFieldInput!) { 5 | validateCalculatedField(data: $data) { 6 | message 7 | valid 8 | } 9 | } 10 | `; 11 | 12 | export const CREATE_CALCULATED_FIELD = gql` 13 | mutation CreateCalculatedField($data: CreateCalculatedFieldInput!) { 14 | createCalculatedField(data: $data) 15 | } 16 | `; 17 | 18 | export const UPDATE_CALCULATED_FIELD = gql` 19 | mutation UpdateCalculatedField( 20 | $where: UpdateCalculatedFieldWhere! 21 | $data: UpdateCalculatedFieldInput! 22 | ) { 23 | updateCalculatedField(where: $where, data: $data) 24 | } 25 | `; 26 | 27 | export const DELETE_CALCULATED_FIELD = gql` 28 | mutation DeleteCalculatedField($where: UpdateCalculatedFieldWhere!) { 29 | deleteCalculatedField(where: $where) 30 | } 31 | `; 32 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/client/graphql/deploy.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const DEPLOY = gql` 4 | mutation Deploy { 5 | deploy 6 | } 7 | `; 8 | 9 | export const GET_DEPLOY_STATUS = gql` 10 | query DeployStatus { 11 | modelSync { 12 | status 13 | } 14 | } 15 | `; 16 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/client/graphql/instructions.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | const INSTRUCTION = gql` 4 | fragment Instruction on Instruction { 5 | id 6 | projectId 7 | instruction 8 | questions 9 | isDefault 10 | createdAt 11 | updatedAt 12 | } 13 | `; 14 | 15 | export const LIST_INSTRUCTIONS = gql` 16 | query Instructions { 17 | instructions { 18 | ...Instruction 19 | } 20 | } 21 | 22 | ${INSTRUCTION} 23 | `; 24 | 25 | export const CREATE_INSTRUCTION = gql` 26 | mutation CreateInstruction($data: CreateInstructionInput!) { 27 | createInstruction(data: $data) { 28 | ...Instruction 29 | } 30 | } 31 | 32 | ${INSTRUCTION} 33 | `; 34 | 35 | export const UPDATE_INSTRUCTION = gql` 36 | mutation UpdateInstruction( 37 | $where: InstructionWhereInput! 38 | $data: UpdateInstructionInput! 39 | ) { 40 | updateInstruction(where: $where, data: $data) { 41 | ...Instruction 42 | } 43 | } 44 | 45 | ${INSTRUCTION} 46 | `; 47 | 48 | export const DELETE_INSTRUCTION = gql` 49 | mutation DeleteInstruction($where: InstructionWhereInput!) { 50 | deleteInstruction(where: $where) 51 | } 52 | `; 53 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/client/graphql/learning.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const LEARNING_RECORD = gql` 4 | query LearningRecord { 5 | learningRecord { 6 | paths 7 | } 8 | } 9 | `; 10 | 11 | export const SAVE_LEARNING_RECORD = gql` 12 | mutation SaveLearningRecord($data: SaveLearningRecordInput!) { 13 | saveLearningRecord(data: $data) { 14 | paths 15 | } 16 | } 17 | `; 18 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/client/graphql/metadata.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const UPDATE_MODEL_METADATA = gql` 4 | mutation UpdateModelMetadata( 5 | $where: ModelWhereInput! 6 | $data: UpdateModelMetadataInput! 7 | ) { 8 | updateModelMetadata(where: $where, data: $data) 9 | } 10 | `; 11 | 12 | export const UPDATE_VIEW_METADATA = gql` 13 | mutation UpdateViewMetadata( 14 | $where: ViewWhereUniqueInput! 15 | $data: UpdateViewMetadataInput! 16 | ) { 17 | updateViewMetadata(where: $where, data: $data) 18 | } 19 | `; 20 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/client/graphql/onboarding.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const ONBOARDING_STATUS = gql` 4 | query OnboardingStatus { 5 | onboardingStatus { 6 | status 7 | } 8 | } 9 | `; 10 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/client/graphql/relationship.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const CREATE_RELATIONSHIP = gql` 4 | mutation CreateRelationship($data: RelationInput!) { 5 | createRelation(data: $data) 6 | } 7 | `; 8 | 9 | export const UPDATE_RELATIONSHIP = gql` 10 | mutation UpdateRelationship( 11 | $where: WhereIdInput! 12 | $data: UpdateRelationInput! 13 | ) { 14 | updateRelation(where: $where, data: $data) 15 | } 16 | `; 17 | 18 | export const DELETE_RELATIONSHIP = gql` 19 | mutation DeleteRelationship($where: WhereIdInput!) { 20 | deleteRelation(where: $where) 21 | } 22 | `; 23 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/client/graphql/settings.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const GET_SETTINGS = gql` 4 | query GetSettings { 5 | settings { 6 | productVersion 7 | dataSource { 8 | type 9 | properties 10 | sampleDataset 11 | } 12 | language 13 | } 14 | } 15 | `; 16 | 17 | export const RESET_CURRENT_PROJECT = gql` 18 | mutation ResetCurrentProject { 19 | resetCurrentProject 20 | } 21 | `; 22 | 23 | export const UPDATE_CURRENT_PROJECT = gql` 24 | mutation UpdateCurrentProject($data: UpdateCurrentProjectInput!) { 25 | updateCurrentProject(data: $data) 26 | } 27 | `; 28 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/client/graphql/sql.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const PREVIEW_SQL_STATEMENT = gql` 4 | mutation PreviewSQL($data: PreviewSQLDataInput!) { 5 | previewSql(data: $data) 6 | } 7 | `; 8 | 9 | export const GENERATE_QUESTION = gql` 10 | mutation GenerateQuestion($data: GenerateQuestionInput!) { 11 | generateQuestion(data: $data) 12 | } 13 | `; 14 | 15 | export const MODEL_SUBSTITUDE = gql` 16 | mutation ModelSubstitute($data: ModelSubstituteInput!) { 17 | modelSubstitute(data: $data) 18 | } 19 | `; 20 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/client/graphql/sqlPairs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | const SQL_PAIR = gql` 4 | fragment SqlPair on SqlPair { 5 | id 6 | projectId 7 | sql 8 | question 9 | createdAt 10 | updatedAt 11 | } 12 | `; 13 | 14 | export const LIST_SQL_PAIRS = gql` 15 | query SqlPairs { 16 | sqlPairs { 17 | ...SqlPair 18 | } 19 | } 20 | 21 | ${SQL_PAIR} 22 | `; 23 | 24 | export const CREATE_SQL_PAIR = gql` 25 | mutation CreateSqlPair($data: CreateSqlPairInput!) { 26 | createSqlPair(data: $data) { 27 | ...SqlPair 28 | } 29 | } 30 | 31 | ${SQL_PAIR} 32 | `; 33 | 34 | export const UPDATE_SQL_PAIR = gql` 35 | mutation UpdateSqlPair( 36 | $where: SqlPairWhereUniqueInput! 37 | $data: UpdateSqlPairInput! 38 | ) { 39 | updateSqlPair(where: $where, data: $data) { 40 | ...SqlPair 41 | } 42 | } 43 | 44 | ${SQL_PAIR} 45 | `; 46 | 47 | export const DELETE_SQL_PAIR = gql` 48 | mutation DeleteSqlPair($where: SqlPairWhereUniqueInput!) { 49 | deleteSqlPair(where: $where) 50 | } 51 | `; 52 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/client/graphql/view.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const CREATE_VIEW = gql` 4 | mutation CreateView($data: CreateViewInput!) { 5 | createView(data: $data) { 6 | id 7 | name 8 | statement 9 | } 10 | } 11 | `; 12 | 13 | export const DELETE_VIEW = gql` 14 | mutation DeleteView($where: ViewWhereUniqueInput!) { 15 | deleteView(where: $where) 16 | } 17 | `; 18 | 19 | export const GET_VIEW = gql` 20 | query GetView($where: ViewWhereUniqueInput!) { 21 | view(where: $ViewWhereUniqueInput) { 22 | id 23 | name 24 | statement 25 | } 26 | } 27 | `; 28 | 29 | export const LIST_VIEWS = gql` 30 | query ListViews { 31 | listViews { 32 | id 33 | name 34 | displayName 35 | statement 36 | } 37 | } 38 | `; 39 | 40 | export const PREVIEW_VIEW_DATA = gql` 41 | mutation PreviewViewData($where: PreviewViewDataInput!) { 42 | previewViewData(where: $where) 43 | } 44 | `; 45 | 46 | export const VALIDATE_CREATE_VIEW = gql` 47 | mutation ValidateView($data: ValidateViewInput!) { 48 | validateView(data: $data) { 49 | valid 50 | message 51 | } 52 | } 53 | `; 54 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/client/index.ts: -------------------------------------------------------------------------------- 1 | import { ApolloClient, HttpLink, InMemoryCache, from } from '@apollo/client'; 2 | import { onError } from '@apollo/client/link/error'; 3 | import errorHandler from '@/utils/errorHandler'; 4 | 5 | const apolloErrorLink = onError((error) => errorHandler(error)); 6 | 7 | const httpLink = new HttpLink({ 8 | uri: '/api/graphql', 9 | }); 10 | 11 | const client = new ApolloClient({ 12 | link: from([apolloErrorLink, httpLink]), 13 | cache: new InMemoryCache(), 14 | }); 15 | 16 | export default client; 17 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/adaptors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ibisAdaptor'; 2 | export * from './wrenAIAdaptor'; 3 | export * from './wrenEngineAdaptor'; 4 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/backgrounds/index.ts: -------------------------------------------------------------------------------- 1 | export * from './adjustmentBackgroundTracker'; 2 | export * from './textBasedAnswerBackgroundTracker'; 3 | export * from './chart'; 4 | export * from './recommend-question'; 5 | export * from './dashboardCacheBackgroundTracker'; 6 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sample'; 2 | export * from './type'; 3 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/data/type.ts: -------------------------------------------------------------------------------- 1 | export enum SampleDatasetName { 2 | MUSIC = 'MUSIC', 3 | NBA = 'NBA', 4 | ECOMMERCE = 'ECOMMERCE', 5 | HR = 'HR', 6 | } 7 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/index.ts: -------------------------------------------------------------------------------- 1 | export * from './schema'; 2 | export * from './resolvers'; 3 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './model'; 2 | export * from './instruction'; 3 | export * from './adaptor'; 4 | export * from './dashboard'; 5 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/models/instruction.ts: -------------------------------------------------------------------------------- 1 | export interface InstructionInput { 2 | projectId: number; 3 | instruction: string; 4 | questions: string[]; 5 | isDefault: boolean; 6 | } 7 | 8 | export interface UpdateInstructionInput { 9 | id: number; 10 | projectId: number; 11 | instruction: string; 12 | questions: string[]; 13 | isDefault: boolean; 14 | } 15 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/repositories/dashboardItemRefreshJobRepository.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from 'knex'; 2 | import { BaseRepository, IBasicRepository } from './baseRepository'; 3 | 4 | export enum DashboardCacheRefreshStatus { 5 | IN_PROGRESS = 'in_progress', 6 | SUCCESS = 'success', 7 | FAILED = 'failed', 8 | } 9 | 10 | export interface DashboardItemRefreshJob { 11 | id: number; 12 | hash: string; 13 | dashboardId: number; 14 | dashboardItemId: number; 15 | startedAt: Date; 16 | finishedAt: Date | null; 17 | status: DashboardCacheRefreshStatus; 18 | errorMessage: string | null; 19 | createdAt: Date; 20 | updatedAt: Date; 21 | } 22 | 23 | export interface IDashboardItemRefreshJobRepository 24 | extends IBasicRepository {} 25 | 26 | export class DashboardItemRefreshJobRepository 27 | extends BaseRepository 28 | implements IDashboardItemRefreshJobRepository 29 | { 30 | constructor(knexPg: Knex) { 31 | super({ knexPg, tableName: 'dashboard_item_refresh_job' }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/repositories/dashboardRepository.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from 'knex'; 2 | import { BaseRepository, IBasicRepository } from './baseRepository'; 3 | import { ScheduleFrequencyEnum } from '@server/models/dashboard'; 4 | 5 | export interface Dashboard { 6 | id: number; 7 | projectId: number; 8 | name: string; 9 | cacheEnabled: boolean; 10 | scheduleFrequency: ScheduleFrequencyEnum | null; 11 | scheduleTimezone: string | null; // e.g. 'America/New_York', 'Asia/Taipei' 12 | scheduleCron: string | null; // cron expression string 13 | nextScheduledAt: Date | null; // Next scheduled run timestamp 14 | } 15 | 16 | export interface IDashboardRepository extends IBasicRepository {} 17 | 18 | export class DashboardRepository 19 | extends BaseRepository 20 | implements IDashboardRepository 21 | { 22 | constructor(knexPg: Knex) { 23 | super({ knexPg, tableName: 'dashboard' }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/repositories/index.ts: -------------------------------------------------------------------------------- 1 | export * from './baseRepository'; 2 | export * from './learningRepository'; 3 | export * from './modelRepository'; 4 | export * from './projectRepository'; 5 | export * from './modelColumnRepository'; 6 | export * from './modelNestedColumnRepository'; 7 | export * from './relationshipRepository'; 8 | export * from './metricsRepository'; 9 | export * from './metricsMeasureRepository'; 10 | export * from './deployLogRepository'; 11 | export * from './viewRepository'; 12 | export * from './threadRepository'; 13 | export * from './threadResponseRepository'; 14 | export * from './schemaChangeRepository'; 15 | export * from './dashboardRepository'; 16 | export * from './dashboardItemRepository'; 17 | export * from './sqlPairRepository'; 18 | export * from './askingTaskRepository'; 19 | export * from './instructionRepository'; 20 | export * from './apiHistoryRepository'; 21 | export * from './dashboardItemRefreshJobRepository'; 22 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/repositories/metricsMeasureRepository.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from 'knex'; 2 | import { BaseRepository, IBasicRepository } from './baseRepository'; 3 | 4 | export interface MetricMeasure { 5 | id: number; // ID 6 | metricId: number; // Reference to metric ID 7 | name: string; // Measure name 8 | expression: string; // Expression for the measure 9 | granularity?: string; // Granularity for the measure, eg: "day", "hour", "minute", "year" 10 | } 11 | 12 | export interface IMetricMeasureRepository 13 | extends IBasicRepository {} 14 | 15 | export class MetricMeasureRepository 16 | extends BaseRepository 17 | implements IMetricMeasureRepository 18 | { 19 | constructor(knexPg: Knex) { 20 | super({ knexPg, tableName: 'metric_measure' }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/repositories/metricsRepository.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from 'knex'; 2 | import { BaseRepository, IBasicRepository } from './baseRepository'; 3 | 4 | export interface Metric { 5 | id: number; // ID 6 | projectId: number; // Reference to project.id 7 | name: string; // Metric name 8 | type: string; // Metric type, ex: "simple" or "cumulative" 9 | cached: boolean; // Model is cached or not 10 | refreshTime?: string; // Contain a number followed by a time unit (ns, us, ms, s, m, h, d). For example, "2h" 11 | 12 | // metric can based on model or another metric 13 | modelId?: number; // Reference to model.id 14 | metricId?: number; // Reference to metric.id 15 | properties?: string; // Metric properties, a json string, the description and displayName should be stored here 16 | } 17 | 18 | export interface IMetricRepository extends IBasicRepository {} 19 | 20 | export class MetricRepository 21 | extends BaseRepository 22 | implements IMetricRepository 23 | { 24 | constructor(knexPg: Knex) { 25 | super({ knexPg, tableName: 'metric' }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/repositories/sqlPairRepository.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from 'knex'; 2 | import { BaseRepository, IBasicRepository } from './baseRepository'; 3 | 4 | export interface SqlPair { 5 | id: number; // ID 6 | projectId: number; // Reference to project.id 7 | sql: string; // SQL query 8 | question: string; // Natural language question 9 | createdAt?: string; // Date and time when the SQL pair was created 10 | updatedAt?: string; // Date and time when the SQL pair was last updated 11 | } 12 | 13 | export interface ISqlPairRepository extends IBasicRepository {} 14 | 15 | export class SqlPairRepository 16 | extends BaseRepository 17 | implements ISqlPairRepository 18 | { 19 | constructor(knexPg: Knex) { 20 | super({ knexPg, tableName: 'sql_pair' }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/repositories/viewRepository.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from 'knex'; 2 | import { BaseRepository, IBasicRepository } from './baseRepository'; 3 | 4 | export interface View { 5 | id: number; // ID 6 | projectId: number; // Reference to project.id 7 | name: string; // The view name 8 | statement: string; // The SQL statement of this view 9 | cached: boolean; // View is cached or not 10 | refreshTime?: string; // Contain a number followed by a time unit (ns, us, ms, s, m, h, d). For example, "2h" 11 | properties?: string; // View properties, a json string, the description and displayName should be stored here 12 | } 13 | 14 | export interface IViewRepository extends IBasicRepository {} 15 | 16 | export class ViewRepository 17 | extends BaseRepository 18 | implements IViewRepository 19 | { 20 | constructor(knexPg: Knex) { 21 | super({ knexPg, tableName: 'view' }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/resolvers/learningResolver.ts: -------------------------------------------------------------------------------- 1 | import { IContext } from '@server/types'; 2 | import { getConfig } from '@server/config'; 3 | 4 | import { getLogger } from '@server/utils'; 5 | import { uniq } from 'lodash'; 6 | 7 | const config = getConfig(); 8 | 9 | const logger = getLogger('LearingResolver'); 10 | logger.level = 'debug'; 11 | 12 | export class LearningResolver { 13 | constructor() { 14 | this.getLearningRecord = this.getLearningRecord.bind(this); 15 | this.saveLearningRecord = this.saveLearningRecord.bind(this); 16 | } 17 | 18 | public async getLearningRecord( 19 | _root: any, 20 | _args: any, 21 | ctx: IContext, 22 | ): Promise { 23 | const result = await ctx.learningRepository.findAll(); 24 | return { paths: result[0]?.paths || [] }; 25 | } 26 | 27 | public async saveLearningRecord( 28 | _root: any, 29 | args: any, 30 | ctx: IContext, 31 | ): Promise { 32 | const { path } = args.data; 33 | const result = await ctx.learningRepository.findAll(); 34 | 35 | if (!result.length) { 36 | return await ctx.learningRepository.createOne({ 37 | userId: config?.userUUID, 38 | paths: [path], 39 | }); 40 | } 41 | 42 | const [record] = result; 43 | return await ctx.learningRepository.updateOne(record.id, { 44 | userId: config?.userUUID, 45 | paths: uniq([...record.paths, path]), 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/scalars.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLScalarType } from 'graphql'; 2 | import { DialectSQL } from '@server/models/adaptor'; 3 | 4 | export const DialectSQLScalar = new GraphQLScalarType({ 5 | name: 'DialectSQL', 6 | description: 'A string representing a SQL query in a specific dialect', 7 | serialize(value: unknown): string { 8 | if (typeof value !== 'string') { 9 | throw new Error('DialectSQL must be a string'); 10 | } 11 | return value; 12 | }, 13 | parseValue(value: unknown): DialectSQL { 14 | if (typeof value !== 'string') { 15 | throw new Error('DialectSQL must be a string'); 16 | } 17 | return value as DialectSQL; 18 | }, 19 | parseLiteral(ast: any): DialectSQL { 20 | if (ast.kind !== 'StringValue') { 21 | throw new Error('DialectSQL must be a string'); 22 | } 23 | return ast.value as DialectSQL; 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './askingService'; 2 | export * from './deployService'; 3 | export * from './mdlService'; 4 | export * from './modelService'; 5 | export * from './projectService'; 6 | export * from './queryService'; 7 | export * from './metadataService'; 8 | export * from './dashboardService'; 9 | export * from './askingTaskTracker'; 10 | export * from './instructionService'; 11 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/types/dataSource.ts: -------------------------------------------------------------------------------- 1 | export enum DataSourceName { 2 | BIG_QUERY = 'BIG_QUERY', 3 | DUCKDB = 'DUCKDB', 4 | POSTGRES = 'POSTGRES', 5 | MYSQL = 'MYSQL', 6 | ORACLE = 'ORACLE', 7 | MSSQL = 'MSSQL', 8 | CLICK_HOUSE = 'CLICK_HOUSE', 9 | TRINO = 'TRINO', 10 | SNOWFLAKE = 'SNOWFLAKE', 11 | } 12 | 13 | export interface DataSource { 14 | type: DataSourceName; 15 | properties: DataSourceProperties; 16 | sampleDataset?: string; 17 | } 18 | 19 | export interface SampleDatasetData { 20 | name: string; 21 | } 22 | 23 | export type DataSourceProperties = { displayName: string } & Partial< 24 | BigQueryDataSourceProperties & 25 | DuckDBDataSourceProperties & 26 | PGDataSourceProperties 27 | >; 28 | 29 | export interface BigQueryDataSourceProperties { 30 | displayName: string; 31 | projectId: string; 32 | datasetId: string; 33 | credentials: JSON; 34 | } 35 | 36 | export interface DuckDBDataSourceProperties { 37 | displayName: string; 38 | initSql: string; 39 | extensions: string[]; 40 | configurations: Record; 41 | } 42 | 43 | export interface PGDataSourceProperties { 44 | displayName: string; 45 | host: string; 46 | port: number; 47 | database: string; 48 | user: string; 49 | password: string; 50 | ssl?: boolean; 51 | } 52 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dataSource'; 2 | export * from './relationship'; 3 | export * from './manifest'; 4 | export * from './diagram'; 5 | export * from './metric'; 6 | export * from './context'; 7 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/types/relationship.ts: -------------------------------------------------------------------------------- 1 | export interface RelationData { 2 | fromModelId: number; 3 | fromColumnId: number; 4 | toModelId: number; 5 | toColumnId: number; 6 | type: RelationType; 7 | description?: string; 8 | } 9 | 10 | export interface UpdateRelationData { 11 | type: RelationType; 12 | } 13 | 14 | export interface AnalysisRelationInfo { 15 | name: string; 16 | fromModelId: number; 17 | fromModelReferenceName: string; 18 | fromColumnId: number; 19 | fromColumnReferenceName: string; 20 | toModelId: number; 21 | toModelReferenceName: string; 22 | toColumnId: number; 23 | toColumnReferenceName: string; 24 | type: RelationType; 25 | } 26 | 27 | export enum RelationType { 28 | ONE_TO_ONE = 'ONE_TO_ONE', 29 | ONE_TO_MANY = 'ONE_TO_MANY', 30 | MANY_TO_ONE = 'MANY_TO_ONE', 31 | } 32 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/utils/dataUtils.ts: -------------------------------------------------------------------------------- 1 | import { ColumnMetadata } from '@server/services/queryService'; 2 | 3 | /** 4 | * Transform raw data (columns + rows) into an array of objects 5 | * @param columns Column metadata (name, type) 6 | * @param rows Raw data rows 7 | * @returns Array of objects with column names as keys 8 | */ 9 | export const transformToObjects = ( 10 | columns: ColumnMetadata[], 11 | rows: any[][], 12 | ): Record[] => { 13 | if (!rows || !columns || rows.length === 0 || columns.length === 0) { 14 | return []; 15 | } 16 | 17 | // throw an error if the number of columns in the rows does not match the number of columns in the columns array 18 | if (rows[0].length !== columns.length) { 19 | throw new Error( 20 | 'Number of columns in the rows does not match the number of columns in the columns array', 21 | ); 22 | } 23 | 24 | return rows.map((row) => { 25 | const obj: Record = {}; 26 | columns.forEach((col, index) => { 27 | obj[col.name] = row[index]; 28 | }); 29 | return obj; 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/utils/docker.ts: -------------------------------------------------------------------------------- 1 | export const toDockerHost = (host: string) => { 2 | // if host is localhost or 127.0.0.1, rewrite it to docker.for.{platform}.localhost 3 | if (host === 'localhost' || host === '127.0.0.1') { 4 | const platform = process.platform; 5 | switch (platform) { 6 | case 'darwin': 7 | return 'docker.for.mac.localhost'; 8 | case 'linux': 9 | return 'docker.for.linux.localhost'; 10 | default: 11 | // windows and others... 12 | return 'host.docker.internal'; 13 | } 14 | } 15 | return host; 16 | }; 17 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/utils/encode.ts: -------------------------------------------------------------------------------- 1 | export function toBase64(str: string): string { 2 | return Buffer.from(str).toString('base64'); 3 | } 4 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/utils/helper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @function 3 | * @description Retrieve json without error 4 | */ 5 | export const safeParseJson = (data) => { 6 | try { 7 | return JSON.parse(data); 8 | } catch (_e) { 9 | return false; 10 | } 11 | }; 12 | 13 | export const safeStringify = (data) => { 14 | if (typeof data === 'string') { 15 | return data; 16 | } 17 | try { 18 | return JSON.stringify(data); 19 | } catch (_e) { 20 | return data; 21 | } 22 | }; 23 | 24 | export const convertColumnType = (parent: { type: string }) => { 25 | return parent.type.includes('STRUCT') ? 'RECORD' : parent.type; 26 | }; 27 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './logger'; 2 | export * from './encryptor'; 3 | export * from './encode'; 4 | export * from './string'; 5 | export * from './docker'; 6 | export * from './model'; 7 | export * from './helper'; 8 | export * from './regex'; 9 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/utils/knex.ts: -------------------------------------------------------------------------------- 1 | interface KnexOptions { 2 | dbType: string; 3 | pgUrl?: string; 4 | debug?: boolean; 5 | sqliteFile?: string; 6 | } 7 | 8 | export const bootstrapKnex = (options: KnexOptions) => { 9 | if (options.dbType === 'pg') { 10 | const { pgUrl, debug } = options; 11 | console.log('using pg'); 12 | /* eslint-disable @typescript-eslint/no-var-requires */ 13 | return require('knex')({ 14 | client: 'pg', 15 | connection: pgUrl, 16 | debug, 17 | pool: { min: 2, max: 10 }, 18 | }); 19 | } else { 20 | console.log('using sqlite'); 21 | /* eslint-disable @typescript-eslint/no-var-requires */ 22 | return require('knex')({ 23 | client: 'better-sqlite3', 24 | connection: { 25 | filename: options.sqliteFile, 26 | }, 27 | useNullAsDefault: true, 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/utils/logger.ts: -------------------------------------------------------------------------------- 1 | export { getLogger } from 'log4js'; 2 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/utils/regex.ts: -------------------------------------------------------------------------------- 1 | export interface ValidationResult { 2 | valid: boolean; 3 | message: string | null; 4 | } 5 | 6 | export function validateDisplayName(displayName: string): ValidationResult { 7 | let message = null; 8 | let valid = true; 9 | 10 | const allowableSyntaxRegex = /^[A-Za-z0-9 !@#$%^&*()_+{}[\],.'"-]*$/; 11 | const syntaxValid = allowableSyntaxRegex.test(displayName); 12 | if (!syntaxValid) { 13 | valid = false; 14 | message = 15 | 'Only space & [ a-z, A-Z, 0-9, _, -, !@#$%^&*()-+{}[]\'"., ] are allowed.'; 16 | } 17 | const startWithLetterRegex = /^[A-Za-z]/; 18 | const startWithLetterValid = startWithLetterRegex.test(displayName); 19 | if (!startWithLetterValid) { 20 | valid = false; 21 | message = 'Must start with a letter.'; 22 | } 23 | 24 | return { 25 | valid, 26 | message, 27 | }; 28 | } 29 | 30 | export function replaceAllowableSyntax(str: string) { 31 | const replacedStr = str.replace(/[!@#$%^&*()+{}[\]'",. -]/g, '_'); 32 | return replacedStr; 33 | } 34 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/utils/string.ts: -------------------------------------------------------------------------------- 1 | export const trim = (str: string) => str.replace(/^\s+|\s+$/g, ''); 2 | -------------------------------------------------------------------------------- /wren-ui/src/apollo/server/utils/timezone.ts: -------------------------------------------------------------------------------- 1 | export function getUTCOffsetMinutes(timeZone: string) { 2 | const date = new Date(); 3 | const utcDate = new Date( 4 | date.toLocaleString('en-US', { timeZone: 'UTC' }), 5 | ) as any; 6 | const tzDate = new Date(date.toLocaleString('en-US', { timeZone })) as any; 7 | 8 | return (tzDate - utcDate) / 60000; // Convert to minutes 9 | } 10 | 11 | export function formatUTCOffset(offsetMinutes: number) { 12 | const sign = offsetMinutes >= 0 ? '+' : '-'; 13 | const absOffset = Math.abs(offsetMinutes); 14 | const hours = Math.floor(absOffset / 60) 15 | .toString() 16 | .padStart(2, '0'); 17 | const minutes = (absOffset % 60).toString().padStart(2, '0'); 18 | return `UTC${sign}${hours}:${minutes}`; 19 | } 20 | -------------------------------------------------------------------------------- /wren-ui/src/components/LogoBar.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | 3 | export default function LogoBar() { 4 | return ( 5 | Wren AI 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /wren-ui/src/components/chart/properties/DonutProperties.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Row, Col, Select } from 'antd'; 2 | import { 3 | PropertiesProps, 4 | getColumnOptions, 5 | getChartTypeOptions, 6 | ChartTypeProperty, 7 | } from './BasicProperties'; 8 | 9 | export default function DonutProperties(props: PropertiesProps) { 10 | const { columns, titleMap } = props; 11 | const chartTypeOptions = getChartTypeOptions(); 12 | const columnOptions = getColumnOptions(columns, titleMap); 13 | return ( 14 | <> 15 | 16 | 17 | 18 | 19 | 20 | 21 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /wren-ui/src/components/chart/properties/GroupedBarProperties.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Row, Col, Select } from 'antd'; 2 | import { 3 | PropertiesProps, 4 | getChartTypeOptions, 5 | getColumnOptions, 6 | ChartTypeProperty, 7 | AxisProperty, 8 | } from './BasicProperties'; 9 | 10 | export default function GroupedBarProperties(props: PropertiesProps) { 11 | const { columns, titleMap } = props; 12 | const chartTypeOptions = getChartTypeOptions(); 13 | const columnOptions = getColumnOptions(columns, titleMap); 14 | return ( 15 | <> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /wren-ui/src/components/chart/properties/StackedBarProperties.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Row, Col, Select } from 'antd'; 2 | import { 3 | PropertiesProps, 4 | getChartTypeOptions, 5 | getColumnOptions, 6 | ChartTypeProperty, 7 | AxisProperty, 8 | } from './BasicProperties'; 9 | 10 | export default function StackedBarProperties(props: PropertiesProps) { 11 | const { columns, titleMap } = props; 12 | const chartTypeOptions = getChartTypeOptions(); 13 | const columnOptions = getColumnOptions(columns, titleMap); 14 | return ( 15 | <> 16 | 17 | 18 | 19 | 20 | 21 | 22 | e.stopPropagation()} 19 | onKeyDown={(e: React.KeyboardEvent) => { 20 | // change back to the original title 21 | if (e.key.toLowerCase() === ESCAPE) onCancelChange(); 22 | }} 23 | onChange={(e: React.ChangeEvent) => 24 | onSetTitle(e.target.value) 25 | } 26 | onPressEnter={(_e) => onRename(title)} 27 | onBlur={(_e) => onRename(title)} 28 | /> 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /wren-ui/src/components/table/CalculatedFieldTable.tsx: -------------------------------------------------------------------------------- 1 | import BaseTable, { 2 | Props, 3 | COLUMN, 4 | ExpandableRows, 5 | } from '@/components/table/BaseTable'; 6 | 7 | export default function CalculatedFieldTable(props: Props) { 8 | const { columns, showExpandable } = props; 9 | return ( 10 | ( 23 | 28 | ), 29 | } 30 | : null 31 | } 32 | /> 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /wren-ui/src/components/table/ModelRelationSelectionTable.tsx: -------------------------------------------------------------------------------- 1 | import type { ColumnsType } from 'antd/es/table'; 2 | import { JOIN_TYPE } from '@/utils/enum'; 3 | import { ModelIcon } from '@/utils/icons'; 4 | import SelectionTable from '@/components/table/SelectionTable'; 5 | 6 | interface ModelField { 7 | modelId: string; 8 | modelName: string; 9 | fieldId: string; 10 | fieldName: string; 11 | } 12 | 13 | export interface RelationsDataType { 14 | name: string; 15 | fromField: ModelField; 16 | isAutoGenerated: boolean; 17 | type: JOIN_TYPE; 18 | toField: ModelField; 19 | properties: Record; 20 | } 21 | 22 | interface Props { 23 | columns: ColumnsType; 24 | dataSource: RelationsDataType[]; 25 | enableRowSelection?: boolean; 26 | extra?: ( 27 | onCollapseOpen: ( 28 | event: React.MouseEvent, 29 | key: string, 30 | ) => void, 31 | ) => React.ReactNode; 32 | onChange?: (value: any | null) => void; 33 | tableTitle: string; 34 | rowKey: (record: RelationsDataType) => string; 35 | } 36 | 37 | export default function ModelRelationSelectionTable(props: Props) { 38 | return ( 39 | 43 | 44 | {props.tableTitle} 45 | 46 | } 47 | /> 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /wren-ui/src/components/table/NestedFieldTable.tsx: -------------------------------------------------------------------------------- 1 | import { COLUMN } from '@/components/table/BaseTable'; 2 | import { Table, TableProps } from 'antd'; 3 | import { DiagramModelNestedField } from '@/apollo/client/graphql/__types__'; 4 | 5 | type Props = TableProps; 6 | 7 | export default function NestedFieldTable(props: Props) { 8 | const { columns } = props; 9 | return ( 10 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /wren-ui/src/components/table/RelationTable.tsx: -------------------------------------------------------------------------------- 1 | import BaseTable, { 2 | Props, 3 | COLUMN, 4 | ExpandableRows, 5 | } from '@/components/table/BaseTable'; 6 | 7 | export default function RelationTable(props: Props) { 8 | const { columns, showExpandable } = props; 9 | return ( 10 | ( 25 | 38 | ), 39 | } 40 | : null 41 | } 42 | /> 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /wren-ui/src/hooks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canner/WrenAI/08674cb226f967a74dcceeae899a8619d45df250/wren-ui/src/hooks/.gitkeep -------------------------------------------------------------------------------- /wren-ui/src/hooks/useDrawerAction.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { FORM_MODE } from '@/utils/enum'; 3 | 4 | export interface DrawerAction { 5 | visible: boolean; 6 | onClose: () => void; 7 | onSubmit?: (values: any) => Promise; 8 | formMode?: FORM_MODE; 9 | // use as form default value or view data 10 | defaultValue?: TData; 11 | } 12 | 13 | export default function useDrawerAction() { 14 | const [visible, setVisible] = useState(false); 15 | const [formMode, setFormMode] = useState(FORM_MODE.CREATE); 16 | const [defaultValue, setDefaultValue] = useState(null); 17 | 18 | const openDrawer = (value?: any) => { 19 | value && setDefaultValue(value); 20 | value && setFormMode(FORM_MODE.EDIT); 21 | setVisible(true); 22 | }; 23 | 24 | const closeDrawer = () => { 25 | setVisible(false); 26 | setDefaultValue(null); 27 | setFormMode(FORM_MODE.CREATE); 28 | }; 29 | 30 | const updateState = (value?: any) => { 31 | setDefaultValue(value); 32 | }; 33 | 34 | return { 35 | state: { 36 | visible, 37 | formMode, 38 | defaultValue, 39 | }, 40 | openDrawer, 41 | closeDrawer, 42 | updateState, 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /wren-ui/src/hooks/useDropdown.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | export default function useDropdown() { 4 | const [visible, setVisible] = useState(false); 5 | 6 | const onVisibleChange = (visible: boolean) => setVisible(visible); 7 | 8 | const onCloseDropdownMenu = () => setVisible(false); 9 | 10 | return { 11 | visible, 12 | onVisibleChange, 13 | onCloseDropdownMenu, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /wren-ui/src/hooks/useExpressionFieldOptions.tsx: -------------------------------------------------------------------------------- 1 | import { ExpressionName } from '@/apollo/client/graphql/__types__'; 2 | import { getExpressionTexts } from '@/utils/data'; 3 | import { 4 | aggregations, 5 | mathFunctions, 6 | stringFunctions, 7 | } from '@/utils/expressionType'; 8 | import { useMemo } from 'react'; 9 | 10 | export default function useExpressionFieldOptions() { 11 | const expressionOptions = useMemo(() => { 12 | const convertor = (name: ExpressionName) => { 13 | const texts = getExpressionTexts(name); 14 | return { 15 | label: texts.name, 16 | value: name, 17 | content: { 18 | title: texts.syntax, 19 | description: texts.description, 20 | expression: texts.syntax, 21 | }, 22 | }; 23 | }; 24 | 25 | return [ 26 | { 27 | label: 'Aggregation', 28 | options: aggregations.map(convertor), 29 | }, 30 | { 31 | label: 'Math functions', 32 | options: mathFunctions.map(convertor), 33 | }, 34 | { 35 | label: 'String functions', 36 | options: stringFunctions.map(convertor), 37 | }, 38 | ]; 39 | }, []); 40 | 41 | return expressionOptions; 42 | } 43 | -------------------------------------------------------------------------------- /wren-ui/src/hooks/useGlobalConfig.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import { createContext, useContext, useEffect, useState } from 'react'; 3 | import { getUserConfig, UserConfig } from '@/utils/env'; 4 | import { trackUserTelemetry } from '@/utils/telemetry'; 5 | 6 | type ContextProps = { 7 | config?: UserConfig | null; 8 | }; 9 | 10 | const GlobalConfigContext = createContext({}); 11 | 12 | export const GlobalConfigProvider = ({ children }) => { 13 | const router = useRouter(); 14 | const [config, setConfig] = useState(null); 15 | 16 | useEffect(() => { 17 | getUserConfig() 18 | .then((config) => { 19 | setConfig(config); 20 | // telemetry setup 21 | const cleanup = trackUserTelemetry(router, config); 22 | return cleanup; 23 | }) 24 | .catch((error) => { 25 | console.error('Failed to get user config', error); 26 | }); 27 | }, [router]); 28 | 29 | const value = { 30 | config, 31 | }; 32 | 33 | return ( 34 | 35 | {children} 36 | 37 | ); 38 | }; 39 | 40 | export default function useGlobalConfig() { 41 | return useContext(GlobalConfigContext); 42 | } 43 | -------------------------------------------------------------------------------- /wren-ui/src/hooks/useHomeSidebar.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { useRouter } from 'next/router'; 3 | import { Path } from '@/utils/enum'; 4 | import { 5 | useDeleteThreadMutation, 6 | useThreadsQuery, 7 | useUpdateThreadMutation, 8 | } from '@/apollo/client/graphql/home.generated'; 9 | 10 | export default function useHomeSidebar() { 11 | const router = useRouter(); 12 | const { data, refetch } = useThreadsQuery({ 13 | fetchPolicy: 'cache-and-network', 14 | }); 15 | const [updateThread] = useUpdateThreadMutation(); 16 | const [deleteThread] = useDeleteThreadMutation(); 17 | 18 | const threads = useMemo( 19 | () => 20 | (data?.threads || []).map((thread) => ({ 21 | id: thread.id.toString(), 22 | name: thread.summary, 23 | })), 24 | [data], 25 | ); 26 | 27 | const onSelect = (selectKeys: string[]) => { 28 | router.push(`${Path.Home}/${selectKeys[0]}`); 29 | }; 30 | 31 | const onRename = async (id: string, newName: string) => { 32 | await updateThread({ 33 | variables: { where: { id: Number(id) }, data: { summary: newName } }, 34 | }); 35 | refetch(); 36 | }; 37 | 38 | const onDelete = async (id) => { 39 | await deleteThread({ variables: { where: { id: Number(id) } } }); 40 | refetch(); 41 | }; 42 | 43 | return { 44 | data: { threads }, 45 | onSelect, 46 | onRename, 47 | onDelete, 48 | refetch, 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /wren-ui/src/hooks/useModalAction.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { FORM_MODE } from '@/utils/enum'; 3 | 4 | export interface ModalAction { 5 | visible: boolean; 6 | onClose: () => void; 7 | onSubmit?: (values: SData) => Promise; 8 | formMode?: FORM_MODE; 9 | defaultValue?: TData; 10 | payload?: Record; 11 | } 12 | 13 | export default function useModalAction() { 14 | const [visible, setVisible] = useState(false); 15 | const [formMode, setFormMode] = useState(FORM_MODE.CREATE); 16 | const [payload, setPayload] = useState(null); 17 | const [defaultValue, setDefaultValue] = useState(null); 18 | 19 | const openModal = (value?: any, payload?: any) => { 20 | payload && setPayload(payload); 21 | value && setDefaultValue(value); 22 | value && setFormMode(FORM_MODE.EDIT); 23 | setVisible(true); 24 | }; 25 | 26 | const closeModal = () => { 27 | setVisible(false); 28 | setPayload(null); 29 | setDefaultValue(null); 30 | setFormMode(FORM_MODE.CREATE); 31 | }; 32 | 33 | return { 34 | state: { 35 | visible, 36 | formMode, 37 | defaultValue, 38 | payload, 39 | }, 40 | openModal, 41 | closeModal, 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /wren-ui/src/hooks/useSetupConnectionSampleDataset.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import { useCallback } from 'react'; 3 | import { Path } from '@/utils/enum'; 4 | import { ONBOARDING_STATUS } from '@/apollo/client/graphql/onboarding'; 5 | import { useStartSampleDatasetMutation } from '@/apollo/client/graphql/dataSource.generated'; 6 | import { SampleDatasetName } from '@/apollo/client/graphql/__types__'; 7 | 8 | export default function useSetupConnectionSampleDataset() { 9 | const router = useRouter(); 10 | 11 | const [startSampleDatasetMutation, { loading, error }] = 12 | useStartSampleDatasetMutation({ 13 | onError: (error) => console.error(error), 14 | onCompleted: () => router.push(Path.Modeling), 15 | refetchQueries: [{ query: ONBOARDING_STATUS }], 16 | awaitRefetchQueries: true, 17 | }); 18 | 19 | const saveSampleDataset = useCallback( 20 | async (template: SampleDatasetName) => { 21 | await startSampleDatasetMutation({ 22 | variables: { data: { name: template } }, 23 | }); 24 | }, 25 | [startSampleDatasetMutation], 26 | ); 27 | 28 | return { 29 | loading, 30 | error, 31 | saveSampleDataset, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /wren-ui/src/hooks/useSetupModels.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Path, SETUP } from '@/utils/enum'; 3 | import { useRouter } from 'next/router'; 4 | import { 5 | useListDataSourceTablesQuery, 6 | useSaveTablesMutation, 7 | } from '@/apollo/client/graphql/dataSource.generated'; 8 | 9 | export default function useSetupModels() { 10 | const [stepKey] = useState(SETUP.SELECT_MODELS); 11 | 12 | const router = useRouter(); 13 | 14 | const { data, loading: fetching } = useListDataSourceTablesQuery({ 15 | fetchPolicy: 'no-cache', 16 | onError: (error) => console.error(error), 17 | }); 18 | 19 | const [saveTablesMutation, { loading: submitting }] = useSaveTablesMutation(); 20 | 21 | const submitModels = async (tables: string[]) => { 22 | await saveTablesMutation({ 23 | variables: { 24 | data: { tables }, 25 | }, 26 | }); 27 | router.push(Path.OnboardingRelationships); 28 | }; 29 | 30 | const onBack = () => { 31 | router.push(Path.OnboardingConnection); 32 | }; 33 | 34 | const onNext = (data: { selectedTables: string[] }) => { 35 | submitModels(data.selectedTables); 36 | }; 37 | 38 | return { 39 | submitting, 40 | fetching, 41 | stepKey, 42 | onBack, 43 | onNext, 44 | tables: data?.listDataSourceTables || [], 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /wren-ui/src/hooks/useStoreContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | 3 | const contextMap = new Map>(); 4 | 5 | export const STORE = { 6 | PROMPT_THREAD: 'PromptThread', 7 | }; 8 | 9 | // Base store context hook 10 | export default function useStoreContext() { 11 | const createStore = (id: string) => { 12 | if (contextMap.has(id)) return contextMap.get(id); 13 | const context = createContext(null); 14 | contextMap.set(id, context); 15 | return context; 16 | }; 17 | 18 | const clearStore = (id: string) => { 19 | contextMap.delete(id); 20 | }; 21 | 22 | const useStore = (id: string) => { 23 | const context = contextMap.get(id); 24 | if (!context) throw new Error(`Context not found for id: ${id}`); 25 | return useContext(context); 26 | }; 27 | 28 | return { 29 | createStore, 30 | clearStore, 31 | useStore, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /wren-ui/src/import/icon.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * use client import to avoid bundle size issue 3 | */ 4 | 5 | import dynamic from 'next/dynamic'; 6 | export type { IconComponentProps } from '@ant-design/icons/lib/components/Icon'; 7 | 8 | const Icon = dynamic(() => import('@ant-design/icons/lib/components/Icon'), { 9 | ssr: false, 10 | }); 11 | 12 | export default Icon; 13 | -------------------------------------------------------------------------------- /wren-ui/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from 'next/app'; 2 | import Head from 'next/head'; 3 | import { Spin } from 'antd'; 4 | import posthog from 'posthog-js'; 5 | import apolloClient from '@/apollo/client'; 6 | import { GlobalConfigProvider } from '@/hooks/useGlobalConfig'; 7 | import { PostHogProvider } from 'posthog-js/react'; 8 | import { ApolloProvider } from '@apollo/client'; 9 | import { defaultIndicator } from '@/components/PageLoading'; 10 | 11 | require('../styles/index.less'); 12 | 13 | Spin.setDefaultIndicator(defaultIndicator); 14 | 15 | function App({ Component, pageProps }: AppProps) { 16 | return ( 17 | <> 18 | 19 | Wren AI 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 |
29 |
30 |
31 | 32 | ); 33 | } 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /wren-ui/src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/display-name */ 2 | import Document, { 3 | Html, 4 | Head, 5 | Main, 6 | NextScript, 7 | DocumentContext, 8 | DocumentInitialProps, 9 | } from 'next/document'; 10 | import { ServerStyleSheet } from 'styled-components'; 11 | 12 | export default class AppDocument extends Document { 13 | static async getInitialProps( 14 | ctx: DocumentContext, 15 | ): Promise { 16 | const originalRenderPage = ctx.renderPage; 17 | 18 | const sheet = new ServerStyleSheet(); 19 | 20 | ctx.renderPage = () => 21 | originalRenderPage({ 22 | enhanceApp: (App) => (props) => sheet.collectStyles(), 23 | enhanceComponent: (Component) => Component, 24 | }); 25 | 26 | const intialProps = await Document.getInitialProps(ctx); 27 | const styles = sheet.getStyleElement(); 28 | 29 | return { ...intialProps, styles }; 30 | } 31 | 32 | render() { 33 | return ( 34 | 35 | {this.props.styles} 36 | 37 |
38 | 39 | 40 | 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /wren-ui/src/pages/api/ask_task/streaming.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next'; 2 | import { components } from '@/common'; 3 | 4 | const { wrenAIAdaptor } = components; 5 | 6 | export default async function handler( 7 | req: NextApiRequest, 8 | res: NextApiResponse, 9 | ) { 10 | res.setHeader('Content-Type', 'text/event-stream'); 11 | res.setHeader('Cache-Control', 'no-cache, no-transform'); 12 | res.setHeader('Connection', 'keep-alive'); 13 | res.flushHeaders(); 14 | 15 | const { queryId } = req.query; 16 | try { 17 | const stream = await wrenAIAdaptor.getAskStreamingResult(queryId as string); 18 | 19 | stream.on('data', (chunk) => { 20 | // pass the chunk directly to the client 21 | res.write(chunk); 22 | }); 23 | 24 | stream.on('end', () => { 25 | res.write(`data: ${JSON.stringify({ done: true })}\n\n`); 26 | res.end(); 27 | }); 28 | 29 | // destroy the stream if the client closes the connection 30 | req.on('close', () => { 31 | stream.destroy(); 32 | }); 33 | } catch (error) { 34 | console.error(error); 35 | res.status(500).end(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /wren-ui/src/pages/api/config.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next'; 2 | import { getConfig } from '@/apollo/server/config'; 3 | 4 | export default function handler(_: NextApiRequest, res: NextApiResponse) { 5 | const config = getConfig(); 6 | const encodedTelemetryKey = config.posthogApiKey 7 | ? Buffer.from(config.posthogApiKey).toString('base64') 8 | : ''; 9 | 10 | res.status(200).json({ 11 | isTelemetryEnabled: config.telemetryEnabled || false, 12 | telemetryKey: encodedTelemetryKey, 13 | telemetryHost: config.posthogHost || '', 14 | userUUID: config.userUUID || '', 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /wren-ui/src/pages/api/v1/stream_explanation.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next'; 2 | import { components } from '@/common'; 3 | 4 | const { wrenAIAdaptor } = components; 5 | 6 | export default async function handler( 7 | req: NextApiRequest, 8 | res: NextApiResponse, 9 | ) { 10 | res.setHeader('Content-Type', 'text/event-stream'); 11 | res.setHeader('Cache-Control', 'no-cache, no-transform'); 12 | res.setHeader('Connection', 'keep-alive'); 13 | res.flushHeaders(); 14 | 15 | const { queryId } = req.query; 16 | if (!queryId) { 17 | res.status(400).json({ error: 'queryId is required' }); 18 | return; 19 | } 20 | 21 | try { 22 | const stream = await wrenAIAdaptor.getAskStreamingResult(queryId as string); 23 | 24 | stream.on('data', (chunk) => { 25 | // pass the chunk directly to the client 26 | res.write(chunk); 27 | }); 28 | 29 | stream.on('end', () => { 30 | res.write(`data: ${JSON.stringify({ done: true })}\n\n`); 31 | res.end(); 32 | }); 33 | 34 | // destroy the stream if the client closes the connection 35 | req.on('close', () => { 36 | stream.destroy(); 37 | }); 38 | } catch (error) { 39 | console.error(error); 40 | res.status(500).json({ error: error.message }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /wren-ui/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { useWithOnboarding } from '@/hooks/useCheckOnboarding'; 2 | import PageLoading from '@/components/PageLoading'; 3 | 4 | export default function Index() { 5 | useWithOnboarding(); 6 | 7 | return ; 8 | } 9 | -------------------------------------------------------------------------------- /wren-ui/src/pages/setup/connection.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import SimpleLayout from '@/components/layouts/SimpleLayout'; 3 | import ContainerCard from '@/components/pages/setup/ContainerCard'; 4 | import useSetupConnection from '@/hooks/useSetupConnection'; 5 | import { SETUP_STEPS } from '@/components/pages/setup/utils'; 6 | 7 | export default function SetupConnection() { 8 | const { connectError, dataSource, onBack, onNext, stepKey, submitting } = 9 | useSetupConnection(); 10 | 11 | const current = useMemo(() => SETUP_STEPS[stepKey], [stepKey]); 12 | 13 | return ( 14 | 15 | 16 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /wren-ui/src/pages/setup/models.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import SimpleLayout from '@/components/layouts/SimpleLayout'; 3 | import ContainerCard from '@/components/pages/setup/ContainerCard'; 4 | import useSetupModels from '@/hooks/useSetupModels'; 5 | import { SETUP_STEPS } from '@/components/pages/setup/utils'; 6 | 7 | export default function SetupModels() { 8 | const { fetching, stepKey, tables, onNext, onBack, submitting } = 9 | useSetupModels(); 10 | 11 | const current = useMemo(() => SETUP_STEPS[stepKey], [stepKey]); 12 | 13 | return ( 14 | 15 | 16 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /wren-ui/src/pages/setup/relationships.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import SimpleLayout from '@/components/layouts/SimpleLayout'; 3 | import ContainerCard from '@/components/pages/setup/ContainerCard'; 4 | import useSetupRelations from '@/hooks/useSetupRelations'; 5 | import { SETUP_STEPS } from '@/components/pages/setup/utils'; 6 | 7 | export default function SetupRelationships() { 8 | const { 9 | fetching, 10 | stepKey, 11 | recommendRelationsResult, 12 | onNext, 13 | onBack, 14 | onSkip, 15 | submitting, 16 | } = useSetupRelations(); 17 | 18 | const current = useMemo(() => SETUP_STEPS[stepKey], [stepKey]); 19 | 20 | return ( 21 | 22 | 23 | 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /wren-ui/src/styles/components/alert.less: -------------------------------------------------------------------------------- 1 | .ant-alert { 2 | align-items: flex-start; 3 | .anticon-exclamation-circle, .anticon-warning { 4 | margin-top: 4px; 5 | } 6 | .ant-alert-message { 7 | color: currentColor; 8 | } 9 | } -------------------------------------------------------------------------------- /wren-ui/src/styles/components/avatar.less: -------------------------------------------------------------------------------- 1 | .adm-avatar { 2 | &-xs { 3 | .avatar-size(@avatar-size-xs, @avatar-font-size-xs); 4 | } 5 | } -------------------------------------------------------------------------------- /wren-ui/src/styles/components/button.less: -------------------------------------------------------------------------------- 1 | .adm-btn-no-style { 2 | cursor: pointer; 3 | background: transparent; 4 | line-height: 1.2; 5 | height: auto; 6 | border: none; 7 | } 8 | 9 | .adm-onboarding-btn { 10 | width: 130px; 11 | } 12 | 13 | .adm-modeling-header-btn { 14 | width: 60px; 15 | } 16 | 17 | .adm-fix-it-btn { 18 | border-color: @citrus-6; 19 | color: @citrus-6; 20 | background-color: @citrus-1; 21 | &:hover,&:focus { 22 | background-color: @citrus-1; 23 | border-color: @citrus-4; 24 | color: @citrus-5; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /wren-ui/src/styles/components/scrollbar.less: -------------------------------------------------------------------------------- 1 | .adm-scrollbar-track { 2 | scrollbar-width: thin; 3 | scrollbar-color: @gray-5 transparent; 4 | } -------------------------------------------------------------------------------- /wren-ui/src/styles/components/select.less: -------------------------------------------------------------------------------- 1 | .adm-model-field-select-dropdown { 2 | .ant-select-item-option-grouped { 3 | padding: 5px 12px; 4 | } 5 | } -------------------------------------------------------------------------------- /wren-ui/src/styles/components/table.less: -------------------------------------------------------------------------------- 1 | // override antd styles 2 | .ant-table { 3 | border: 1px @gray-4 solid; 4 | 5 | .ant-table-row:last-child .ant-table-cell { 6 | border-bottom: none; 7 | } 8 | 9 | &.ant-table-empty { 10 | .ant-table-body { 11 | overflow: auto !important; 12 | } 13 | 14 | .ant-table-tbody .ant-table-cell { 15 | border-bottom: none; 16 | } 17 | } 18 | 19 | .ant-table-expanded-row { 20 | > .ant-table-cell { 21 | background-color: @gray-3; 22 | } 23 | } 24 | 25 | .adm-nested-table { 26 | .ant-table { 27 | border: none; 28 | border-radius: 0; 29 | 30 | .ant-table-thead > tr > th { 31 | background: @gray-2; 32 | } 33 | 34 | .ant-table-tbody > tr.ant-table-row:hover > td, 35 | .ant-table-tbody > tr > td.ant-table-cell-row-hover { 36 | background: @gray-2; 37 | } 38 | } 39 | } 40 | 41 | &--text-sm { 42 | .ant-table { 43 | font-size: @font-size-sm !important; 44 | } 45 | } 46 | } 47 | 48 | .ant-table-wrapper:not(.ant-table-has-header) { 49 | .ant-table-empty { 50 | border: none; 51 | 52 | .ant-empty-normal { 53 | margin: 80px 0; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /wren-ui/src/styles/components/tag.less: -------------------------------------------------------------------------------- 1 | .ant-tag { 2 | &--geekblue { 3 | color: @geekblue-6; 4 | background: @geekblue-1; 5 | border-color: @geekblue-6; 6 | } 7 | &--citrus { 8 | color: @citrus-6; 9 | background: @citrus-1; 10 | border-color: @citrus-6; 11 | } 12 | } -------------------------------------------------------------------------------- /wren-ui/src/styles/components/transfer.less: -------------------------------------------------------------------------------- 1 | .ant-transfer { 2 | .ant-transfer-list { 3 | min-width: 0; 4 | } 5 | 6 | .ant-table-wrapper:not(.ant-table-has-header) { 7 | .ant-table-empty { 8 | .ant-empty-normal { 9 | margin: 58px 0px; 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /wren-ui/src/styles/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/dist/antd.less'; 2 | 3 | // Components 4 | @import './components/table.less'; 5 | @import './components/avatar.less'; 6 | @import './components/button.less'; 7 | @import './components/transfer.less'; 8 | @import './components/select.less'; 9 | @import './components/alert.less'; 10 | @import './components/tag.less'; 11 | @import './components/driver.less'; 12 | @import './components/chart.less'; 13 | @import './components/scrollbar.less'; 14 | 15 | // Layouts 16 | @import './layouts/global.less'; 17 | @import './layouts/main.less'; 18 | 19 | // Utilities 20 | @import './utilities/animation.less'; 21 | @import './utilities/display.less'; 22 | @import './utilities/flex.less'; 23 | @import './utilities/grid.less'; 24 | @import './utilities/text.less'; 25 | @import './utilities/color.less'; 26 | @import './utilities/spacing.less'; 27 | @import './utilities/border.less'; 28 | -------------------------------------------------------------------------------- /wren-ui/src/styles/layouts/global.less: -------------------------------------------------------------------------------- 1 | :root { 2 | .make-color-variables(); 3 | --disabled: @disabled-color; 4 | } 5 | 6 | body { 7 | min-width: 360px; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | html body { 12 | background: #fff; 13 | } 14 | h1.next-error-h1 { 15 | border-right: 1px solid rgba(0, 0, 0, 0.3); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /wren-ui/src/styles/layouts/main.less: -------------------------------------------------------------------------------- 1 | .@{prefix}-main { 2 | min-height: 100vh; 3 | height: 100%; 4 | } 5 | 6 | .adm-layout { 7 | height: 100%; 8 | } 9 | 10 | .adm-content { 11 | height: calc(100vh - 48px); 12 | overflow: auto; 13 | } 14 | -------------------------------------------------------------------------------- /wren-ui/src/styles/utilities/animation.less: -------------------------------------------------------------------------------- 1 | @keyframes fade-in { 2 | 0% { 3 | opacity: 0; 4 | } 5 | 100% { 6 | opacity: 1; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /wren-ui/src/styles/utilities/border.less: -------------------------------------------------------------------------------- 1 | .border { 2 | border-width: 1px !important; 3 | border-style: solid !important; 4 | 5 | &-t { 6 | border-top-width: 1px !important; 7 | border-top-style: solid !important; 8 | } 9 | &-r { 10 | border-right-width: 1px !important; 11 | border-right-style: solid !important; 12 | } 13 | &-l { 14 | border-left-width: 1px !important; 15 | border-left-style: solid !important; 16 | } 17 | &-b { 18 | border-bottom-width: 1px !important; 19 | border-bottom-style: solid !important; 20 | } 21 | } 22 | 23 | .rounded { 24 | border-radius: 4px !important; 25 | } 26 | 27 | .rounded-pill { 28 | border-radius: 999px !important; 29 | } -------------------------------------------------------------------------------- /wren-ui/src/styles/utilities/color.less: -------------------------------------------------------------------------------- 1 | .make-color-classes(@i: length(@preset-colors)) when (@i > 0) { 2 | .make-color-classes(@i - 1); 3 | @color: extract(@preset-colors, @i); 4 | each(range(1, 10), { 5 | @colorVar: ~'var(--@{color}-@{index})'; 6 | .bg-@{color}-@{index} { 7 | background-color: @colorVar !important; 8 | } 9 | .@{color}-@{index} { 10 | color: @colorVar !important; 11 | } 12 | .border-@{color}-@{index} { 13 | border-color: @colorVar !important; 14 | } 15 | }); 16 | } 17 | 18 | .make-color-classes(); 19 | 20 | *[class*='hover\:'] { 21 | transition: color 0.3s ease; 22 | } 23 | 24 | .hover\:text:hover { 25 | color: var(--geekblue-6) !important; 26 | } -------------------------------------------------------------------------------- /wren-ui/src/styles/utilities/display.less: -------------------------------------------------------------------------------- 1 | .make-display-classes() { 2 | @preset-display: { 3 | block: block; 4 | inline: inline; 5 | inline-block: inline-block; 6 | flex: flex; 7 | inline-flex: inline-flex; 8 | grid: grid; 9 | none: none; 10 | }; 11 | each(@preset-display, { 12 | .d-@{key} { 13 | display: @value !important; 14 | } 15 | }); 16 | } 17 | 18 | .make-display-classes(); 19 | 20 | .cursor-pointer { 21 | cursor: pointer !important; 22 | } 23 | 24 | .cursor-wait { 25 | cursor: wait !important; 26 | } 27 | 28 | .cursor-not-allowed { 29 | cursor: not-allowed !important; 30 | } 31 | 32 | .select-none { 33 | user-select: none !important; 34 | } 35 | 36 | .overflow-hidden { 37 | overflow: hidden !important; 38 | } 39 | 40 | .scrollable-y { 41 | overflow: hidden auto !important; 42 | } 43 | 44 | .scrollable-x { 45 | overflow: auto hidden !important; 46 | } 47 | -------------------------------------------------------------------------------- /wren-ui/src/styles/utilities/flex.less: -------------------------------------------------------------------------------- 1 | .justify { 2 | &-start { 3 | justify-content: flex-start !important; 4 | } 5 | &-end { 6 | justify-content: flex-end !important; 7 | } 8 | &-center { 9 | justify-content: center !important; 10 | } 11 | &-space-between { 12 | justify-content: space-between !important; 13 | } 14 | } 15 | 16 | .align { 17 | &-start { 18 | align-items: flex-start !important; 19 | } 20 | &-end { 21 | align-items: flex-end !important; 22 | } 23 | &-center { 24 | align-items: center !important; 25 | } 26 | &-baseline { 27 | align-items: baseline !important; 28 | } 29 | } 30 | 31 | .flex { 32 | &-shrink-0 { 33 | flex-shrink: 0 !important; 34 | } 35 | 36 | &-shrink-1 { 37 | flex-shrink: 1 !important; 38 | } 39 | 40 | &-grow-0 { 41 | flex-grow: 0 !important; 42 | } 43 | 44 | &-grow-1 { 45 | flex-grow: 1 !important; 46 | } 47 | 48 | &-row { 49 | flex-direction: row !important; 50 | } 51 | 52 | &-column { 53 | flex-direction: column !important; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /wren-ui/src/styles/utilities/grid.less: -------------------------------------------------------------------------------- 1 | 2 | .make-grid-classes(@i: 6) when (@i >= 1) { 3 | .make-grid-classes(@i - 1); 4 | .grid-columns-@{i} { 5 | grid-template-columns: repeat(@i, 1fr) !important; 6 | } 7 | 8 | // grid gap 9 | @base-gap: 4px; 10 | @gap: @i * @base-gap; 11 | 12 | .g-@{i} { 13 | column-gap: @gap !important; 14 | row-gap: @gap !important; 15 | } 16 | .gx-@{i} { 17 | column-gap: @gap !important; 18 | } 19 | .gy-@{i} { 20 | row-gap: @gap !important; 21 | } 22 | } 23 | 24 | .make-grid-classes(); -------------------------------------------------------------------------------- /wren-ui/src/styles/utilities/text.less: -------------------------------------------------------------------------------- 1 | .make-text-classes() { 2 | @preset-text: { 3 | base: @font-size-base; 4 | xs: @font-size-sm - 2px; 5 | sm: @font-size-sm; 6 | md: @font-size-lg; 7 | lg: @font-size-lg + 2px; 8 | }; 9 | each(@preset-text, { 10 | .text-@{key} { 11 | font-size: @value !important; 12 | } 13 | }); 14 | } 15 | 16 | .text-left { 17 | text-align: left !important; 18 | } 19 | 20 | .text-right { 21 | text-align: right !important; 22 | } 23 | 24 | .text-center { 25 | text-align: center !important; 26 | } 27 | 28 | .text-extra-bold { 29 | font-weight: 800 !important; 30 | } 31 | 32 | .text-bold { 33 | font-weight: 700 !important; 34 | } 35 | 36 | .text-semi-bold { 37 | font-weight: 600 !important; 38 | } 39 | 40 | .text-medium { 41 | font-weight: 500 !important; 42 | } 43 | 44 | .make-text-classes(); 45 | 46 | .text-truncate { 47 | overflow: hidden; 48 | text-overflow: ellipsis; 49 | white-space: nowrap; 50 | } 51 | 52 | .text-family-base { 53 | font-family: @font-family !important; 54 | } 55 | 56 | .text-nowrap { 57 | white-space: nowrap !important; 58 | } 59 | 60 | .underline { 61 | text-decoration: underline !important; 62 | } 63 | 64 | .hover\:underline:hover { 65 | text-decoration: underline !important; 66 | } 67 | 68 | .text-break-word { 69 | overflow-wrap: break-word !important; 70 | word-break: break-word !important; 71 | } 72 | -------------------------------------------------------------------------------- /wren-ui/src/utils/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './type'; 2 | export * from './dictionary'; 3 | -------------------------------------------------------------------------------- /wren-ui/src/utils/data/type/index.ts: -------------------------------------------------------------------------------- 1 | export * from './modeling'; 2 | -------------------------------------------------------------------------------- /wren-ui/src/utils/diagram/creator.ts: -------------------------------------------------------------------------------- 1 | import { Edge, Node, Viewport, ReactFlowJsonObject } from 'reactflow'; 2 | import { Diagram } from '@/utils/data/type'; 3 | import { Transformer } from './transformer'; 4 | 5 | export class DiagramCreator { 6 | private nodes: Node[]; 7 | private edges: Edge[]; 8 | private viewport: Viewport = { x: 0, y: 0, zoom: 1 }; 9 | 10 | constructor(data: Diagram) { 11 | const transformedData = new Transformer(data); 12 | this.nodes = transformedData.nodes; 13 | this.edges = transformedData.edges; 14 | } 15 | 16 | public toJsonObject(): ReactFlowJsonObject { 17 | return { 18 | nodes: this.nodes, 19 | edges: this.edges, 20 | viewport: this.viewport, 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /wren-ui/src/utils/diagram/index.ts: -------------------------------------------------------------------------------- 1 | export * from './creator'; 2 | export * from './transformer'; 3 | -------------------------------------------------------------------------------- /wren-ui/src/utils/enum/dataSources.ts: -------------------------------------------------------------------------------- 1 | export enum DATA_SOURCES { 2 | BIG_QUERY = 'BIG_QUERY', 3 | DUCKDB = 'DUCKDB', 4 | POSTGRES = 'POSTGRES', 5 | MYSQL = 'MYSQL', 6 | ORACLE = 'ORACLE', 7 | MSSQL = 'MSSQL', 8 | CLICK_HOUSE = 'CLICK_HOUSE', 9 | TRINO = 'TRINO', 10 | SNOWFLAKE = 'SNOWFLAKE', 11 | } 12 | -------------------------------------------------------------------------------- /wren-ui/src/utils/enum/diagram.ts: -------------------------------------------------------------------------------- 1 | export enum MARKER_TYPE { 2 | MANY = 'many', 3 | ONE = 'one', 4 | } 5 | 6 | export enum EDGE_TYPE { 7 | STEP = 'step', 8 | SMOOTHSTEP = 'smoothstep', 9 | BEZIER = 'bezier', 10 | MODEL = 'model', 11 | METRIC = 'metric', 12 | } 13 | -------------------------------------------------------------------------------- /wren-ui/src/utils/enum/dropdown.ts: -------------------------------------------------------------------------------- 1 | export enum MORE_ACTION { 2 | EDIT = 'edit', 3 | DELETE = 'delete', 4 | UPDATE_COLUMNS = 'update_columns', 5 | CACHE_SETTINGS = 'cache_settings', 6 | REFRESH = 'refresh', 7 | HIDE_CATEGORY = 'hide_category', 8 | VIEW_SQL_PAIR = 'view_sql_pair', 9 | VIEW_INSTRUCTION = 'view_instruction', 10 | ADJUST_SQL = 'adjust_sql', 11 | ADJUST_STEPS = 'adjust_steps', 12 | } 13 | -------------------------------------------------------------------------------- /wren-ui/src/utils/enum/form.ts: -------------------------------------------------------------------------------- 1 | export enum FORM_MODE { 2 | CREATE = 'CREATE', 3 | EDIT = 'EDIT', 4 | } 5 | 6 | // identifier separated by special & unique symbol 7 | const specialSymbol = '☺'; 8 | 9 | export const convertObjectToIdentifier = (obj: T, paths: string[]): string => 10 | paths.map((path) => `${path}:${obj[path] || ''}`).join(specialSymbol); 11 | 12 | export const convertIdentifierToObject = (identifier: string): T => 13 | Object.fromEntries( 14 | identifier.split(specialSymbol).map((str) => str.split(':')), 15 | ); 16 | -------------------------------------------------------------------------------- /wren-ui/src/utils/enum/home.ts: -------------------------------------------------------------------------------- 1 | export enum COLLAPSE_CONTENT_TYPE { 2 | NONE = 'none', 3 | VIEW_SQL = 'view_sql', 4 | PREVIEW_DATA = 'preview_data', 5 | } 6 | 7 | export enum PROCESS_STATE { 8 | IDLE, 9 | UNDERSTANDING, 10 | SEARCHING, 11 | PLANNING, 12 | GENERATING, 13 | CORRECTING, 14 | FINISHED, 15 | FAILED, 16 | STOPPED, 17 | NO_RESULT, 18 | } 19 | 20 | export enum ANSWER_TAB_KEYS { 21 | ANSWER = 'answer', 22 | VIEW_SQL = 'view-sql', 23 | CHART = 'chart', 24 | } 25 | -------------------------------------------------------------------------------- /wren-ui/src/utils/enum/index.ts: -------------------------------------------------------------------------------- 1 | export * from './form'; 2 | export * from './setup'; 3 | export * from './dataSources'; 4 | export * from './columnType'; 5 | export * from './modeling'; 6 | export * from './path'; 7 | export * from './diagram'; 8 | export * from './home'; 9 | export * from './settings'; 10 | export * from './dropdown'; 11 | export * from './menu'; 12 | -------------------------------------------------------------------------------- /wren-ui/src/utils/enum/menu.ts: -------------------------------------------------------------------------------- 1 | export enum MENU_KEY { 2 | QUESTION_SQL_PAIRS = 'question-sql-pairs', 3 | INSTRUCTIONS = 'instructions', 4 | API_HISTORY = 'api-history', 5 | API_REFERENCE = 'api-reference', 6 | } 7 | -------------------------------------------------------------------------------- /wren-ui/src/utils/enum/modeling.ts: -------------------------------------------------------------------------------- 1 | export { 2 | NodeType as NODE_TYPE, 3 | RelationType as JOIN_TYPE, 4 | } from '@/apollo/client/graphql/__types__'; 5 | -------------------------------------------------------------------------------- /wren-ui/src/utils/enum/path.ts: -------------------------------------------------------------------------------- 1 | export enum Path { 2 | Home = '/home', 3 | HomeDashboard = '/home/dashboard', 4 | Thread = '/home/[id]', 5 | Modeling = '/modeling', 6 | Onboarding = '/setup', 7 | OnboardingConnection = '/setup/connection', 8 | OnboardingModels = '/setup/models', 9 | OnboardingRelationships = '/setup/relationships', 10 | Knowledge = '/knowledge', 11 | KnowledgeQuestionSQLPairs = '/knowledge/question-sql-pairs', 12 | KnowledgeInstructions = '/knowledge/instructions', 13 | APIManagement = '/api-management', 14 | APIManagementHistory = '/api-management/history', 15 | } 16 | -------------------------------------------------------------------------------- /wren-ui/src/utils/enum/settings.ts: -------------------------------------------------------------------------------- 1 | export enum SETTINGS { 2 | DATA_SOURCE = 'DATA_SOURCE', 3 | PROJECT = 'PROJECT', 4 | } 5 | -------------------------------------------------------------------------------- /wren-ui/src/utils/enum/setup.ts: -------------------------------------------------------------------------------- 1 | export enum SETUP { 2 | STARTER = 'starter', 3 | CREATE_DATA_SOURCE = 'createDataSource', 4 | SELECT_MODELS = 'selectModels', 5 | RECOMMEND_RELATIONS = 'recommendRelations', 6 | DEFINE_RELATIONS = 'defineRelations', 7 | } 8 | -------------------------------------------------------------------------------- /wren-ui/src/utils/env.ts: -------------------------------------------------------------------------------- 1 | const env = { 2 | isDevelopment: process.env.NODE_ENV === 'development', 3 | isProduction: process.env.NODE_ENV === 'production', 4 | }; 5 | 6 | export default env; 7 | 8 | export type UserConfig = { 9 | isTelemetryEnabled: boolean; 10 | telemetryKey: string; 11 | telemetryHost: string; 12 | userUUID: string; 13 | }; 14 | 15 | // Get the user configuration 16 | export const getUserConfig = async (): Promise => { 17 | const config = await fetch('/api/config').then((res) => res.json()); 18 | const decodedTelemetryKey = Buffer.from( 19 | config.telemetryKey, 20 | 'base64', 21 | ).toString(); 22 | return { ...config, telemetryKey: decodedTelemetryKey }; 23 | }; 24 | -------------------------------------------------------------------------------- /wren-ui/src/utils/error/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dictionary'; 2 | -------------------------------------------------------------------------------- /wren-ui/src/utils/events.tsx: -------------------------------------------------------------------------------- 1 | export const subscribe = (eventName: string, listener: any) => { 2 | document.addEventListener(eventName, listener); 3 | }; 4 | 5 | export const unsubscribe = (eventName: string, listener: any) => { 6 | document.removeEventListener(eventName, listener); 7 | }; 8 | 9 | export const dispatch = (eventName: string, detail?: any) => { 10 | const event = new CustomEvent(eventName, { detail }); 11 | document.dispatchEvent(event); 12 | }; 13 | 14 | export const EVENT_NAME = { 15 | GO_TO_FIRST_MODEL: 'goToFirstModel', 16 | }; 17 | -------------------------------------------------------------------------------- /wren-ui/src/utils/expressionType.ts: -------------------------------------------------------------------------------- 1 | import { ExpressionName } from '@/apollo/client/graphql/__types__'; 2 | 3 | export const aggregations = [ 4 | ExpressionName.AVG, 5 | ExpressionName.COUNT, 6 | ExpressionName.MAX, 7 | ExpressionName.MIN, 8 | ExpressionName.SUM, 9 | ]; 10 | 11 | export const mathFunctions = [ 12 | ExpressionName.ABS, 13 | ExpressionName.CBRT, 14 | ExpressionName.CEIL, 15 | ExpressionName.EXP, 16 | ExpressionName.FLOOR, 17 | ExpressionName.LN, 18 | ExpressionName.LOG10, 19 | ExpressionName.ROUND, 20 | ExpressionName.SIGN, 21 | ]; 22 | 23 | export const stringFunctions = [ExpressionName.LENGTH, ExpressionName.REVERSE]; 24 | -------------------------------------------------------------------------------- /wren-ui/src/utils/helper.ts: -------------------------------------------------------------------------------- 1 | import { omitBy, isUndefined } from 'lodash'; 2 | 3 | /** 4 | * @function 5 | * @description Remove undefined property value in an object 6 | */ 7 | export const compactObject = (obj: T) => { 8 | return omitBy(obj, isUndefined) as T; 9 | }; 10 | 11 | /** 12 | * @function 13 | * @description Retrieve json without error 14 | */ 15 | export const parseJson = (data) => { 16 | try { 17 | return JSON.parse(data); 18 | } catch (_e) { 19 | return data; 20 | } 21 | }; 22 | 23 | export const attachLoading = ( 24 | asyncRequest: (...args: any[]) => Promise, 25 | setLoading: React.Dispatch>, 26 | ) => { 27 | return async (...args) => { 28 | setLoading(true); 29 | try { 30 | await asyncRequest(...args); 31 | } finally { 32 | setLoading(false); 33 | } 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /wren-ui/src/utils/iteration.tsx: -------------------------------------------------------------------------------- 1 | interface Props { 2 | [key: string]: any; 3 | data: any[]; 4 | // by default it will use item['key'] as keyIndex unless specifying keyIndex 5 | keyIndex?: string | ((item: any) => string); 6 | } 7 | 8 | export type IterableComponent = { 9 | data: T[]; 10 | index: number; 11 | key: string; 12 | } & T; 13 | 14 | export const makeIterable = (Template: React.FC>) => { 15 | const Iterator = (props: Props) => { 16 | const { data, keyIndex = 'key', ...restProps } = props; 17 | const result = data.map((item, index) => { 18 | const key = 19 | typeof keyIndex === 'function' ? keyIndex(item) : item[keyIndex]; 20 | return ( 21 |