From a87e1c070f16c7d73a06334a510329ff8f226593 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 17 Oct 2024 19:17:50 +0200 Subject: [PATCH] aspire-ize semantic workbench using localhost globally working example with aspire and uvicorn integration dynamic python port python assistant port defined via env var python assistant read workbench service url from env reverting port changes for python services using correct env var for python assistant and dockerfile for FE use custom env var for workbench service endpoint in app correct injection of workbench servic endoint ensure correct env var are used in dotnet example 3 deploy via azd using workaround for dockerfile using correct env var for workbench_service url in python assistants working deployment with azd don't pass port during frontend docker build - not needed working deployment with hardcoded appids in wk service update doc app registration id is now picked from .env in docker working deployment of dotnet example working assistant deployment reorders apphost reorders packages cleans up adds doc uses new start assistant imrpove doc updates doc fixes assistant container params differentiate aspire dockerfile from already existing ones cleans workbench app dockerfile Minor code style changes back to 127.0.0.1 on workbench config fixes workbench app docker-entrypoint logs revert space on start removed next-steps.md removes sshd from Dockerfile workbench service and assistant reverts .env.example reverts settings.py improve readme improves readme requirements fixes folder for apphost in doc Update aspire-orchestrator/SemanticWorkbench.Aspire.Hosting.Extensions/PathNormalizer.cs Co-authored-by: Devis Lucato Update aspire-orchestrator/SemanticWorkbench.Aspire.Hosting.Extensions/PathNormalizer.cs Co-authored-by: Devis Lucato using arg value for vite_semanticworkbenchurl moves dockerfileextensions to separate class aspire overwrites correct var env for agents adds copyright Revert "using arg value for vite_semanticworkbenchurl" This reverts commit b0d15aef565dde0c8ab3fe36734b58f5b24c8e8a. deletes aspire-manifest uses workaround for workbench service url in frontend dockerignore .env files dockerignore .azure folder adds comment on agent autoreference adds doc for dotnet dev certs use env var for entra ID config read entraid config from appsettings adds default values for app registration Code style Update workbench-service/.env.example Add comment to new docker file Code style Revert code style changes Revert changes to agent 3 csproj Update examples/dotnet/dotnet-03-simple-chatbot/Program.cs Update examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj Update libraries/python/semantic-workbench-assistant/semantic_workbench_assistant/settings.py Update workbench-app/src/Constants.ts Update workbench-app/src/global.d.ts Fix SLN filename Fix project names Revert code style changes Revert changes to agent 3 csproj Update examples/dotnet/dotnet-03-simple-chatbot/Program.cs Update examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj Update libraries/python/semantic-workbench-assistant/semantic_workbench_assistant/settings.py Update workbench-app/src/Constants.ts Update workbench-app/src/global.d.ts Fix SLN filename Fix project names Update libraries/python/semantic-workbench-assistant/semantic_workbench_assistant/settings.py Update workbench-app/src/Constants.ts Update workbench-app/src/global.d.ts Fix SLN filename Fix project names Reorg projects Fix URLs and ping errors Handle ping exceptions Improve logging Change ping frequency Auto detect port in use and better handle some exceptions Rebasing Update workbench-service/.env.example Update workbench-app/.env.example Fix merge Fix merge Fix merge --- .dockerignore | 1 + .gitignore | 1 + aspire-orchestrator/.editorconfig | 407 ++++++++++++++++++ aspire-orchestrator/Aspire.AppHost/.gitignore | 1 + .../Aspire.AppHost/Aspire.AppHost.csproj | 28 ++ aspire-orchestrator/Aspire.AppHost/Program.cs | 38 ++ .../Properties/launchSettings.json | 29 ++ .../Aspire.AppHost/appsettings.json | 13 + .../Aspire.Extensions.csproj | 17 + .../Aspire.Extensions/DockerFileExtensions.cs | 36 ++ .../Aspire.Extensions/PathNormalizer.cs | 24 ++ .../UvAppHostingExtensions.cs | 88 ++++ .../Aspire.Extensions/UvAppResource.cs | 9 + .../Aspire.Extensions/VirtualEnvironment.cs | 39 ++ .../WorkbenchServiceHostingExtensions.cs | 72 ++++ .../Aspire.ServiceDefaults.csproj | 23 + .../Aspire.ServiceDefaults/Extensions.cs | 121 ++++++ aspire-orchestrator/README.md | 54 +++ .../SemanticWorkbench.Aspire.sln | 48 +++ .../SemanticWorkbench.Aspire.sln.DotSettings | 304 +++++++++++++ aspire-orchestrator/run.sh | 38 ++ tools/docker/Dockerfile.aspire.assistant | 43 ++ workbench-app/.dockerignore | 1 + workbench-app/Dockerfile | 39 ++ workbench-app/docker-entrypoint.sh | 15 + workbench-app/index.html | 5 + workbench-app/nginx.conf | 13 + workbench-app/src/Constants.ts | 10 +- workbench-app/src/global.d.ts | 9 + workbench-service/Dockerfile.aspire | 41 ++ 30 files changed, 1566 insertions(+), 1 deletion(-) create mode 100644 aspire-orchestrator/.editorconfig create mode 100644 aspire-orchestrator/Aspire.AppHost/.gitignore create mode 100644 aspire-orchestrator/Aspire.AppHost/Aspire.AppHost.csproj create mode 100644 aspire-orchestrator/Aspire.AppHost/Program.cs create mode 100644 aspire-orchestrator/Aspire.AppHost/Properties/launchSettings.json create mode 100644 aspire-orchestrator/Aspire.AppHost/appsettings.json create mode 100644 aspire-orchestrator/Aspire.Extensions/Aspire.Extensions.csproj create mode 100644 aspire-orchestrator/Aspire.Extensions/DockerFileExtensions.cs create mode 100644 aspire-orchestrator/Aspire.Extensions/PathNormalizer.cs create mode 100644 aspire-orchestrator/Aspire.Extensions/UvAppHostingExtensions.cs create mode 100644 aspire-orchestrator/Aspire.Extensions/UvAppResource.cs create mode 100644 aspire-orchestrator/Aspire.Extensions/VirtualEnvironment.cs create mode 100644 aspire-orchestrator/Aspire.Extensions/WorkbenchServiceHostingExtensions.cs create mode 100644 aspire-orchestrator/Aspire.ServiceDefaults/Aspire.ServiceDefaults.csproj create mode 100644 aspire-orchestrator/Aspire.ServiceDefaults/Extensions.cs create mode 100644 aspire-orchestrator/README.md create mode 100644 aspire-orchestrator/SemanticWorkbench.Aspire.sln create mode 100644 aspire-orchestrator/SemanticWorkbench.Aspire.sln.DotSettings create mode 100755 aspire-orchestrator/run.sh create mode 100644 tools/docker/Dockerfile.aspire.assistant create mode 100644 workbench-app/.dockerignore create mode 100644 workbench-app/Dockerfile create mode 100644 workbench-app/docker-entrypoint.sh create mode 100644 workbench-app/nginx.conf create mode 100644 workbench-app/src/global.d.ts create mode 100644 workbench-service/Dockerfile.aspire diff --git a/.dockerignore b/.dockerignore index 70779ad0..02089233 100644 --- a/.dockerignore +++ b/.dockerignore @@ -18,3 +18,4 @@ **/__pycache__ **/.pytest_cache **/.venv +.azure diff --git a/.gitignore b/.gitignore index 31f7e8f7..e08f9ce5 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* +.azure diff --git a/aspire-orchestrator/.editorconfig b/aspire-orchestrator/.editorconfig new file mode 100644 index 00000000..06817985 --- /dev/null +++ b/aspire-orchestrator/.editorconfig @@ -0,0 +1,407 @@ +# To learn more about .editorconfig see https://aka.ms/editorconfigdocs +############################### +# Core EditorConfig Options # +############################### +root = true +# All files +[*] +indent_style = space +end_of_line = lf + +# XML project files +[*.{vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML config files +[*.{targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# XML config files +[*.csproj] +indent_size = 4 + +# XML config files +[*.props] +indent_size = 4 + +[Directory.Packages.props] +indent_size = 2 + +# YAML config files +[*.{yml,yaml}] +tab_width = 2 +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +# JSON config files +[*.json] +tab_width = 2 +indent_size = 2 +insert_final_newline = false +trim_trailing_whitespace = true + +# Typescript files +[*.{ts,tsx}] +insert_final_newline = true +trim_trailing_whitespace = true +tab_width = 4 +indent_size = 4 +file_header_template = Copyright (c) Microsoft. All rights reserved. + +# Stylesheet files +[*.{css,scss,sass,less}] +insert_final_newline = true +trim_trailing_whitespace = true +tab_width = 4 +indent_size = 4 + +# Code files +[*.{cs,csx,vb,vbx}] +tab_width = 4 +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8-bom +file_header_template = Copyright (c) Microsoft. All rights reserved. + +############################### +# .NET Coding Conventions # +############################### +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true +# this. preferences +dotnet_style_qualification_for_field = true:error +dotnet_style_qualification_for_property = true:error +dotnet_style_qualification_for_method = true:error +dotnet_style_qualification_for_event = true:error +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:error +dotnet_style_readonly_field = true:suggestion +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:silent +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +# Code quality rules +dotnet_code_quality_unused_parameters = all:suggestion + +[*.cs] + +# TODO: enable this but stop "dotnet format" from applying incorrect fixes and introducing a lot of unwanted changes. +dotnet_analyzer_diagnostic.severity = none + +# Note: these settings cause "dotnet format" to fix the code. You should review each change if you uses "dotnet format". +dotnet_diagnostic.RCS1007.severity = warning # Add braces +dotnet_diagnostic.RCS1036.severity = warning # Remove unnecessary blank line. +dotnet_diagnostic.RCS1037.severity = warning # Remove trailing white-space. +dotnet_diagnostic.RCS1097.severity = warning # Remove redundant 'ToString' call. +dotnet_diagnostic.RCS1138.severity = warning # Add summary to documentation comment. +dotnet_diagnostic.RCS1139.severity = warning # Add summary element to documentation comment. +dotnet_diagnostic.RCS1168.severity = warning # Parameter name 'foo' differs from base name 'bar'. +dotnet_diagnostic.RCS1175.severity = warning # Unused 'this' parameter 'operation'. +dotnet_diagnostic.RCS1192.severity = warning # Unnecessary usage of verbatim string literal. +dotnet_diagnostic.RCS1194.severity = warning # Implement exception constructors. +dotnet_diagnostic.RCS1211.severity = warning # Remove unnecessary else clause. +dotnet_diagnostic.RCS1214.severity = warning # Unnecessary interpolated string. +dotnet_diagnostic.RCS1225.severity = warning # Make class sealed. +dotnet_diagnostic.RCS1232.severity = warning # Order elements in documentation comment. + +# Diagnostics elevated as warnings +dotnet_diagnostic.CA1000.severity = warning # Do not declare static members on generic types +dotnet_diagnostic.CA1031.severity = warning # Do not catch general exception types +dotnet_diagnostic.CA1050.severity = none # Declare types in namespaces +dotnet_diagnostic.CA1063.severity = warning # Implement IDisposable correctly +dotnet_diagnostic.CA1064.severity = warning # Exceptions should be public +dotnet_diagnostic.CA1303.severity = warning # Do not pass literals as localized parameters +dotnet_diagnostic.CA1416.severity = warning # Validate platform compatibility +dotnet_diagnostic.CA1508.severity = warning # Avoid dead conditional code +dotnet_diagnostic.CA1852.severity = warning # Sealed classes +dotnet_diagnostic.CA1859.severity = warning # Use concrete types when possible for improved performance +dotnet_diagnostic.CA1860.severity = warning # Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance +dotnet_diagnostic.CA2000.severity = warning # Call System.IDisposable.Dispose on object before all references to it are out of scope +dotnet_diagnostic.CA2007.severity = error # Do not directly await a Task +dotnet_diagnostic.CA2201.severity = warning # Exception type System.Exception is not sufficiently specific +dotnet_diagnostic.CA2225.severity = warning # Operator overloads have named alternates + +dotnet_diagnostic.IDE0001.severity = warning # Simplify name +dotnet_diagnostic.IDE0005.severity = warning # Remove unnecessary using directives +dotnet_diagnostic.IDE1006.severity = warning # Code style errors, e.g. dotnet_naming_rule rules violations +dotnet_diagnostic.IDE0009.severity = warning # Add this or Me qualification +dotnet_diagnostic.IDE0011.severity = warning # Add braces +dotnet_diagnostic.IDE0018.severity = warning # Inline variable declaration +dotnet_diagnostic.IDE0032.severity = warning # Use auto-implemented property +dotnet_diagnostic.IDE0034.severity = warning # Simplify 'default' expression +dotnet_diagnostic.IDE0035.severity = warning # Remove unreachable code +dotnet_diagnostic.IDE0040.severity = warning # Add accessibility modifiers +dotnet_diagnostic.IDE0049.severity = warning # Use language keywords instead of framework type names for type references +dotnet_diagnostic.IDE0050.severity = warning # Convert anonymous type to tuple +dotnet_diagnostic.IDE0051.severity = warning # Remove unused private member +dotnet_diagnostic.IDE0055.severity = warning # Formatting rule +dotnet_diagnostic.IDE0060.severity = warning # Remove unused parameter +dotnet_diagnostic.IDE0070.severity = warning # Use 'System.HashCode.Combine' +dotnet_diagnostic.IDE0071.severity = warning # Simplify interpolation +dotnet_diagnostic.IDE0073.severity = warning # Require file header +dotnet_diagnostic.IDE0082.severity = warning # Convert typeof to nameof +dotnet_diagnostic.IDE0090.severity = warning # Simplify new expression +dotnet_diagnostic.IDE0130.severity = none # Namespace does not match folder structure +dotnet_diagnostic.IDE0161.severity = warning # Use file-scoped namespace + +dotnet_diagnostic.RCS1032.severity = warning # Remove redundant parentheses. +dotnet_diagnostic.RCS1118.severity = warning # Mark local variable as const. +dotnet_diagnostic.RCS1141.severity = warning # Add 'param' element to documentation comment. +dotnet_diagnostic.RCS1197.severity = warning # Optimize StringBuilder.AppendLine call. +dotnet_diagnostic.RCS1205.severity = warning # Order named arguments according to the order of parameters. +dotnet_diagnostic.RCS1229.severity = warning # Use async/await when necessary. + +dotnet_diagnostic.VSTHRD111.severity = error # Use .ConfigureAwait(bool) + +# Suppressed diagnostics + +# Commented out because `dotnet format` change can be disruptive. +# dotnet_diagnostic.RCS1085.severity = warning # Use auto-implemented property. + +# Commented out because `dotnet format` removes the xmldoc element, while we should add the missing documentation instead. +# dotnet_diagnostic.RCS1228.severity = warning # Unused element in documentation comment. + +dotnet_diagnostic.CA1002.severity = none # Change 'List' in '...' to use 'Collection' ... +dotnet_diagnostic.CA1032.severity = none # We're using RCS1194 which seems to cover more ctors +dotnet_diagnostic.CA1034.severity = none # Do not nest type. Alternatively, change its accessibility so that it is not externally visible +dotnet_diagnostic.CA1054.severity = none # URI parameters should not be strings +dotnet_diagnostic.CA1062.severity = none # Disable null check, C# already does it for us +dotnet_diagnostic.CS1591.severity = none # Missing XML comment for publicly visible type or member +dotnet_diagnostic.CA1805.severity = none # Member is explicitly initialized to its default value +dotnet_diagnostic.CA1822.severity = none # Member does not access instance data and can be marked as static +dotnet_diagnostic.CA1848.severity = none # For improved performance, use the LoggerMessage delegates +dotnet_diagnostic.CA2227.severity = none # Change to be read-only by removing the property setter +dotnet_diagnostic.CA2253.severity = none # Named placeholders in the logging message template should not be comprised of only numeric characters +dotnet_diagnostic.RCS1021.severity = none # Use expression-bodied lambda. +dotnet_diagnostic.RCS1061.severity = none # Merge 'if' with nested 'if'. +dotnet_diagnostic.RCS1069.severity = none # Remove unnecessary case label. +dotnet_diagnostic.RCS1074.severity = none # Remove redundant constructor. +dotnet_diagnostic.RCS1077.severity = none # Optimize LINQ method call. +dotnet_diagnostic.RCS1124.severity = none # Inline local variable. +dotnet_diagnostic.RCS1129.severity = none # Remove redundant field initialization. +dotnet_diagnostic.RCS1140.severity = none # Add exception to documentation comment. +dotnet_diagnostic.RCS1142.severity = none # Add 'typeparam' element to documentation comment. +dotnet_diagnostic.RCS1146.severity = none # Use conditional access. +dotnet_diagnostic.RCS1151.severity = none # Remove redundant cast. +dotnet_diagnostic.RCS1158.severity = none # Static member in generic type should use a type parameter. +dotnet_diagnostic.RCS1161.severity = none # Enum should declare explicit value +dotnet_diagnostic.RCS1163.severity = none # Unused parameter 'foo'. +dotnet_diagnostic.RCS1170.severity = none # Use read-only auto-implemented property. +dotnet_diagnostic.RCS1173.severity = none # Use coalesce expression instead of 'if'. +dotnet_diagnostic.RCS1181.severity = none # Convert comment to documentation comment. +dotnet_diagnostic.RCS1186.severity = none # Use Regex instance instead of static method. +dotnet_diagnostic.RCS1188.severity = none # Remove redundant auto-property initialization. +dotnet_diagnostic.RCS1189.severity = none # Add region name to #endregion. +dotnet_diagnostic.RCS1201.severity = none # Use method chaining. +dotnet_diagnostic.RCS1212.severity = none # Remove redundant assignment. +dotnet_diagnostic.RCS1217.severity = none # Convert interpolated string to concatenation. +dotnet_diagnostic.RCS1222.severity = none # Merge preprocessor directives. +dotnet_diagnostic.RCS1226.severity = none # Add paragraph to documentation comment. +dotnet_diagnostic.RCS1234.severity = none # Enum duplicate value +dotnet_diagnostic.RCS1238.severity = none # Avoid nested ?: operators. +dotnet_diagnostic.RCS1241.severity = none # Implement IComparable when implementing IComparable. +dotnet_diagnostic.IDE0001.severity = none # Simplify name +dotnet_diagnostic.IDE0002.severity = none # Simplify member access +dotnet_diagnostic.IDE0004.severity = none # Remove unnecessary cast +dotnet_diagnostic.IDE0035.severity = none # Remove unreachable code +dotnet_diagnostic.IDE0051.severity = none # Remove unused private member +dotnet_diagnostic.IDE0052.severity = none # Remove unread private member +dotnet_diagnostic.IDE0058.severity = none # Remove unused expression value +dotnet_diagnostic.IDE0059.severity = none # Unnecessary assignment of a value +dotnet_diagnostic.IDE0060.severity = none # Remove unused parameter +dotnet_diagnostic.IDE0080.severity = none # Remove unnecessary suppression operator +dotnet_diagnostic.IDE0100.severity = none # Remove unnecessary equality operator +dotnet_diagnostic.IDE0110.severity = none # Remove unnecessary discards +dotnet_diagnostic.IDE0032.severity = none # Use auto property +dotnet_diagnostic.IDE0160.severity = none # Use block-scoped namespace +dotnet_diagnostic.VSTHRD111.severity = none # Use .ConfigureAwait(bool) is hidden by default, set to none to prevent IDE from changing on autosave +dotnet_diagnostic.xUnit1004.severity = none # Test methods should not be skipped. Remove the Skip property to start running the test again. + +dotnet_diagnostic.SKEXP0003.severity = none # XYZ is for evaluation purposes only +dotnet_diagnostic.SKEXP0010.severity = none +dotnet_diagnostic.SKEXP0010.severity = none +dotnet_diagnostic.SKEXP0011.severity = none +dotnet_diagnostic.SKEXP0052.severity = none +dotnet_diagnostic.SKEXP0101.severity = none + +dotnet_diagnostic.KMEXP00.severity = none # XYZ is for evaluation purposes only +dotnet_diagnostic.KMEXP01.severity = none +dotnet_diagnostic.KMEXP02.severity = none +dotnet_diagnostic.KMEXP03.severity = none + +############################### +# C# Coding Conventions # +############################### + +# var preferences +csharp_style_var_for_built_in_types = false:none +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = false:none +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +# Modifier preferences +csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:suggestion +# Expression-level preferences +csharp_prefer_braces = true:error +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:error +csharp_style_inlined_variable_declaration = true:suggestion + +############################### +# C# Formatting Rules # +############################### + +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = false # Does not work with resharper, forcing code to be on long lines instead of wrapping +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation preferences +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true +csharp_using_directive_placement = outside_namespace:warning +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = file_scoped:warning +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent + +############################### +# Global Naming Conventions # +############################### + +# Styles + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +dotnet_naming_style.static_underscored.capitalization = camel_case +dotnet_naming_style.static_underscored.required_prefix = s_ + +dotnet_naming_style.underscored.capitalization = camel_case +dotnet_naming_style.underscored.required_prefix = _ + +dotnet_naming_style.uppercase_with_underscore_separator.capitalization = all_upper +dotnet_naming_style.uppercase_with_underscore_separator.word_separator = _ + +dotnet_naming_style.end_in_async.required_prefix = +dotnet_naming_style.end_in_async.required_suffix = Async +dotnet_naming_style.end_in_async.capitalization = pascal_case +dotnet_naming_style.end_in_async.word_separator = + +# Symbols + +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + +dotnet_naming_symbols.local_constant.applicable_kinds = local +dotnet_naming_symbols.local_constant.applicable_accessibilities = * +dotnet_naming_symbols.local_constant.required_modifiers = const + +dotnet_naming_symbols.private_constant_fields.applicable_kinds = field +dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private +dotnet_naming_symbols.private_constant_fields.required_modifiers = const + +dotnet_naming_symbols.private_static_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private +dotnet_naming_symbols.private_static_fields.required_modifiers = static + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private + +dotnet_naming_symbols.any_async_methods.applicable_kinds = method +dotnet_naming_symbols.any_async_methods.applicable_accessibilities = * +dotnet_naming_symbols.any_async_methods.required_modifiers = async + +# Rules + +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = error + +dotnet_naming_rule.local_constant_should_be_pascal_case.symbols = local_constant +dotnet_naming_rule.local_constant_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.local_constant_should_be_pascal_case.severity = error + +dotnet_naming_rule.private_constant_fields.symbols = private_constant_fields +dotnet_naming_rule.private_constant_fields.style = pascal_case_style +dotnet_naming_rule.private_constant_fields.severity = error + +dotnet_naming_rule.private_static_fields_underscored.symbols = private_static_fields +dotnet_naming_rule.private_static_fields_underscored.style = static_underscored +dotnet_naming_rule.private_static_fields_underscored.severity = error + +dotnet_naming_rule.private_fields_underscored.symbols = private_fields +dotnet_naming_rule.private_fields_underscored.style = underscored +dotnet_naming_rule.private_fields_underscored.severity = error + +##################################################################################################### + + +resharper_check_namespace_highlighting = none + +#resharper_inconsistent_naming_highlighting = none +#resharper_comment_typo_highlighting = none + + + + diff --git a/aspire-orchestrator/Aspire.AppHost/.gitignore b/aspire-orchestrator/Aspire.AppHost/.gitignore new file mode 100644 index 00000000..8e843802 --- /dev/null +++ b/aspire-orchestrator/Aspire.AppHost/.gitignore @@ -0,0 +1 @@ +.azure diff --git a/aspire-orchestrator/Aspire.AppHost/Aspire.AppHost.csproj b/aspire-orchestrator/Aspire.AppHost/Aspire.AppHost.csproj new file mode 100644 index 00000000..009dc84e --- /dev/null +++ b/aspire-orchestrator/Aspire.AppHost/Aspire.AppHost.csproj @@ -0,0 +1,28 @@ + + + + + + Exe + net8.0 + enable + enable + true + b43d9a6d-f7bd-491d-b1f9-82372d537e4e + SemanticWorkbench.Aspire.AppHost + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/aspire-orchestrator/Aspire.AppHost/Program.cs b/aspire-orchestrator/Aspire.AppHost/Program.cs new file mode 100644 index 00000000..2d65a03f --- /dev/null +++ b/aspire-orchestrator/Aspire.AppHost/Program.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All rights reserved. + +var builder = DistributedApplication.CreateBuilder(args); + +var authority = builder.AddParameterFromConfiguration("authority", "EntraId:Authority"); +var clientId = builder.AddParameterFromConfiguration("clientId", "EntraId:ClientId"); + +// Workbench backend +var workbenchService = builder.AddWorkbenchService("workbenchservice", projectDirectory: Path.Combine("..", "..", "workbench-service"), clientId: clientId); +var workbenchServiceEndpoint = workbenchService.GetSemanticWorkbenchEndpoint(builder.ExecutionContext.IsPublishMode); + +// Workbench frontend +var workbenchApp = builder.AddViteApp("workbenchapp", workingDirectory: Path.Combine("..", "..", "workbench-app"), packageManager: "pnpm") + .WithPnpmPackageInstallation() + .WithEnvironment(name: "VITE_SEMANTIC_WORKBENCH_SERVICE_URL", workbenchServiceEndpoint) + .WaitFor(workbenchService) + .PublishAsDockerFile([ + new DockerBuildArg("VITE_SEMANTIC_WORKBENCH_CLIENT_ID", clientId.Resource.Value), + new DockerBuildArg("VITE_SEMANTIC_WORKBENCH_AUTHORITY", authority.Resource.Value), + new DockerBuildArg("SSHD_ENABLED", "false") + ]); + +// Sample Python agent +builder.AddAssistantApp("skill-assistant", projectDirectory: Path.Combine("..", "..", "assistants", "skill-assistant"), assistantModuleName: "skill-assistant") + .WithEnvironment(name: "assistant__workbench_service_url", workbenchServiceEndpoint); + +// Sample .NET agent +builder.AddProject("simple-chatbot-dotnet") + .WithReference(workbenchServiceEndpoint) + .WaitFor(workbenchService) + .WithEnvironment(name: "Workbench__WorkbenchEndpoint", workbenchServiceEndpoint); + +if (!builder.ExecutionContext.IsPublishMode) +{ + workbenchApp.WithHttpsEndpoint(env: "PORT"); +} + +builder.Build().Run(); diff --git a/aspire-orchestrator/Aspire.AppHost/Properties/launchSettings.json b/aspire-orchestrator/Aspire.AppHost/Properties/launchSettings.json new file mode 100644 index 00000000..f136226d --- /dev/null +++ b/aspire-orchestrator/Aspire.AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17149;http://localhost:15061", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21236", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22204" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15061", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19243", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20261" + } + } + } +} diff --git a/aspire-orchestrator/Aspire.AppHost/appsettings.json b/aspire-orchestrator/Aspire.AppHost/appsettings.json new file mode 100644 index 00000000..71a9a93d --- /dev/null +++ b/aspire-orchestrator/Aspire.AppHost/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + }, + "EntraID": { + "ClientId": "22cb77c3-ca98-4a26-b4db-ac4dcecba690", + "Authority": "https://login.microsoftonline.com/common" + } +} diff --git a/aspire-orchestrator/Aspire.Extensions/Aspire.Extensions.csproj b/aspire-orchestrator/Aspire.Extensions/Aspire.Extensions.csproj new file mode 100644 index 00000000..c14c91ab --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/Aspire.Extensions.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + semantic workbench extensions + An Aspire integration for Semantic Workbench. + Aspire.Hosting.Extensions + + + + + + + + \ No newline at end of file diff --git a/aspire-orchestrator/Aspire.Extensions/DockerFileExtensions.cs b/aspire-orchestrator/Aspire.Extensions/DockerFileExtensions.cs new file mode 100644 index 00000000..940b7de7 --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/DockerFileExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Aspire.Hosting; + +public static class DockerFileExtensions +{ + public static IResourceBuilder PublishAsDockerImage(this IResourceBuilder builder, + string? dockerContext = null, + string? dockerFilePath = "Dockerfile", + Action>? configure = null) + { + if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode) + { + return builder; + } + + // Bait and switch the ExecutableResource with a ContainerResource + builder.ApplicationBuilder.Resources.Remove(builder.Resource); + + var container = new ExecutableContainerResource(builder.Resource); + var cb = builder.ApplicationBuilder.AddResource(container); + cb.WithImage(builder.Resource.Name); + cb.WithDockerfile(contextPath: dockerContext ?? builder.Resource.WorkingDirectory, dockerfilePath: dockerFilePath); + // Clear the runtime args + cb.WithArgs(c => c.Args.Clear()); + + configure?.Invoke(cb); + + return builder; + } + + private sealed class ExecutableContainerResource(ExecutableResource er) : ContainerResource(er.Name) + { + public override ResourceAnnotationCollection Annotations => er.Annotations; + } +} diff --git a/aspire-orchestrator/Aspire.Extensions/PathNormalizer.cs b/aspire-orchestrator/Aspire.Extensions/PathNormalizer.cs new file mode 100644 index 00000000..3fe4ef79 --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/PathNormalizer.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Aspire.Hosting.Extensions; + +internal static class PathNormalizer +{ + /// + /// Normalizes the path for the current platform. + /// + /// The path value. + /// Returns the normalized path value for the current platform. + public static string NormalizePathForCurrentPlatform(this string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return path; + } + + // Fix slashes + path = path.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar); + + return Path.GetFullPath(path); + } +} \ No newline at end of file diff --git a/aspire-orchestrator/Aspire.Extensions/UvAppHostingExtensions.cs b/aspire-orchestrator/Aspire.Extensions/UvAppHostingExtensions.cs new file mode 100644 index 00000000..2dcbdd32 --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/UvAppHostingExtensions.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Aspire.Hosting.ComponentModel; +using Aspire.Hosting.Extensions; + +namespace Aspire.Hosting; + +public static class UvAppHostingExtensions +{ + public static IResourceBuilder AddUvApp( + this IDistributedApplicationBuilder builder, + string name, + string projectDirectory, + string scriptPath, + params string[] scriptArgs) + { + ArgumentNullException.ThrowIfNull(builder); + + return builder.AddUvApp(name, scriptPath, projectDirectory, ".venv", scriptArgs); + } + + private static IResourceBuilder AddUvApp(this IDistributedApplicationBuilder builder, + string name, + string scriptPath, + string projectDirectory, + string virtualEnvironmentPath, + params string[] args) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(scriptPath); + + string wd = projectDirectory ?? Path.Combine("..", name); + + projectDirectory = PathNormalizer.NormalizePathForCurrentPlatform(Path.Combine(builder.AppHostDirectory, wd)); + + var virtualEnvironment = new VirtualEnvironment(Path.IsPathRooted(virtualEnvironmentPath) + ? virtualEnvironmentPath + : Path.Join(projectDirectory, virtualEnvironmentPath)); + + var instrumentationExecutable = virtualEnvironment.GetExecutable("opentelemetry-instrument"); + // var pythonExecutable = virtualEnvironment.GetRequiredExecutable("python"); + // var projectExecutable = instrumentationExecutable ?? pythonExecutable; + + string[] allArgs = args is { Length: > 0 } + ? ["run", scriptPath, .. args] + : ["run", scriptPath]; + + var projectResource = new UvAppResource(name, projectDirectory); + + var resourceBuilder = builder.AddResource(projectResource) + .WithArgs(allArgs) + .WithArgs(context => + { + // If the project is to be automatically instrumented, add the instrumentation executable arguments first. + if (!string.IsNullOrEmpty(instrumentationExecutable)) + { + AddOpenTelemetryArguments(context); + + // // Add the python executable as the next argument so we can run the project. + // context.Args.Add(pythonExecutable!); + } + }); + + if (!string.IsNullOrEmpty(instrumentationExecutable)) + { + resourceBuilder.WithOtlpExporter(); + + // Make sure to attach the logging instrumentation setting, so we can capture logs. + // Without this you'll need to configure logging yourself. Which is kind of a pain. + resourceBuilder.WithEnvironment("OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED", "true"); + } + + return resourceBuilder; + } + + private static void AddOpenTelemetryArguments(CommandLineArgsCallbackContext context) + { + context.Args.Add("--traces_exporter"); + context.Args.Add("otlp"); + + context.Args.Add("--logs_exporter"); + context.Args.Add("console,otlp"); + + context.Args.Add("--metrics_exporter"); + context.Args.Add("otlp"); + } +} diff --git a/aspire-orchestrator/Aspire.Extensions/UvAppResource.cs b/aspire-orchestrator/Aspire.Extensions/UvAppResource.cs new file mode 100644 index 00000000..d807059c --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/UvAppResource.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Aspire.Hosting.ComponentModel; + +public class UvAppResource(string name, string workingDirectory) + : ExecutableResource(name, "uv", workingDirectory), IResourceWithServiceDiscovery +{ + internal const string HttpEndpointName = "http"; +} diff --git a/aspire-orchestrator/Aspire.Extensions/VirtualEnvironment.cs b/aspire-orchestrator/Aspire.Extensions/VirtualEnvironment.cs new file mode 100644 index 00000000..d2c7b351 --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/VirtualEnvironment.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Aspire.Hosting.ComponentModel; + +internal sealed class VirtualEnvironment(string virtualEnvironmentPath) +{ + /// + /// Locates an executable in the virtual environment. + /// + /// The name of the executable. + /// Returns the path to the executable if it exists in the virtual environment. + public string? GetExecutable(string name) + { + if (OperatingSystem.IsWindows()) + { + string[] allowedExtensions = [".exe", ".cmd", ".bat"]; + + return allowedExtensions + .Select(allowedExtension => Path.Join(virtualEnvironmentPath, "Scripts", name + allowedExtension)) + .FirstOrDefault(File.Exists); + } + + var executablePath = Path.Join(virtualEnvironmentPath, "bin", name); + return File.Exists(executablePath) ? executablePath : null; + } + + /// + /// Locates a required executable in the virtual environment. + /// + /// The name of the executable. + /// The path to the executable in the virtual environment. + /// Gets thrown when the executable couldn't be located. + public string GetRequiredExecutable(string name) + { + return this.GetExecutable(name) ?? throw new DistributedApplicationException( + $"The executable {name} could not be found in the virtual environment at '{virtualEnvironmentPath}' . " + + "Make sure the virtual environment is initialized and the executable is installed."); + } +} diff --git a/aspire-orchestrator/Aspire.Extensions/WorkbenchServiceHostingExtensions.cs b/aspire-orchestrator/Aspire.Extensions/WorkbenchServiceHostingExtensions.cs new file mode 100644 index 00000000..b34dca97 --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/WorkbenchServiceHostingExtensions.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Aspire.Hosting; + +public static class WorkbenchServiceHostingExtensions +{ + public static IResourceBuilder AddWorkbenchService( + this IDistributedApplicationBuilder builder, + string name, + string projectDirectory, + IResourceBuilder clientId, + params string[] scriptArgs) + { + ArgumentNullException.ThrowIfNull(builder); + + var workbenchService = builder.AddUvApp(name, projectDirectory, "start-semantic-workbench-service", scriptArgs) + .PublishAsDockerImage(dockerContext: Path.Combine("..", ".."), + dockerFilePath: Path.Combine("workbench-service", "Dockerfile"), + configure: new(configure => configure + .WithBuildArg("SSHD_ENABLED", "false"))) + .WithEnvironment(name: "WORKBENCH__AUTH__ALLOWED_APP_ID", clientId.Resource.Value); + if (builder.ExecutionContext.IsPublishMode) + { + workbenchService.WithHttpsEndpoint(port: 3000); + } + else + { + workbenchService.WithHttpEndpoint(env: "PORT"); + } + + workbenchService.WithExternalHttpEndpoints(); + + return workbenchService; + } + + public static EndpointReference GetSemanticWorkbenchEndpoint(this IResourceBuilder workbenchService, bool isPublishMode) + { + ArgumentNullException.ThrowIfNull(workbenchService); + + return workbenchService.GetEndpoint(isPublishMode ? "https" : "http"); + } + + public static IResourceBuilder AddAssistantApp( + this IDistributedApplicationBuilder builder, + string name, + string projectDirectory, + string assistantModuleName) + { + ArgumentNullException.ThrowIfNull(builder); + + var assistant = builder.AddUvApp(name, projectDirectory, "start-assistant") + .PublishAsDockerImage(dockerContext: Path.Combine("..", ".."), + dockerFilePath: Path.Combine("tools", "docker", "Dockerfile.assistant"), + configure: new(configure => configure + .WithBuildArg("package", assistantModuleName) + .WithBuildArg("app", $"assistant.{assistantModuleName.Replace('-', '_')}:app") + )); + if (builder.ExecutionContext.IsPublishMode) + { + assistant.WithHttpEndpoint(port: 3001, env: "ASSISTANT__PORT"); + } + else + { + assistant.WithHttpEndpoint(env: "ASSISTANT__PORT"); + } + + var assistantEndpoint = assistant.GetEndpoint("http"); + assistant.WithEnvironment(name: "assistant__assistant_service_url", assistantEndpoint); + + return assistant; + } +} diff --git a/aspire-orchestrator/Aspire.ServiceDefaults/Aspire.ServiceDefaults.csproj b/aspire-orchestrator/Aspire.ServiceDefaults/Aspire.ServiceDefaults.csproj new file mode 100644 index 00000000..ddf72422 --- /dev/null +++ b/aspire-orchestrator/Aspire.ServiceDefaults/Aspire.ServiceDefaults.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + true + SemanticWorkbench.Aspire.ServiceDefaults + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/aspire-orchestrator/Aspire.ServiceDefaults/Extensions.cs b/aspire-orchestrator/Aspire.ServiceDefaults/Extensions.cs new file mode 100644 index 00000000..4397a884 --- /dev/null +++ b/aspire-orchestrator/Aspire.ServiceDefaults/Extensions.cs @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace SemanticWorkbench.Aspire.ServiceDefaults; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation() + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} diff --git a/aspire-orchestrator/README.md b/aspire-orchestrator/README.md new file mode 100644 index 00000000..0cefccd6 --- /dev/null +++ b/aspire-orchestrator/README.md @@ -0,0 +1,54 @@ +# .NET Aspire Integratiuon + +## Run Prerequisites + +- [dotnet (8 or greater)](https://dotnet.microsoft.com/en-us/download) + +## Deployment Prerequisites + +- [Azure Developer CLI](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd?tabs=winget-windows%2Cbrew-mac%2Cscript-linux) +- [dotnet (9 or greater)](https://dotnet.microsoft.com/en-us/download) +- [Python 3.8 or greater](https://www.python.org/downloads/) +- [NodeJs v18.12 or greater](https://nodejs.org/en/download/) + +## Run with .NET Aspire + +1. Make sure you have accepted the [dotnet trust dev certificate](https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-9.0&tabs=visual-studio%2Clinux-sles#trust-the-aspnet-core-https-development-certificate) +```bash +dotnet dev-certs https --trust +``` + +2. Clone the repository + +```bash +make +cd aspire-orchestrator +cd Aspire.AppHost +dotnet run +``` + +## Deployment Steps with azd + +1. Clone the repository +2. In the root folder, run +```bash +make +``` +3. [Configure the app registration and configure workbenchapp and workbench service](../docs/CUSTOM_APP_REGISTRATION.md). Set the values in the `aspire-orchestrator/aspire-orchestrator/aspire-orchestrator/aspire-orchestrator/appsettings.json` file +```json +{ + "EntraID": { + "ClientId": "", + "Authority": "https://login.microsoftonline.com/" + } +} +``` +4. Authenticate with Azure Developer CLI +```bash +azd login +``` +5. Create the infrastructure and deploy the application +```bash +azd up +``` +6. Update the App Registration with the correct workbenchapp redirect URI \ No newline at end of file diff --git a/aspire-orchestrator/SemanticWorkbench.Aspire.sln b/aspire-orchestrator/SemanticWorkbench.Aspire.sln new file mode 100644 index 00000000..8ef9b393 --- /dev/null +++ b/aspire-orchestrator/SemanticWorkbench.Aspire.sln @@ -0,0 +1,48 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.0.0 +MinimumVisualStudioVersion = 17.8.0.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.AppHost", "Aspire.AppHost\Aspire.AppHost.csproj", "{85D9E2AF-8F25-4006-A375-A66E2259CB69}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.ServiceDefaults", "Aspire.ServiceDefaults\Aspire.ServiceDefaults.csproj", "{1A1BA42B-F071-42ED-BF6D-4BD51F2D7A41}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-03-simple-chatbot", "..\examples\dotnet\dotnet-03-simple-chatbot\dotnet-03-simple-chatbot.csproj", "{670F22E3-71CC-4A9B-8BB8-DA855CFC0EEC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Extensions", "Aspire.Extensions\Aspire.Extensions.csproj", "{773468A0-6628-4D14-8EE8-7C3F790B6192}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkbenchConnector", "..\libraries\dotnet\WorkbenchConnector\WorkbenchConnector.csproj", "{A4B94C9F-C917-4193-99CC-10E694A8A594}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {85D9E2AF-8F25-4006-A375-A66E2259CB69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85D9E2AF-8F25-4006-A375-A66E2259CB69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85D9E2AF-8F25-4006-A375-A66E2259CB69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85D9E2AF-8F25-4006-A375-A66E2259CB69}.Release|Any CPU.Build.0 = Release|Any CPU + {1A1BA42B-F071-42ED-BF6D-4BD51F2D7A41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A1BA42B-F071-42ED-BF6D-4BD51F2D7A41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A1BA42B-F071-42ED-BF6D-4BD51F2D7A41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A1BA42B-F071-42ED-BF6D-4BD51F2D7A41}.Release|Any CPU.Build.0 = Release|Any CPU + {670F22E3-71CC-4A9B-8BB8-DA855CFC0EEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {670F22E3-71CC-4A9B-8BB8-DA855CFC0EEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {670F22E3-71CC-4A9B-8BB8-DA855CFC0EEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {670F22E3-71CC-4A9B-8BB8-DA855CFC0EEC}.Release|Any CPU.Build.0 = Release|Any CPU + {773468A0-6628-4D14-8EE8-7C3F790B6192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {773468A0-6628-4D14-8EE8-7C3F790B6192}.Debug|Any CPU.Build.0 = Debug|Any CPU + {773468A0-6628-4D14-8EE8-7C3F790B6192}.Release|Any CPU.ActiveCfg = Release|Any CPU + {773468A0-6628-4D14-8EE8-7C3F790B6192}.Release|Any CPU.Build.0 = Release|Any CPU + {A4B94C9F-C917-4193-99CC-10E694A8A594}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4B94C9F-C917-4193-99CC-10E694A8A594}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4B94C9F-C917-4193-99CC-10E694A8A594}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4B94C9F-C917-4193-99CC-10E694A8A594}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {30B8E5F7-972C-4722-B57A-0626109F80B6} + EndGlobalSection +EndGlobal diff --git a/aspire-orchestrator/SemanticWorkbench.Aspire.sln.DotSettings b/aspire-orchestrator/SemanticWorkbench.Aspire.sln.DotSettings new file mode 100644 index 00000000..e0cd1b82 --- /dev/null +++ b/aspire-orchestrator/SemanticWorkbench.Aspire.sln.DotSettings @@ -0,0 +1,304 @@ + + False + False + True + True + FullFormat + True + True + True + True + True + SOLUTION + False + SUGGESTION + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + SUGGESTION + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + True + Field, Property, Event, Method + True + True + True + NEXT_LINE + True + True + True + True + True + True + 1 + 1 + True + True + True + ALWAYS + True + True + False + 512 + True + Copyright (c) Microsoft. All rights reserved. + ACS + AI + AIGPT + AMQP + API + BOM + CORS + DB + DI + GPT + GRPC + HMAC + HTTP + IM + IO + IOS + JSON + JWT + MQ + MQTT + MS + MSAL + OCR + OID + OK + OS + PR + QA + SHA + SK + SKHTTP + SSL + TTL + UI + UID + URL + XML + YAML + False + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB"><ExtraRule Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="Async" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="Async" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="Async" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="Async" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="Async" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local variables"><ElementKinds><Kind Name="LOCAL_VARIABLE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="Async" Style="aaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local functions"><ElementKinds><Kind Name="LOCAL_FUNCTION" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="Async" Style="AaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Methods"><ElementKinds><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="Async" Style="AaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Parameters"><ElementKinds><Kind Name="PARAMETER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="Async" Style="aaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"><ElementKinds><Kind Name="LOCAL_CONSTANT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB"><ExtraRule Prefix="" Suffix="" Style="aaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="Async" Style="AaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" /></Policy> + + 2 + False + True + Console + PushToShowHints + True + True + True + True + True + True + True + True + True + True + True + True + False + TRACE + 8201 + Automatic + True + True + True + True + True + 2.0 + InCSharpFile + pragma + True + #pragma warning disable CA0000 // reason + +#pragma warning restore CA0000 + True + True + False + True + guid() + 0 + True + True + False + False + True + 2.0 + InCSharpFile + aaa + True + [Fact] +public void It$SOMENAME$() +{ + // Arrange + + // Act + + // Assert + +} + True + True + MSFT copyright + True + 2.0 + InCSharpFile + copy + // Copyright (c) Microsoft. All rights reserved. + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + DO_NOT_SHOW + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + diff --git a/aspire-orchestrator/run.sh b/aspire-orchestrator/run.sh new file mode 100755 index 00000000..3a178add --- /dev/null +++ b/aspire-orchestrator/run.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +set -e + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/" +cd "$HERE" + +# Check node version, it must be major version 20 (any minor), otherwise show an error and exit +NODE_VERSION=$(node -v) +if [[ ! $NODE_VERSION =~ ^v(1[8-9]|[2-9][0-9]).* ]]; then + echo "Node version is $NODE_VERSION, expected 18.x.x or higher." + + # Attempt to source nvm + if [ -s "$NVM_DIR/nvm.sh" ]; then + . "$NVM_DIR/nvm.sh" + elif [ -s "$HOME/.nvm/nvm.sh" ]; then + export NVM_DIR="$HOME/.nvm" + . "$NVM_DIR/nvm.sh" + else + echo "nvm not found. Please install Node 18 or higher manually." + echo "See also README.md for instructions." + exit 1 + fi + + echo "Installing latest LTS Node version via nvm..." + nvm install --lts + nvm use --lts + + NODE_VERSION=$(node -v) + if [[ ! $NODE_VERSION =~ ^v(1[8-9]|[2-9][0-9]).* ]]; then + echo "Failed to switch to Node 18 or higher via nvm. You have $NODE_VERSION." + exit 1 + fi +fi + +cd Aspire.AppHost + +dotnet run \ No newline at end of file diff --git a/tools/docker/Dockerfile.aspire.assistant b/tools/docker/Dockerfile.aspire.assistant new file mode 100644 index 00000000..db0de017 --- /dev/null +++ b/tools/docker/Dockerfile.aspire.assistant @@ -0,0 +1,43 @@ +# syntax=docker/dockerfile:1 + +# Dockerfile for assistants +# Context root is expected to be the root of the repo +ARG python_image=python:3.11-slim + +# These build arguments will differ per assistant: +# package is the directory name of the assistant package under /assistants +ARG package +ARG app + +FROM ${python_image} AS build + +ARG package + +COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv + +# copy all library packages +COPY ./libraries/python /packages/libraries/python +# copy the assistant package +COPY ./assistants/${package} /packages/assistants/assistant + +# install the assistant and dependencies to /.venv +RUN uv sync --directory /packages/assistants/assistant --no-editable --no-dev --locked + +FROM ${python_image} + +ARG app + +COPY --from=build /packages/assistants/assistant/.venv /packages/assistants/assistant/.venv +ENV PATH=/packages/assistants/assistant/.venv/bin:$PATH + +COPY ./tools/docker/docker-entrypoint.sh /scripts/docker-entrypoint.sh +RUN chmod +x /scripts/docker-entrypoint.sh + +ENV ASSISTANT_APP=${app} + +ENV assistant__host=0.0.0.0 +ENV assistant__port=3001 + +SHELL ["/bin/bash", "-c"] +ENTRYPOINT ["/scripts/docker-entrypoint.sh"] +CMD ["start-assistant"] diff --git a/workbench-app/.dockerignore b/workbench-app/.dockerignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/workbench-app/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/workbench-app/Dockerfile b/workbench-app/Dockerfile new file mode 100644 index 00000000..31e5d490 --- /dev/null +++ b/workbench-app/Dockerfile @@ -0,0 +1,39 @@ +# Stage 1: Build the React application +FROM node:18-alpine AS build +WORKDIR /app + +# Install pnpm +RUN npm install -g pnpm + +# Copy package.json and pnpm-lock.yaml +COPY package.json pnpm-lock.yaml ./ + +# Install dependencies +RUN pnpm install + +# Copy the rest of the application code +COPY . . + +# Build the application +RUN pnpm run build + +# Stage 2: Serve the app with Nginx +FROM nginx:alpine +RUN apk add --no-cache gettext +WORKDIR /usr/share/nginx/html + +# Remove default Nginx static files +RUN rm -rf ./* + +# Copy build artifacts from the previous stage +COPY --from=build /app/build . + +# Copy custom entrypoint script +COPY docker-entrypoint.sh /usr/bin/ +RUN chmod +x /usr/bin/docker-entrypoint.sh + +# Copy Nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf + +ENTRYPOINT ["docker-entrypoint.sh"] +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/workbench-app/docker-entrypoint.sh b/workbench-app/docker-entrypoint.sh new file mode 100644 index 00000000..8e21bf51 --- /dev/null +++ b/workbench-app/docker-entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# Allow static build of REact code to access env vars +# SEE https://create-react-app.dev/docs/title-and-meta-tags/#injecting-data-from-the-server-into-the-page +# Replace placeholders in static files with environment variables +envsubst '$VITE_SEMANTIC_WORKBENCH_SERVICE_URL' < /usr/share/nginx/html/index.html > /usr/share/nginx/html/index.html.tmp +mv /usr/share/nginx/html/index.html.tmp /usr/share/nginx/html/index.html + +envsubst '$PORT' < /etc/nginx/conf.d/default.conf > /etc/nginx/conf.d/default.conf.tmp +mv /etc/nginx/conf.d/default.conf.tmp /etc/nginx/conf.d/default.conf + +echo "VITE_SEMANTIC_WORKBENCH_SERVICE_URL = $VITE_SEMANTIC_WORKBENCH_SERVICE_URL" +echo "PORT = $PORT" + +exec "$@" \ No newline at end of file diff --git a/workbench-app/index.html b/workbench-app/index.html index 1ae0097f..115f2c65 100644 --- a/workbench-app/index.html +++ b/workbench-app/index.html @@ -16,4 +16,9 @@
+ diff --git a/workbench-app/nginx.conf b/workbench-app/nginx.conf new file mode 100644 index 00000000..faa61f18 --- /dev/null +++ b/workbench-app/nginx.conf @@ -0,0 +1,13 @@ +# Required so that nginx can resolve IPs when working with Docker Compose +resolver 127.0.0.11 ipv6=off; + +index index.html index.htm; + +server { + listen $PORT; + server_name reverseproxy 127.0.0.1; + + location / { + root /usr/share/nginx/html; + } +} \ No newline at end of file diff --git a/workbench-app/src/Constants.ts b/workbench-app/src/Constants.ts index f2fb432c..4627564c 100644 --- a/workbench-app/src/Constants.ts +++ b/workbench-app/src/Constants.ts @@ -1,3 +1,11 @@ +// Allow static build of React code to access env vars +// SEE https://create-react-app.dev/docs/title-and-meta-tags/#injecting-data-from-the-server-into-the-page +const serviceUrl = (window.VITE_SEMANTIC_WORKBENCH_SERVICE_URL && window.VITE_SEMANTIC_WORKBENCH_SERVICE_URL.startsWith('https://')) + ? window.VITE_SEMANTIC_WORKBENCH_SERVICE_URL + : (import.meta.env.VITE_SEMANTIC_WORKBENCH_SERVICE_URL) + ? import.meta.env.VITE_SEMANTIC_WORKBENCH_SERVICE_URL + : 'http://127.0.0.1:3000'; + export const Constants = { app: { name: 'Semantic Workbench', @@ -26,7 +34,7 @@ export const Constants = { id: 'local', name: 'Semantic Workbench backend service on localhost or GitHub Codespaces', // Can be overridden by env var VITE_SEMANTIC_WORKBENCH_SERVICE_URL - url: import.meta.env.VITE_SEMANTIC_WORKBENCH_SERVICE_URL || 'http://localhost:3000', + url: serviceUrl, brand: 'light', }, // { diff --git a/workbench-app/src/global.d.ts b/workbench-app/src/global.d.ts new file mode 100644 index 00000000..fc224a76 --- /dev/null +++ b/workbench-app/src/global.d.ts @@ -0,0 +1,9 @@ +export { }; + +// Allow static build of React code to access env vars +// SEE https://create-react-app.dev/docs/title-and-meta-tags/#injecting-data-from-the-server-into-the-page +declare global { + interface Window { + VITE_SEMANTIC_WORKBENCH_SERVICE_URL?: string; + } +} \ No newline at end of file diff --git a/workbench-service/Dockerfile.aspire b/workbench-service/Dockerfile.aspire new file mode 100644 index 00000000..01d611ac --- /dev/null +++ b/workbench-service/Dockerfile.aspire @@ -0,0 +1,41 @@ +# Note: this is a copy of workbench-service/Dockerfile with the exception of +# SSHD configuration that's been removed. SSHD open port breaks Azure Container +# deployment, plus SSH is not required and a potential issue public facing +# deployments. + +ARG python_image=python:3.11-slim + +FROM ${python_image} AS build + +COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv + +COPY ./libraries/python/semantic-workbench-api-model /libraries/python/semantic-workbench-api-model +COPY ./libraries/python/semantic-workbench-assistant /libraries/python/semantic-workbench-assistant +COPY ./workbench-service /workbench-service +COPY ./workbench-service/.env /workbench-service/.env + +RUN uv sync --directory /workbench-service --no-editable --no-dev --locked + +FROM ${python_image} + +COPY --from=build /workbench-service/.venv /workbench-service/.venv +ENV PATH=/workbench-service/.venv/bin:$PATH + +# alembic migrations related files +COPY ./workbench-service/alembic.ini /workbench-service/alembic.ini +COPY ./workbench-service/migrations /workbench-service/migrations + +COPY ./workbench-service/.env /workbench-service/.env + +# entrypoint script +COPY ./tools/docker/docker-entrypoint.sh /scripts/docker-entrypoint.sh +RUN chmod +x /scripts/docker-entrypoint.sh + +WORKDIR /workbench-service + +ENV workbench__service__host=0.0.0.0 +ENV workbench__service__port=3000 + +SHELL ["/bin/bash", "-c"] +ENTRYPOINT ["/scripts/docker-entrypoint.sh"] +CMD ["start-service"]