├── Floom.Tests ├── Usings.cs ├── Floom.Tests.csproj └── ModelsUnitTest.cs ├── ReadmeAssets ├── 112.jpg ├── 336.jpg ├── 400x.jpg ├── Flow.jpg ├── 224-nn.jpg ├── Define.jpg ├── 60-github.jpg ├── SDK-+-Cost.jpg ├── GitHub-Logo.png ├── 25github-text.jpg ├── 35github-text.jpg ├── Data-+-Updates.jpg ├── PipelineDefintion.jpg ├── Security-+-Safety.jpg ├── YouTube-Thumbnail.jpg └── Caching-Robustness.jpg ├── Floom.Python ├── requirements.txt ├── tests │ └── simple_openai_query.py ├── Dockerfile └── main.py ├── Floom.Core ├── Tests │ ├── Examples │ │ ├── Docs Pipeline │ │ │ ├── docs-model.yml │ │ │ ├── docs-embeddings.yml │ │ │ ├── docs-pipeline.yml │ │ │ ├── docs-vectorstore.yml │ │ │ ├── docs-data.yml │ │ │ ├── docs-prompt.yml │ │ │ └── docs-response.yml │ │ └── CreateImage Pipeline │ │ │ ├── create-image-model.yml │ │ │ ├── create-image-prompt.yml │ │ │ ├── create-image-response.yml │ │ │ └── create-image-pipeline.yml │ ├── Yamls │ │ ├── seperated-ymls │ │ │ ├── main.yml │ │ │ ├── pipeline-v2-chat-decision.yml │ │ │ ├── pipeline-v2-standalone.yml │ │ │ ├── config.yml │ │ │ ├── pipeline-v2-data.yml │ │ │ └── pipeline-v2-prompt.yml │ │ ├── pipeline-v2-chat-decision.yml │ │ ├── pipeline-v1.yml │ │ └── pipeline-v2.yml │ ├── setup.ts │ └── Postman │ │ └── FirePrompt.postman_collection.json ├── Utils │ ├── DocumentManager.cs │ ├── StringEnumConverter.cs │ ├── StringUtils.cs │ ├── HttpContextHelper.cs │ ├── ConfigureSwaggerOptions.cs │ ├── FileUtils.cs │ ├── MongoUtils.cs │ └── Extensions.cs ├── appsettings.Development.json ├── Functions │ ├── FunctionsUtils.cs │ ├── TranslatedField.cs │ ├── FunctionDto.cs │ └── FunctionsService.cs ├── Auth │ ├── AllowAnonymous.cs │ ├── ApiKeyEntity.cs │ ├── ApiKeyUtils.cs │ ├── UserEntity.cs │ ├── ApiKeyInitializer.cs │ ├── ApiKeyAuthorizationAttribute.cs │ └── UsersService.cs ├── Base │ ├── CollectionNameAttribute.cs │ ├── FloomSingletonBase.cs │ └── FloomOperationResult.cs ├── Assets │ ├── AssetEntity.cs │ ├── FloomAsset.cs │ └── FloomAssetsRepository.cs ├── Logs │ └── FloomLoggerFactory.cs ├── Audit │ ├── AuditRowEntity.cs │ └── FloomAuditService.cs ├── Controllers │ ├── UsersController.cs │ ├── MiscController.cs │ ├── AssetsController.cs │ ├── FloomUsernameGenerator.cs │ ├── FunctionsController.cs │ └── AccountController.cs ├── Repository │ ├── IDatabase.cs │ ├── DatabaseEntity.cs │ ├── RepositoryFactory.cs │ ├── FunctionEntity.cs │ ├── DynamoDB │ │ ├── DynamoDbMapper.cs │ │ ├── DynamoDbInitializer.cs │ │ └── DynamoDbDatabase.cs │ ├── Repository.cs │ └── MongoDb │ │ ├── MongoDbInitializer.cs │ │ └── MongoDatabase.cs ├── Dockerfiles │ ├── Dockerfile │ ├── docker-compose.yml │ └── docker-compose-local.yml ├── Config │ ├── MongoConfiguration.cs │ └── DynamoDbConfiguration.cs ├── Floom.Core.sln ├── Floom.Core.csproj ├── Server │ └── DynamicApiRoutingMiddleware.cs └── Program.cs ├── LICENSE ├── .gitignore ├── README.md ├── Floom.sln └── .github └── workflows └── deploy.yml /Floom.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ReadmeAssets/112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/112.jpg -------------------------------------------------------------------------------- /ReadmeAssets/336.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/336.jpg -------------------------------------------------------------------------------- /ReadmeAssets/400x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/400x.jpg -------------------------------------------------------------------------------- /ReadmeAssets/Flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/Flow.jpg -------------------------------------------------------------------------------- /ReadmeAssets/224-nn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/224-nn.jpg -------------------------------------------------------------------------------- /ReadmeAssets/Define.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/Define.jpg -------------------------------------------------------------------------------- /ReadmeAssets/60-github.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/60-github.jpg -------------------------------------------------------------------------------- /ReadmeAssets/SDK-+-Cost.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/SDK-+-Cost.jpg -------------------------------------------------------------------------------- /ReadmeAssets/GitHub-Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/GitHub-Logo.png -------------------------------------------------------------------------------- /ReadmeAssets/25github-text.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/25github-text.jpg -------------------------------------------------------------------------------- /ReadmeAssets/35github-text.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/35github-text.jpg -------------------------------------------------------------------------------- /ReadmeAssets/Data-+-Updates.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/Data-+-Updates.jpg -------------------------------------------------------------------------------- /Floom.Python/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi~=0.111.0 2 | uvicorn~=0.30.1 3 | pydantic~=2.7.2 4 | langchain_openai 5 | mangum -------------------------------------------------------------------------------- /ReadmeAssets/PipelineDefintion.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/PipelineDefintion.jpg -------------------------------------------------------------------------------- /ReadmeAssets/Security-+-Safety.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/Security-+-Safety.jpg -------------------------------------------------------------------------------- /ReadmeAssets/YouTube-Thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/YouTube-Thumbnail.jpg -------------------------------------------------------------------------------- /ReadmeAssets/Caching-Robustness.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloomAI/Floom/HEAD/ReadmeAssets/Caching-Robustness.jpg -------------------------------------------------------------------------------- /Floom.Core/Tests/Examples/Docs Pipeline/docs-model.yml: -------------------------------------------------------------------------------- 1 | schema: v1 2 | kind: Model 3 | id: docs-model 4 | vendor: OpenAI 5 | model: gpt-3.5-turbo 6 | apiKey: TEST -------------------------------------------------------------------------------- /Floom.Core/Utils/DocumentManager.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Floom.Utils; 3 | 4 | public enum ExtractionMethod 5 | { 6 | ByTOC, 7 | ByPages, 8 | ByParagraphs, 9 | } -------------------------------------------------------------------------------- /Floom.Core/Tests/Examples/CreateImage Pipeline/create-image-model.yml: -------------------------------------------------------------------------------- 1 | schema: v1 2 | kind: Model 3 | id: create-image-model 4 | vendor: OpenAI 5 | model: dall-e 6 | apiKey: TEST -------------------------------------------------------------------------------- /Floom.Core/Tests/Examples/CreateImage Pipeline/create-image-prompt.yml: -------------------------------------------------------------------------------- 1 | schema: v1 2 | kind: Prompt 3 | id: create-image-prompt 4 | type: text 5 | user: "Create a beatiful {{item}}" -------------------------------------------------------------------------------- /Floom.Core/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Floom.Core/Tests/Examples/Docs Pipeline/docs-embeddings.yml: -------------------------------------------------------------------------------- 1 | schema: v1 2 | kind: Embeddings 3 | id: docs-embeddings 4 | type: text 5 | vendor: OpenAI 6 | model: text-embedding-ada-002 7 | apiKey: sk -------------------------------------------------------------------------------- /Floom.Core/Tests/Examples/Docs Pipeline/docs-pipeline.yml: -------------------------------------------------------------------------------- 1 | schema: v1 2 | kind: Pipeline 3 | id: docs-pipeline 4 | model: docs-model 5 | prompt: docs-prompt 6 | response: docs-response 7 | data: 8 | - docs-data -------------------------------------------------------------------------------- /Floom.Core/Functions/FunctionsUtils.cs: -------------------------------------------------------------------------------- 1 | public static class FunctionsUtils 2 | { 3 | public static string NormalizeFunctionName(string name) 4 | { 5 | return name.Replace(" ", "-").ToLower(); 6 | } 7 | } -------------------------------------------------------------------------------- /Floom.Core/Tests/Examples/CreateImage Pipeline/create-image-response.yml: -------------------------------------------------------------------------------- 1 | schema: v1 2 | kind: Response 3 | id: create-image-response 4 | type: image 5 | resolution: 512x512 6 | options: 2 7 | #format: jpg 8 | #quality: 0.7 -------------------------------------------------------------------------------- /Floom.Core/Tests/Examples/Docs Pipeline/docs-vectorstore.yml: -------------------------------------------------------------------------------- 1 | schema: v1 2 | kind: VectorStore 3 | id: docs-vectorstore 4 | vendor: Pinecone 5 | apiKey: 0ea0f2ed-54bb-4d3e-878e-248af609a81b 6 | environment: us-west4-gcp-free -------------------------------------------------------------------------------- /Floom.Core/Tests/Examples/CreateImage Pipeline/create-image-pipeline.yml: -------------------------------------------------------------------------------- 1 | schema: v1 2 | kind: Pipeline 3 | id: create-image-pipeline 4 | model: create-image-model 5 | prompt: create-image-prompt 6 | response: create-image-response -------------------------------------------------------------------------------- /Floom.Core/Tests/Examples/Docs Pipeline/docs-data.yml: -------------------------------------------------------------------------------- 1 | schema: v1 2 | kind: Data 3 | id: docs-data 4 | type: file 5 | path: /dev/test/documentation.pdf 6 | split: pages 7 | #embeddings: docs-embeddings 8 | #vectorStore: docs-vectorstore 9 | -------------------------------------------------------------------------------- /Floom.Core/Tests/Examples/Docs Pipeline/docs-prompt.yml: -------------------------------------------------------------------------------- 1 | schema: v1 2 | kind: Prompt 3 | id: docs-prompt 4 | type: text 5 | system: "You are a helpful assitant of Bosch, a washing machine maker. Start with the user's first name which is {{first_name}}." -------------------------------------------------------------------------------- /Floom.Core/Auth/AllowAnonymous.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Floom.Auth 4 | { 5 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)] 6 | public class AllowAnonymousAttribute : Attribute 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Floom.Core/Functions/TranslatedField.cs: -------------------------------------------------------------------------------- 1 | namespace Floom.Functions; 2 | 3 | public class TranslatedField 4 | { 5 | public string en { get; set; } // English 6 | public string? fr { get; set; } // French 7 | public string? es { get; set; } // Spanish 8 | } 9 | -------------------------------------------------------------------------------- /Floom.Core/Base/CollectionNameAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Floom.Base; 2 | 3 | [AttributeUsage(AttributeTargets.Class, Inherited = false)] 4 | public class CollectionNameAttribute : Attribute 5 | { 6 | public string Name { get; } 7 | 8 | public CollectionNameAttribute(string name) 9 | { 10 | Name = name; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Floom.Core/Auth/ApiKeyEntity.cs: -------------------------------------------------------------------------------- 1 | using Floom.Base; 2 | using Floom.Repository; 3 | 4 | namespace Floom.Auth; 5 | 6 | [CollectionName("api-keys")] 7 | public class ApiKeyEntity : DatabaseEntity 8 | { 9 | public string? key { get; set; } 10 | public string userId { get; set; } 11 | public TimeSpan validity { get; set; } = TimeSpan.FromDays(365); // TTL default to 1 year 12 | } -------------------------------------------------------------------------------- /Floom.Core/Auth/ApiKeyUtils.cs: -------------------------------------------------------------------------------- 1 | namespace Floom.Auth; 2 | 3 | public class ApiKeyUtils 4 | { 5 | public static string GenerateApiKey() 6 | { 7 | var random = new Random(); 8 | return new string(Enumerable 9 | .Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 32) 10 | .Select(s => s[random.Next(s.Length)]).ToArray()); 11 | } 12 | } -------------------------------------------------------------------------------- /Floom.Core/Assets/AssetEntity.cs: -------------------------------------------------------------------------------- 1 | using Floom.Base; 2 | using Floom.Repository; 3 | 4 | namespace Floom.Assets; 5 | 6 | [CollectionName("assets")] 7 | public class AssetEntity : DatabaseEntity 8 | { 9 | public string? originalName { get; set; } 10 | public string? storedName { get; set; } 11 | public string? storedPath { get; set; } 12 | public string? extension { get; set; } 13 | public long size { get; set; } //bytes 14 | public string? checksum { get; set; } // Add this line 15 | } -------------------------------------------------------------------------------- /Floom.Core/Auth/UserEntity.cs: -------------------------------------------------------------------------------- 1 | using Floom.Base; 2 | using Floom.Repository; 3 | 4 | namespace Floom.Auth; 5 | 6 | [CollectionName("users")] 7 | public class UserEntity : DatabaseEntity 8 | { 9 | public string registrationProvider { get; set; } 10 | public string type { get; set; } 11 | public bool validated { get; set; } 12 | public string emailAddress { get; set; } = string.Empty; 13 | public string username { get; set; } = string.Empty; 14 | public string nickname { get; set; } = string.Empty; 15 | public string? firstName { get; set; } 16 | public string? lastName { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /Floom.Core/Tests/Examples/Docs Pipeline/docs-response.yml: -------------------------------------------------------------------------------- 1 | schema: v1 2 | kind: Response 3 | id: docs-response 4 | type: text 5 | language: English 6 | maxSentences: 3 7 | maxCharacters: 1500 8 | temperature: 0.9 9 | #blockCurseWords: true 10 | #blockDLP: 11 | #- CreditCards 12 | #- ApiKeys 13 | #- Medical-Information 14 | #- Adult 15 | #- Names 16 | #- IPAddress (ipv4/6) 17 | #- MacAddress 18 | #- PyshicalAddress 19 | #examples: 20 | # - "Turn your ignition key and press the main button." 21 | # - "Turn on the radio using the red button." 22 | # - "Open the engine bay, fill 5 Liters of oil." 23 | referToData: false -------------------------------------------------------------------------------- /Floom.Core/Logs/FloomLoggerFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Floom.Logs; 2 | 3 | public static class FloomLoggerFactory 4 | { 5 | private static ILoggerFactory _loggerFactory; 6 | 7 | public static void Configure(ILoggerFactory loggerFactory) 8 | { 9 | _loggerFactory = loggerFactory; 10 | } 11 | 12 | public static ILogger CreateLogger(Type type) 13 | { 14 | if (_loggerFactory == null) 15 | { 16 | throw new InvalidOperationException("Logger factory not configured. Call Configure() first."); 17 | } 18 | 19 | return _loggerFactory.CreateLogger(type); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Floom.Core/Tests/Yamls/seperated-ymls/main.yml: -------------------------------------------------------------------------------- 1 | # main.flm 2 | version: 1.0.0 3 | description: "Main initialization file for Floom use case." 4 | imports: 5 | - config.flm 6 | - pipeline-v2-data.yml 7 | - pipeline-v2-prompt.yml 8 | 9 | # Define global settings or variables if needed 10 | globals: 11 | log_level: INFO 12 | error_handling: true 13 | 14 | # Define entry points for the use case 15 | entry: 16 | - name: Data Ingest Pipeline 17 | triggers: 18 | - on_deploy 19 | - on_s3_event 20 | pipeline: pipeline-v2-data.yml 21 | 22 | - name: Main Pipeline 23 | triggers: 24 | - on_prompt 25 | pipeline: pipeline-v2-prompt.yml 26 | -------------------------------------------------------------------------------- /Floom.Core/Base/FloomSingletonBase.cs: -------------------------------------------------------------------------------- 1 | using Floom.Repository; 2 | 3 | namespace Floom.Base; 4 | 5 | public abstract class FloomSingletonBase where T : FloomSingletonBase, new() 6 | { 7 | private static readonly Lazy _instance = new Lazy(() => new T()); 8 | 9 | public static T Instance => _instance.Value; 10 | 11 | protected FloomSingletonBase() 12 | { 13 | // Base constructor logic, if any 14 | } 15 | 16 | // Method to provide common initialization logic, if needed. 17 | // This can be abstract or virtual depending on your requirements. 18 | public abstract void Initialize(IRepositoryFactory repositoryFactory); 19 | } 20 | -------------------------------------------------------------------------------- /Floom.Python/tests/simple_openai_query.py: -------------------------------------------------------------------------------- 1 | import os 2 | from langchain_openai import ChatOpenAI 3 | from langchain_core.output_parsers import StrOutputParser 4 | from langchain_core.prompts import ChatPromptTemplate 5 | 6 | OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY') 7 | OPENAI_MODEL = os.environ.get('OPENAI_MODEL') or "gpt-3.5-turbo" 8 | 9 | system_template = "Translate the following into {language}:" 10 | 11 | prompt_template = ChatPromptTemplate.from_messages( 12 | [("system", system_template), ("user", "{input}")] 13 | ) 14 | 15 | llm = ChatOpenAI(model=OPENAI_MODEL, api_key=OPENAI_API_KEY) 16 | 17 | parser = StrOutputParser() 18 | 19 | chain = prompt_template | llm | parser 20 | -------------------------------------------------------------------------------- /Floom.Core/Audit/AuditRowEntity.cs: -------------------------------------------------------------------------------- 1 | using Floom.Base; 2 | using Floom.Repository; 3 | 4 | namespace Floom.Audit; 5 | 6 | [CollectionName("audit")] 7 | public class AuditRowEntity : DatabaseEntity 8 | { 9 | public AuditAction action { get; set; } 10 | public string objectType { get; set; } 11 | public string objectId { get; set; } 12 | public string objectName { get; set; } 13 | public string messageId { get; set; } 14 | public string chatId { get; set; } 15 | 16 | public Dictionary attributes { get; set; } 17 | } 18 | 19 | public enum AuditAction 20 | { 21 | Create = 1, 22 | Update = 2, 23 | Delete = 3, 24 | Get = 4, 25 | GetById = 5, 26 | Floom = 10 27 | } -------------------------------------------------------------------------------- /Floom.Core/Controllers/UsersController.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using Floom.Auth; 3 | using Microsoft.AspNetCore.Authentication; 4 | using Microsoft.AspNetCore.Authentication.Google; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace Floom.Controllers; 8 | 9 | [ApiController] 10 | [Route("/v{version:apiVersion}/[controller]")] 11 | public class UsersController : ControllerBase 12 | { 13 | private readonly IUsersService _service; 14 | 15 | public UsersController(IUsersService service) 16 | { 17 | _service = service; 18 | } 19 | 20 | [HttpPost("Register")] 21 | public async Task Register() 22 | { 23 | var apiKey = await _service.RegisterGuestUserAsync(); 24 | return Ok(apiKey); 25 | } 26 | } -------------------------------------------------------------------------------- /Floom.Core/Utils/StringEnumConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Floom.Utils; 4 | 5 | using System.Text.Json.Serialization; 6 | 7 | public class StringEnumConverter : JsonConverter where TEnum : struct 8 | { 9 | public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 10 | { 11 | var value = reader.GetString(); 12 | if (Enum.TryParse(value, true, out var result)) 13 | { 14 | return result; 15 | } 16 | 17 | throw new JsonException($"{value} is not supported"); 18 | } 19 | 20 | public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) 21 | { 22 | writer.WriteStringValue(value.ToString()); 23 | } 24 | } -------------------------------------------------------------------------------- /Floom.Core/Repository/IDatabase.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using MongoDB.Driver; 3 | 4 | namespace Floom.Repository; 5 | 6 | public interface IDatabase where T : DatabaseEntity 7 | { 8 | Task Create(T entity); 9 | Task Read(string id, string uniqueKey); 10 | public Task ReadByCondition(Expression> condition); 11 | public Task ReadByAttributes(Dictionary attributes); 12 | Task> ReadAll(); 13 | Task> ReadAll(string id, string uniqueKey); 14 | Task> ReadAllByCondition(Expression> condition); 15 | Task Upsert(T entity, string uid, string column); 16 | Task Delete(string id, string uniqueKey); 17 | 18 | Task> ReadAllByFilter(FilterDefinition filter); 19 | } -------------------------------------------------------------------------------- /Floom.Core/Utils/StringUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Text; 3 | 4 | namespace Floom.Utils; 5 | 6 | public static class StringUtils 7 | { 8 | public static string GetShortHash(string input) 9 | { 10 | using (var md5 = MD5.Create()) 11 | { 12 | byte[] inputBytes = Encoding.ASCII.GetBytes(input); 13 | byte[] hashBytes = md5.ComputeHash(inputBytes); 14 | 15 | // Convert the byte array to hexadecimal string 16 | StringBuilder sb = new StringBuilder(); 17 | for (int i = 0; i < hashBytes.Length; i++) 18 | { 19 | sb.Append(hashBytes[i].ToString("X2")); 20 | } 21 | 22 | // Return the first 8 characters of the hash 23 | return sb.ToString().Substring(0, 8); 24 | } 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /Floom.Core/Base/FloomOperationResult.cs: -------------------------------------------------------------------------------- 1 | namespace Floom.Base; 2 | 3 | public class FloomOperationResult 4 | { 5 | public bool Success { get; set; } 6 | public T Data { get; set; } 7 | public string Message { get; set; } 8 | 9 | // Factory methods for a successful result 10 | public static FloomOperationResult CreateSuccessResult(T data) 11 | { 12 | return new FloomOperationResult { Success = true, Data = data }; 13 | } 14 | 15 | public static FloomOperationResult CreateSuccessResult() 16 | { 17 | return new FloomOperationResult { Success = true }; 18 | } 19 | 20 | // Factory method for a failed result 21 | public static FloomOperationResult CreateFailure(string errorMessage) 22 | { 23 | return new FloomOperationResult { Success = false, Message = errorMessage }; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /Floom.Core/Repository/DatabaseEntity.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Floom.Repository; 3 | 4 | public class DatabaseEntity 5 | { 6 | public string Id { get; set; } = string.Empty; 7 | public DateTime createdAt { get; set; } 8 | 9 | public Dictionary createdBy { get; set; } 10 | 11 | public DatabaseEntity() 12 | { 13 | // Initialize with an empty dictionary to avoid null reference exceptions 14 | createdBy = new Dictionary(); 15 | } 16 | 17 | // Method to add a key-value pair to the createdBy dictionary 18 | public void AddCreatedByApiKey(object value) 19 | { 20 | var key = "apiKey"; 21 | createdBy[key] = value; 22 | } 23 | 24 | public void AddCreatedByOwner(object value) 25 | { 26 | var key = "owner"; 27 | createdBy[key] = value; 28 | } 29 | } -------------------------------------------------------------------------------- /Floom.Python/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official AWS Lambda Python base image 2 | FROM public.ecr.aws/lambda/python:3.8 3 | 4 | # Install necessary dependencies 5 | COPY requirements.txt . 6 | RUN pip install --upgrade pip 7 | RUN pip install -r requirements.txt 8 | 9 | # Copy the rest of the application code 10 | COPY main.py . 11 | 12 | # Set the CMD to your handler (default lambda handler entry point) 13 | CMD ["main.handler"] 14 | 15 | 16 | FROM public.ecr.aws/lambda/python:3.10 17 | # Copy function code 18 | COPY ./app ${LAMBDA_TASK_ROOT} 19 | # Install the function's dependencies using file requirements.txt 20 | # from your project folder. 21 | COPY requirements.txt . 22 | RUN pip3 install -r requirements.txt - target "${LAMBDA_TASK_ROOT}" -U - no-cache-dir 23 | # Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile) 24 | CMD [ "main.handler" ] 25 | -------------------------------------------------------------------------------- /Floom.Core/Dockerfiles/Dockerfile: -------------------------------------------------------------------------------- 1 | #docker tag floom floomai/floom:v1 2 | #docker push floomai/floom:v1 3 | 4 | # Base image 5 | FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base 6 | WORKDIR /app 7 | EXPOSE 80 8 | 9 | # Install curl 10 | RUN apt-get update \ 11 | && apt-get install -y curl 12 | 13 | # Build image 14 | FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build 15 | WORKDIR /src 16 | COPY ["Floom.Core.csproj", "."] 17 | RUN dotnet restore "./Floom.Core.csproj" 18 | COPY . . 19 | WORKDIR "/src/." 20 | RUN dotnet build "Floom.Core.csproj" -c Release -o /app/build 21 | 22 | # Publish image 23 | FROM build AS publish 24 | RUN dotnet publish "Floom.Core.csproj" -c Release -o /app/publish /p:UseAppHost=false 25 | 26 | # Final image 27 | FROM base AS final 28 | WORKDIR /app 29 | 30 | COPY DLLs/ ./DLLs 31 | 32 | COPY --from=publish /app/publish . 33 | 34 | ENTRYPOINT ["dotnet", "Floom.Core.dll"] 35 | -------------------------------------------------------------------------------- /Floom.Core/Controllers/MiscController.cs: -------------------------------------------------------------------------------- 1 | using Floom.Repository; 2 | using Floom.Repository.MongoDb; 3 | using Microsoft.AspNetCore.Mvc; 4 | using MongoDB.Driver; 5 | 6 | namespace Floom.Controllers; 7 | 8 | [ApiController] 9 | [Route("/v{version:apiVersion}/[controller]")] 10 | public class MiscController : ControllerBase 11 | { 12 | private IMongoClient _mongoClient; 13 | 14 | public MiscController(IMongoClient mongoClient) 15 | { 16 | _mongoClient = mongoClient; 17 | } 18 | 19 | [HttpGet("Health")] 20 | public async Task Health() 21 | { 22 | var dbInitializer = new MongoDbInitializer(_mongoClient); 23 | var mongoResult = await dbInitializer.TestConnection(); 24 | if (!mongoResult) 25 | { 26 | return StatusCode(500, "Mongo Unhealthy"); 27 | } 28 | return Ok("Healthy"); 29 | } 30 | } -------------------------------------------------------------------------------- /Floom.Core/Auth/ApiKeyInitializer.cs: -------------------------------------------------------------------------------- 1 | using Floom.Repository; 2 | 3 | namespace Floom.Auth 4 | { 5 | public class ApiKeyInitializer 6 | { 7 | private readonly IRepository _repository; 8 | 9 | public ApiKeyInitializer(IRepositoryFactory repositoryFactory) 10 | { 11 | _repository = repositoryFactory.Create(); 12 | } 13 | 14 | public void Initialize() 15 | { 16 | var existingApiKey = _repository.GetAll().Result.FirstOrDefault(); 17 | 18 | if (existingApiKey == null) 19 | { 20 | var random = new Random(); 21 | var apiKey = ApiKeyUtils.GenerateApiKey(); 22 | 23 | _repository.Insert(new ApiKeyEntity { key = apiKey }); 24 | 25 | Console.WriteLine($"Generated API Key: {apiKey}"); 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Floom.Core/Assets/FloomAsset.cs: -------------------------------------------------------------------------------- 1 | using Floom.Assets; 2 | using MongoDB.Bson; 3 | 4 | namespace Floom.Data; 5 | 6 | public class FloomAsset 7 | { 8 | public string? Id { get; set; } 9 | public string? OriginalName { get; set; } 10 | public string? StoredName { get; set; } 11 | public string? StoredPath { get; set; } 12 | public string? Extension { get; set; } 13 | public long Size { get; set; } //bytes 14 | 15 | public static FloomAsset FromEntity(AssetEntity assetEntity) 16 | { 17 | return new FloomAsset 18 | { 19 | Id = assetEntity.Id == string.Empty ? null : assetEntity.Id, 20 | OriginalName = assetEntity.originalName, 21 | StoredName = assetEntity.storedName, 22 | StoredPath = assetEntity.storedPath, 23 | Extension = assetEntity.extension, 24 | Size = assetEntity.size 25 | }; 26 | } 27 | } -------------------------------------------------------------------------------- /Floom.Core/Config/MongoConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Authentication; 2 | using MongoDB.Driver; 3 | 4 | namespace Floom.Config; 5 | 6 | public static class MongoConfiguration 7 | { 8 | public static MongoClient CreateMongoClient() 9 | { 10 | var template = "mongodb://{0}:{1}@{2}"; 11 | var username = Environment.GetEnvironmentVariable("FLOOM_DB_USER"); 12 | var password = Environment.GetEnvironmentVariable("FLOOM_DB_PASSWORD"); 13 | var clusterEndpoint = Environment.GetEnvironmentVariable("FLOOM_DB_ADDRESS"); 14 | var connectionString = string.Format(template, username, password, clusterEndpoint); 15 | var floomEnvironment = Environment.GetEnvironmentVariable("FLOOM_ENVIRONMENT"); 16 | MongoClientSettings? settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString)); 17 | var client = new MongoClient(settings); 18 | return client; 19 | } 20 | } -------------------------------------------------------------------------------- /Floom.Core/Config/DynamoDbConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Amazon.DynamoDBv2; 2 | using Amazon.Runtime; 3 | 4 | namespace Floom.Config; 5 | 6 | public abstract class DynamoDbConfiguration 7 | { 8 | public static IAmazonDynamoDB CreateDynamoDbClient() 9 | { 10 | var config = new AmazonDynamoDBConfig 11 | { 12 | // Point to the local DynamoDB instance 13 | ServiceURL = "http://localhost:8000", 14 | }; 15 | 16 | var credentials = new BasicAWSCredentials("dummy", "dummy"); 17 | 18 | // Create a client with the local configuration 19 | return new AmazonDynamoDBClient(credentials, config); 20 | } 21 | 22 | public static IAmazonDynamoDB CreateCloudDynamoDbClient() 23 | { 24 | // Specify the AWS region of your DynamoDB instance 25 | var region = Amazon.RegionEndpoint.EUNorth1; 26 | return new AmazonDynamoDBClient(new EnvironmentVariablesAWSCredentials(), region); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /Floom.Core/Tests/Yamls/pipeline-v2-chat-decision.yml: -------------------------------------------------------------------------------- 1 | name: Pipeline Chat Decision 2 | description: "Decides whether answer is in chat history, or a new standalone question must by created" 3 | 4 | jobs: 5 | prompt: 6 | steps: 7 | - name: Default Prompt Template 8 | uses: floom/prompt/templates/default 9 | with: 10 | system: "Given a chat history and the latest user question 11 | which might reference context in the chat history, formulate a standalone question 12 | which can be understood without the chat history. Do NOT answer the question, 13 | just reformulate it if needed and otherwise return it as is" 14 | 15 | response: 16 | needs: prompt 17 | steps: 18 | - name: Response Formatter 19 | uses: floom/response/formatter 20 | with: 21 | type: object 22 | structure: 23 | "is_standalone": true 24 | "question": "the actual question" -------------------------------------------------------------------------------- /Floom.Core/Functions/FunctionDto.cs: -------------------------------------------------------------------------------- 1 | namespace Floom.Functions; 2 | 3 | public class BaseFunctionDto 4 | { 5 | /** 6 | packageName = unique = function url 7 | */ 8 | public string name { get; set; } 9 | public string author { get; set; } 10 | public double rating { get; set; } 11 | public List downloads { get; set; } = new(); 12 | 13 | public string runtimeLanguage { get; set; } 14 | public string runtimeFramework { get; set; } 15 | public string version { get; set; } 16 | public List parameters { get; set; } = new(); 17 | 18 | public TranslatedField title { get; set; } 19 | public TranslatedField description { get; set; } 20 | 21 | public TranslatedField promptPlaceholder { get; set; } 22 | } 23 | 24 | public class ParameterDto 25 | { 26 | public string name { get; set; } 27 | public TranslatedField? description { get; set; } 28 | public bool required { get; set; } 29 | public object? defaultValue { get; set; } 30 | } -------------------------------------------------------------------------------- /Floom.Core/Tests/Yamls/seperated-ymls/pipeline-v2-chat-decision.yml: -------------------------------------------------------------------------------- 1 | name: Floom Pipeline 2 | 3 | jobs: 4 | execute: 5 | steps: 6 | - name: Execute LLM Inference 7 | uses: floom/functions/chat-decision-tree 8 | with: 9 | user: ${{ this.prompt }} 10 | system: "History: ${{ this.chat_history }} >> 11 | Given a chat history and the latest user question \ 12 | which might reference context in the chat history, formulate a standalone question \ 13 | which can be understood without the chat history. Do NOT answer the question, \ 14 | just reformulate it if needed and otherwise return it as is" 15 | output: 16 | model_output: ${{ result }} 17 | 18 | - name: Response Formatter 19 | uses: floom/functions/response-formatter 20 | with: 21 | type: object 22 | structure: { 23 | "decision_output": "standalone or history", 24 | "prompt": "prompt returned from inference" 25 | } -------------------------------------------------------------------------------- /Floom.Tests/Floom.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | FloomTests 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Floom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Floom.Core/Utils/HttpContextHelper.cs: -------------------------------------------------------------------------------- 1 | using Floom.Auth; 2 | 3 | namespace Floom.Utils; 4 | 5 | public static class HttpContextHelper 6 | { 7 | public static string? GetApiKeyFromHttpContext() 8 | { 9 | var httpContext = new HttpContextAccessor().HttpContext; 10 | 11 | if (httpContext == null) 12 | { 13 | return ""; 14 | } 15 | 16 | var apiKeyEntity = httpContext.Items[ApiKeyAuthorizationAttribute.ApiKey]; 17 | 18 | if(apiKeyEntity != null) 19 | { 20 | return ((ApiKeyEntity)apiKeyEntity).key; 21 | } 22 | 23 | return ""; 24 | } 25 | 26 | public static string GetUserIdFromHttpContext() 27 | { 28 | var httpContext = new HttpContextAccessor().HttpContext; 29 | 30 | if (httpContext == null) 31 | { 32 | return ""; 33 | } 34 | 35 | var apiKeyEntity = httpContext.Items[ApiKeyAuthorizationAttribute.ApiKey]; 36 | 37 | if(apiKeyEntity != null) 38 | { 39 | return ((ApiKeyEntity)apiKeyEntity).userId; 40 | } 41 | 42 | return ""; 43 | } 44 | } -------------------------------------------------------------------------------- /Floom.Core/Floom.Core.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.002.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Floom.Core", "Floom.Core.csproj", "{237A6919-AEC5-48BE-AB35-86B13BBF175A}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {237A6919-AEC5-48BE-AB35-86B13BBF175A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {237A6919-AEC5-48BE-AB35-86B13BBF175A}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {237A6919-AEC5-48BE-AB35-86B13BBF175A}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {237A6919-AEC5-48BE-AB35-86B13BBF175A}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {0D44E0CD-FDA5-4C55-92EB-83EFA7E28AFF} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Floom.Core/Tests/Yamls/seperated-ymls/pipeline-v2-standalone.yml: -------------------------------------------------------------------------------- 1 | name: Floom Pipeline 2 | 3 | globals: 4 | collection: ${{ this.config.floom_internal_database.default_collection_name }} 5 | database: ${{ this.config.floom_internal_database }} 6 | 7 | jobs: 8 | execute: 9 | steps: 10 | - name: Convert Prompt to Mongodb Query 11 | uses: floom/functions/convert-prompt-mongodb-query 12 | with: 13 | collection: ${{ this.globals.collection }} 14 | database: ${{ this.globals.database }} 15 | user: ${{ this.prompt }} 16 | system: "You are Mongo DB expert, specializing in writing consise MongoDB structures and queries." 17 | 18 | - name: Execute Mongodb Query 19 | uses: floom/functions/execture-mongo-db-query 20 | with: 21 | collection: ${{ this.globals.collection }} 22 | database: ${{ this.globals.database }} 23 | output: 24 | execution_output: ${{ result }} 25 | 26 | - name: Response Formatter 27 | uses: floom/functions/response-formatter 28 | with: 29 | input: ${{ execution_output }} 30 | type: text 31 | output: 32 | this.result: ${{ result }} -------------------------------------------------------------------------------- /Floom.Core/Tests/Yamls/seperated-ymls/config.yml: -------------------------------------------------------------------------------- 1 | # config.flm 2 | version: 1.0.0 3 | description: "Configuration file for Floom use case." 4 | 5 | config: 6 | openai_model: 7 | name: Open AI Model 8 | uses: floom/model/connectors/openai 9 | with: 10 | model: gpt-3.5-turbo 11 | api-key: ${{ secrets.OPENAI_API_KEY }} 12 | max_tokens: 2048 13 | temperature: 0.7 14 | 15 | floom_internal_database: 16 | name: Floom Internal Database (MongoDB) 17 | uses: floom/internal/database/mongodb 18 | with: 19 | host: localhost 20 | username: root 21 | password: MyFloom 22 | default_collection_name: "cvs" 23 | 24 | floom_vector_database: 25 | name: Floom Internal Vector Database (Milvus) 26 | uses: floom/internal/database/Milvus 27 | with: "Milvus" 28 | host: localhost 29 | port: 19530 30 | username: root 31 | password: Milvus 32 | 33 | floom_cost_management: 34 | name: Floom Cost Management 35 | uses: floom/plugins/cost-management 36 | with: 37 | limits: 38 | user: 39 | - day: 1000 40 | pipeline: 41 | - month: 600000 42 | 43 | floom_cache: 44 | name: Floom Cache 45 | uses: floom/plugins/cache 46 | with: 47 | cache-type: memory -------------------------------------------------------------------------------- /Floom.Core/Tests/Yamls/seperated-ymls/pipeline-v2-data.yml: -------------------------------------------------------------------------------- 1 | name: Floom Pipeline 2 | 3 | jobs: 4 | fetch_documents: 5 | steps: 6 | - name: Fetch Documents from AWS S3 7 | uses: floom/data/source/s3 8 | with: 9 | aws-apnr: my-cv.s3.aws.com 10 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 11 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 12 | output: 13 | s3_data: ${{ result.document }} 14 | 15 | convert_documents: 16 | needs: fetch_documents 17 | steps: 18 | - name: Convert documents into RAW text 19 | uses: floom/plugins/pdf-to-text 20 | with: 21 | input: ${{ jobs.fetch_documents.output.s3_data }} 22 | output: 23 | raw_text: ${{ result.raw_text }} 24 | 25 | - name: Convert RAW text into JSON 26 | uses: floom/functions/text-to-json 27 | with: 28 | input: ${{ raw_text }} 29 | output: 30 | json_result: ${{ result.json }} 31 | 32 | - name: Insert JSON into MongoDB 33 | uses: floom/plugins/database/mongodb 34 | with: 35 | collection: ${{ this.config.floom_internal_database.default_collection_name }} 36 | database: ${{ this.config.floom_internal_database }} 37 | input: ${{ json_result }} -------------------------------------------------------------------------------- /Floom.Core/Controllers/AssetsController.cs: -------------------------------------------------------------------------------- 1 | using Floom.Assets; 2 | using Floom.Auth; 3 | using Floom.Data; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace Floom.Controllers; 7 | 8 | [ApiController] 9 | [Route("/v{version:apiVersion}/[controller]")] 10 | [ApiVersion("1.0")] 11 | [ApiKeyAuthorization] 12 | public class AssetsController : ControllerBase 13 | { 14 | private readonly ILogger _logger; 15 | 16 | public AssetsController(ILogger logger) 17 | { 18 | _logger = logger; 19 | } 20 | 21 | [HttpPost] 22 | public async Task Create() 23 | { 24 | try 25 | { 26 | var file = Request.Form.Files.FirstOrDefault(); // Get the uploaded file from the request 27 | if (file == null || file.Length == 0) 28 | { 29 | return BadRequest(new { error = "No asset was provided" }); 30 | } 31 | 32 | var fileId = await FloomAssetsRepository.Instance.CreateAsset(file); 33 | return Ok(new { fileId }); 34 | } 35 | catch (Exception e) 36 | { 37 | _logger.LogError(e, "Error occurred while creating asset."); 38 | return StatusCode(StatusCodes.Status500InternalServerError, 39 | new { error = "An error occurred while creating asset." }); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /Floom.Core/Tests/Yamls/seperated-ymls/pipeline-v2-prompt.yml: -------------------------------------------------------------------------------- 1 | name: Floom Pipeline 2 | 3 | jobs: 4 | execute_prompt: 5 | steps: 6 | - name: Execute Chat Decision Tree 7 | uses: floom/functions/chat-decision-tree (pipeline-v2-chat-decision) 8 | with: 9 | model: ${{ config.openai_model }} 10 | prompt: ${{ this.prompt }} 11 | history: ${{ this.chat_history }} 12 | 13 | - name: Execute Standalone AI Function 14 | uses: floom/functions/standalone-ai-function 15 | needs: "${{ jobs.execute_prompt.output.decision_output == 'standalone' }}" 16 | 17 | - name: Execute History AI Function 18 | uses: floom/functions/history-ai-function 19 | needs: "${{ jobs.execute_prompt.output.decision_output == 'history' }}" 20 | 21 | format_response: 22 | needs: execute_prompt 23 | steps: 24 | - name: Response Validator 25 | uses: floom/plugins/bad-words-filter 26 | with: 27 | input: ${{ jobs.execute_prompt.output }} 28 | disallow: ['pii', 'credit-cards'] 29 | language: en-us 30 | output: 31 | this.result: ${{ result }} 32 | 33 | - name: Post Pipeline Response to Slack API 34 | run: | 35 | curl -X POST "https://api.slack.com/endpoint" \ 36 | -H "Content-Type: application/json" \ 37 | -H "Authorization: Bearer ${{ secrets.SLACK_API_TOKEN }}" \ 38 | -d '{"key": "value", "response": "${{ this.result }}"}' -------------------------------------------------------------------------------- /Floom.Core/Repository/RepositoryFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Floom.Base; 3 | using Floom.Config; 4 | using MongoDB.Driver; 5 | 6 | namespace Floom.Repository; 7 | 8 | public interface IRepositoryFactory 9 | { 10 | IRepository Create(string collectionName= null) where T : DatabaseEntity, new(); 11 | } 12 | 13 | public class RepositoryFactory : IRepositoryFactory 14 | { 15 | public IRepository Create(string collectionName = null) where T : DatabaseEntity, new() 16 | { 17 | // If collectionName is not provided, try to get it from the CollectionNameAttribute 18 | if (string.IsNullOrEmpty(collectionName)) 19 | { 20 | var attribute = typeof(T).GetCustomAttribute(); 21 | if (attribute == null) 22 | { 23 | throw new InvalidOperationException($"The collection name for {typeof(T).Name} is not specified."); 24 | } 25 | collectionName = attribute.Name; 26 | } 27 | 28 | var mongoDatabase = new MongoDatabase(MongoConfiguration.CreateMongoClient(), collectionName); 29 | return new Repository(mongoDatabase); 30 | 31 | 32 | /* 33 | if(databaseType.Equals("dynamodb")) 34 | { 35 | var dynamoDbClient = DynamoDbConfiguration.CreateCloudDynamoDbClient(); 36 | var dynamoDbDatabase = new DynamoDbDatabase(dynamoDbClient, collectionName); 37 | return new Repository(dynamoDbDatabase); 38 | }*/ 39 | 40 | return null; 41 | } 42 | } -------------------------------------------------------------------------------- /Floom.Core/Controllers/FloomUsernameGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Text; 3 | 4 | namespace Floom.Controllers; 5 | 6 | public class FloomUsernameGenerator 7 | { 8 | private static readonly string[] prefixes = { 9 | "Floomi", 10 | "Floomer", 11 | "Floomist", 12 | "Floomify", 13 | "FloomX", 14 | "FloomNova", 15 | "FloomByte", 16 | "FloomNest", 17 | "FloomSpark", 18 | "FloomQuest", 19 | "FloomZen", 20 | "FloomVerse", 21 | "FloomEcho" 22 | }; 23 | 24 | public static string GenerateTemporaryUsername() 25 | { 26 | // Dynamic component based on a hash of the current timestamp and a random element 27 | var random = new Random(); 28 | var timestamp = DateTime.UtcNow.Ticks.ToString(); 29 | var randomComponent = random.Next(1000, 9999).ToString(); 30 | var uniqueComponent = timestamp + randomComponent; 31 | 32 | using (var sha256 = SHA256.Create()) 33 | { 34 | var hashedBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(uniqueComponent)); 35 | var hash = BitConverter.ToString(hashedBytes).Replace("-", "").ToLower(); 36 | 37 | // Take the first 6 characters to keep it short 38 | var shortHash = hash.Substring(0, 6); 39 | 40 | return $"{shortHash}"; 41 | } 42 | } 43 | 44 | public static string GenerateTemporaryNickname() 45 | { 46 | // Randomly select a prefix 47 | var random = new Random(); 48 | string prefix = prefixes[random.Next(prefixes.Length)]; 49 | return prefix; 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # 7 | .idea/ 8 | floom_user_files/ 9 | docker_volumes/ 10 | bin/ 11 | obj/ 12 | Floom.Core/LocalModels/* 13 | Floom.Core/DLLs/* 14 | TestResults/* 15 | Todos.txt 16 | build/ 17 | Floom.Core/Certificates/* 18 | Certificates/* 19 | # Common IntelliJ Platform excludes 20 | 21 | Floom.Python/__pycache__/* 22 | Floom.Python/package/* 23 | 24 | # User specific 25 | **/.idea/**/workspace.xml 26 | **/.idea/**/tasks.xml 27 | **/.idea/shelf/* 28 | **/.idea/dictionaries 29 | **/.idea/httpRequests/ 30 | 31 | # Sensitive or high-churn files 32 | **/.idea/**/dataSources/ 33 | **/.idea/**/dataSources.ids 34 | **/.idea/**/dataSources.xml 35 | **/.idea/**/dataSources.local.xml 36 | **/.idea/**/sqlDataSources.xml 37 | **/.idea/**/dynamic.xml 38 | 39 | # Rider 40 | # Rider auto-generates .iml files, and contentModel.xml 41 | **/.idea/**/*.iml 42 | **/.idea/**/contentModel.xml 43 | **/.idea/**/modules.xml 44 | 45 | *.suo 46 | *.user 47 | .vs/ 48 | [Bb]in/ 49 | [Oo]bj/ 50 | _UpgradeReport_Files/ 51 | [Pp]ackages/ 52 | 53 | Thumbs.db 54 | Desktop.ini 55 | .DS_Store 56 | 57 | # Build results 58 | [Dd]ebug/ 59 | [Dd]ebugPublic/ 60 | [Rr]elease/ 61 | [Rr]eleases/ 62 | x64/ 63 | x86/ 64 | [Ww][Ii][Nn]32/ 65 | [Aa][Rr][Mm]/ 66 | [Aa][Rr][Mm]64/ 67 | bld/ 68 | [Bb]in/ 69 | [Oo]bj/ 70 | [Oo]ut/ 71 | 72 | Floom.Core/appsettings.json 73 | Floom.Core/appsettings.json 74 | build.sh 75 | Floom.Core/appsettings.json 76 | build.sh 77 | Floom.Core/Properties/launchSettings.json 78 | Floom.Core/Properties/launchSettings.json 79 | Floom.Core/appsettings.json 80 | build.sh 81 | /Floom.Core/Tests/Functions 82 | -------------------------------------------------------------------------------- /Floom.Core/Tests/Yamls/pipeline-v1.yml: -------------------------------------------------------------------------------- 1 | kind: 'floom/pipeline/1.2' 2 | 3 | pipeline: 4 | name: pipeline-idm-docs1 5 | 6 | model: 7 | - package: floom/model/connector/openai 8 | model: gpt-3.5-turbo 9 | api-key: TEST 10 | 11 | prompt: 12 | template: 13 | package: floom/prompt/template/default 14 | system: "You are a powerful assistant of the BMW, answer questions about BMW, and help users with their issues." 15 | 16 | context: 17 | - package: floom/prompt/context/pdf 18 | path: /etc/myfiles/tutorial.pdf 19 | 20 | optimization: 21 | - package: floom/prompt/optimization/translation 22 | from: en-us 23 | to: he-il 24 | - package: floom/prompt/optimization/compression 25 | 26 | validation: 27 | - package: promptsec/prompt/validation/bad-words-filter 28 | language: en-us 29 | - package: promptsec/prompt/validation/pii 30 | language: en-us 31 | 32 | response: 33 | format: 34 | - package: floom/response/formatter 35 | type: text 36 | language: en 37 | max-sentences: 20 38 | max-characters: 10000 39 | 40 | validation: 41 | - package: promptsec/response/validation/bad-words-filter 42 | language: en-us 43 | - package: floom/response/validation/sql-validator 44 | 45 | global: 46 | - package: floom/global/conversation-history 47 | - package: promptsec/global/ddos-protection 48 | throttling: 49 | timeframe: min 50 | max-hits: 3 51 | per: user 52 | action: deny 53 | - package: floom/global/cost-management 54 | alert-threshold: 1000 55 | report-frequency: weekly 56 | - package: floom/global/cache 57 | config: 58 | cache-type: memory 59 | max-size: 1024MB 60 | -------------------------------------------------------------------------------- /Floom.Core/Audit/FloomAuditService.cs: -------------------------------------------------------------------------------- 1 | using Floom.Base; 2 | using Floom.Logs; 3 | using Floom.Repository; 4 | using Floom.Utils; 5 | 6 | namespace Floom.Audit; 7 | 8 | public class FloomAuditService : FloomSingletonBase 9 | { 10 | private readonly ILogger _logger; 11 | private IRepository _repository; 12 | 13 | public FloomAuditService() 14 | { 15 | _logger = FloomLoggerFactory.CreateLogger(GetType()); 16 | } 17 | 18 | public override void Initialize(IRepositoryFactory repositoryFactory) 19 | { 20 | _repository = repositoryFactory.Create(); 21 | } 22 | 23 | public async Task> GetByChatId(string chatId) 24 | { 25 | _logger.LogInformation("GetByChatId"); 26 | var models = await _repository.GetAll(chatId, "chatId"); 27 | return models; 28 | } 29 | 30 | public void Insert( 31 | AuditAction action, 32 | string objectType, 33 | string objectId = "", 34 | string objectName = "", 35 | string messageId = "", 36 | string chatId = "", 37 | Dictionary? attributes = null) 38 | { 39 | AuditRowEntity auditRowEntity = new AuditRowEntity() 40 | { 41 | action = action, 42 | createdAt = DateTime.UtcNow, 43 | objectType = objectType, 44 | objectId = objectId, 45 | objectName = objectName, 46 | messageId = messageId, 47 | chatId = chatId 48 | }; 49 | 50 | auditRowEntity.AddCreatedByApiKey(HttpContextHelper.GetApiKeyFromHttpContext() ?? ""); 51 | 52 | if (attributes != null) 53 | { 54 | auditRowEntity.attributes = attributes; 55 | } 56 | 57 | _repository.Insert(auditRowEntity); 58 | } 59 | } -------------------------------------------------------------------------------- /Floom.Core/Repository/FunctionEntity.cs: -------------------------------------------------------------------------------- 1 | using Floom.Base; 2 | using Floom.Functions; 3 | using Floom.Repository; 4 | 5 | [CollectionName("functions")] 6 | public class FunctionEntity: DatabaseEntity 7 | { 8 | /** 9 | * functionName a unique id, used as the URL slug as well 10 | */ 11 | public string name { get; set; } 12 | public string runtimeLanguage { get; set; } 13 | public string runtimeFramework { get; set; } 14 | public string promptUrl { get; set; } 15 | public string? dataUrl { get; set; } 16 | public TranslatedField? title { get; set; } // Translated title, should be short description of function action 17 | public TranslatedField? description { get; set; } // Translated descriptions 18 | public TranslatedField? promptPlaceholder { get; set; } // Translated prompt placeholders 19 | public string userId { get; set; } 20 | public string[]? roles { get; set; } 21 | 22 | public string? version { get; set; } // Version of the function 23 | public double? rating { get; set; } // Rating (e.g., 4 out of 5) 24 | public List? downloads { get; set; } = new(); // Download statistics over time 25 | public List parameters { get; set; } = new(); // Parameters required for the function 26 | 27 | public bool IsPublic() 28 | { 29 | return roles != null && roles.Contains(Roles.Public); 30 | } 31 | 32 | public bool IsFeatured() 33 | { 34 | return roles != null && roles.Contains(Roles.Featured); 35 | } 36 | } 37 | 38 | public class Parameter 39 | { 40 | public string name { get; set; } 41 | public TranslatedField? description { get; set; } // Translated descriptions for parameters 42 | public bool required { get; set; } 43 | public object? defaultValue { get; set; } 44 | } 45 | 46 | public static class Roles 47 | { 48 | public const string Public = "public"; 49 | public const string Featured = "featured"; 50 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | ![25github-text.jpg](/ReadmeAssets/25github-text.jpg) **Floom** orchestrates and executes Generative AI pipelines,
Empowering **Developers** to focus on what matters. 7 |
8 |
![25github-text.jpg](/ReadmeAssets/25github-text.jpg) **Floom** is now Open-Source and **100% FREE** for everyone (including commercial use). 9 | 10 | ![Version](https://img.shields.io/badge/version-1.1.7-blue) 11 | ![License](https://img.shields.io/badge/license-MIT-green) 12 | 13 |

Get started with our Documentation!

14 |
15 | 16 | [![Floom Pipeline YAML](/ReadmeAssets/YouTube-Thumbnail.jpg)](https://www.youtube.com/watch?v=5boCgU-QvLI) 17 | 18 |
19 | 20 |

21 | 22 |

23 |

24 | 25 |

26 |

27 | 28 |

29 |

30 | 31 |

32 |

33 | 34 |

35 | -------------------------------------------------------------------------------- /Floom.Core/Repository/DynamoDB/DynamoDbMapper.cs: -------------------------------------------------------------------------------- 1 | using Amazon.DynamoDBv2.Model; 2 | 3 | namespace Floom.Repository.DynamoDB; 4 | 5 | public class DynamoDbMapper 6 | { 7 | public static T ConvertToEntity(Dictionary item) where T : new() 8 | { 9 | T entity = new T(); 10 | foreach (var prop in typeof(T).GetProperties()) 11 | { 12 | if (item.ContainsKey(prop.Name) && prop.CanWrite) 13 | { 14 | AttributeValue value = item[prop.Name]; 15 | prop.SetValue(entity, ConvertAttributeValue(value, prop.PropertyType)); 16 | } 17 | } 18 | return entity; 19 | } 20 | 21 | private static object ConvertAttributeValue(AttributeValue value, Type targetType) 22 | { 23 | // Handle null case 24 | if (value == null) return null; 25 | 26 | // Convert based on targetType 27 | if (targetType == typeof(string)) 28 | { 29 | return value.S; 30 | } 31 | else if (targetType == typeof(int) || targetType == typeof(int?)) 32 | { 33 | return value.NULL ? default(int?) : int.Parse(value.N); 34 | } 35 | else if (targetType == typeof(long) || targetType == typeof(long?)) 36 | { 37 | return value.NULL ? default(long?) : long.Parse(value.N); 38 | } 39 | else if (targetType == typeof(bool) || targetType == typeof(bool?)) 40 | { 41 | return value.NULL ? default(bool?) : value.BOOL; 42 | } 43 | else if (typeof(IEnumerable).IsAssignableFrom(targetType) && value.L != null) 44 | { 45 | return value.L.Select(av => av.S).ToList(); 46 | } 47 | else if (typeof(IDictionary).IsAssignableFrom(targetType) && value.M != null) 48 | { 49 | return value.M.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.S); 50 | } 51 | else 52 | { 53 | throw new NotSupportedException($"Type {targetType.Name} is not supported"); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Floom.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33815.320 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Floom.Core", "Floom.Core\Floom.Core.csproj", "{988D2C36-3F55-478F-8911-29112D8D9EDF}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Floom.Tests", "Floom.Tests\Floom.Tests.csproj", "{1CE1C2AD-E982-4C91-96E8-E1337E928A2A}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Floom.Plugins", "Floom.Plugins\Floom.Plugins.csproj", "{5EB558DD-208B-4C7D-85FA-EB2B086D9B8C}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {988D2C36-3F55-478F-8911-29112D8D9EDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {988D2C36-3F55-478F-8911-29112D8D9EDF}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {988D2C36-3F55-478F-8911-29112D8D9EDF}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {988D2C36-3F55-478F-8911-29112D8D9EDF}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {1CE1C2AD-E982-4C91-96E8-E1337E928A2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {1CE1C2AD-E982-4C91-96E8-E1337E928A2A}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {1CE1C2AD-E982-4C91-96E8-E1337E928A2A}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {1CE1C2AD-E982-4C91-96E8-E1337E928A2A}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {5EB558DD-208B-4C7D-85FA-EB2B086D9B8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {5EB558DD-208B-4C7D-85FA-EB2B086D9B8C}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {5EB558DD-208B-4C7D-85FA-EB2B086D9B8C}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {5EB558DD-208B-4C7D-85FA-EB2B086D9B8C}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {DF09D604-46E4-4E6E-B2BC-491DD336A509} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /Floom.Core/Tests/setup.ts: -------------------------------------------------------------------------------- 1 | //// setup.ts 2 | 3 | //import { FloomPrompt, PipelineConfig, DataConfig } from './your-sdk'; // Replace './your-sdk' with the actual path to your SDK 4 | 5 | //async function main() { 6 | // // Replace '', '', and '' with actual values 7 | // const apiKey = '824jf285hg828gj2g951gh18'; 8 | // const yourId = ''; 9 | // const pdfPath = '/dev/test/documentation.pdf'; 10 | 11 | // // Create a new FloomPrompt instance with the API key 12 | // const fp = new FloomPrompt(apiKey); 13 | 14 | // // Create a Pipeline configuration 15 | // const pipelineConfig: PipelineConfig = { 16 | // schema: 'v1', 17 | // kind: 'Pipeline', 18 | // name: 'docs-pipeline', 19 | // chatHistory: true, 20 | // model: { 21 | // vendor: 'OpenAI', 22 | // model: 'davinci-003', 23 | // apiKey: apiKey, 24 | // }, 25 | // prompt: { 26 | // type: 'text', 27 | // system: "You are an assistant that speaks like Shakespeare", 28 | // user: "{input}", 29 | // }, 30 | // response: { 31 | // type: 'text', 32 | // language: 'English', 33 | // maxSentences: 3, 34 | // maxCharacters: 1500, 35 | // temperature: 0.9, 36 | // }, 37 | // data: [ 38 | // { 39 | // type: 'file', 40 | // path: pdfPath, 41 | // split: 'pages', 42 | // embeddings: { 43 | // type: 'text', 44 | // vendor: 'OpenAI', 45 | // model: 'text-embedding-ada-002', 46 | // apiKey: apiKey, 47 | // }, 48 | // vectorStore: { 49 | // vendor: 'Pinecone', 50 | // apiKey: apiKey, 51 | // }, 52 | // }, 53 | // ], 54 | // }; 55 | 56 | // try { 57 | // // Create the Pipeline using the provided yourId and pipelineConfig 58 | // await fp.Pipeline(yourId, pipelineConfig); 59 | // console.log('Pipeline created successfully!'); 60 | // } catch (error) { 61 | // console.error('Failed to create pipeline:', error); 62 | // } 63 | //} 64 | 65 | //main(); 66 | -------------------------------------------------------------------------------- /Floom.Core/Repository/DynamoDB/DynamoDbInitializer.cs: -------------------------------------------------------------------------------- 1 | using Amazon.DynamoDBv2; 2 | using Amazon.DynamoDBv2.Model; 3 | using Floom.Logs; 4 | 5 | namespace Floom.Repository.DynamoDB; 6 | 7 | public class DynamoDbInitializer 8 | { 9 | private readonly IAmazonDynamoDB _dynamoDbClient; 10 | private readonly ILogger _logger; 11 | 12 | public DynamoDbInitializer(IAmazonDynamoDB dynamoDbClient) 13 | { 14 | _dynamoDbClient = dynamoDbClient; 15 | _logger = FloomLoggerFactory.CreateLogger(GetType()); 16 | } 17 | 18 | // Add your table creation methods here... 19 | 20 | public async Task Initialize() 21 | { 22 | if (await EnsureDynamoDBConnection()) 23 | { 24 | _logger.LogInformation("Successfully connected to DynamoDB."); 25 | } 26 | else 27 | { 28 | _logger.LogError("Failed to establish a connection to DynamoDB after multiple attempts."); 29 | } 30 | } 31 | 32 | private async Task EnsureDynamoDBConnection() 33 | { 34 | int attempts = 5; 35 | for (int attempt = 1; attempt <= attempts; attempt++) 36 | { 37 | _logger.LogInformation($"Attempting to connect to DynamoDB, attempt {attempt} of {attempts}"); 38 | 39 | if (await TestConnection()) 40 | { 41 | return true; 42 | } 43 | 44 | if (attempt < attempts) 45 | { 46 | _logger.LogWarning("Failed to connect to DynamoDB, retrying in 15 seconds..."); 47 | await Task.Delay(TimeSpan.FromSeconds(15)); 48 | } 49 | } 50 | 51 | return false; 52 | } 53 | 54 | private async Task TestConnection() 55 | { 56 | try 57 | { 58 | // Listing tables as a lightweight operation to test connectivity 59 | var response = await _dynamoDbClient.ListTablesAsync(new ListTablesRequest 60 | { 61 | Limit = 1 62 | }); 63 | 64 | return response.HttpStatusCode == System.Net.HttpStatusCode.OK; 65 | } 66 | catch (Exception ex) 67 | { 68 | _logger.LogError(ex, "An error occurred while testing the DynamoDB connection."); 69 | return false; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /Floom.Core/Utils/ConfigureSwaggerOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ApiExplorer; 2 | using Microsoft.Extensions.Options; 3 | using Microsoft.OpenApi.Models; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | 6 | namespace Floom.Utils 7 | { 8 | public class ConfigureSwaggerOptions 9 | : IConfigureNamedOptions 10 | { 11 | private readonly IApiVersionDescriptionProvider _provider; 12 | 13 | public ConfigureSwaggerOptions( 14 | IApiVersionDescriptionProvider provider) 15 | { 16 | _provider = provider; 17 | } 18 | 19 | /// 20 | /// Configure each API discovered for Swagger Documentation 21 | /// 22 | /// 23 | public void Configure(SwaggerGenOptions options) 24 | { 25 | // add swagger document for every API version discovered 26 | foreach (var description in _provider.ApiVersionDescriptions) 27 | { 28 | options.SwaggerDoc( 29 | description.GroupName, 30 | CreateVersionInfo(description)); 31 | } 32 | } 33 | 34 | /// 35 | /// Configure Swagger Options. Inherited from the Interface 36 | /// 37 | /// 38 | /// 39 | public void Configure(string? name, SwaggerGenOptions options) 40 | { 41 | Configure(options); 42 | } 43 | 44 | /// 45 | /// Create information about the version of the API 46 | /// 47 | /// 48 | /// Information about the API 49 | private OpenApiInfo CreateVersionInfo( 50 | ApiVersionDescription desc) 51 | { 52 | var info = new OpenApiInfo() 53 | { 54 | Title = "Floom", 55 | Version = desc.ApiVersion.ToString() 56 | }; 57 | 58 | if (desc.IsDeprecated) 59 | { 60 | info.Description += " This API version has been deprecated. Please use one of the new APIs available from the explorer."; 61 | } 62 | 63 | return info; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Floom.Core/Tests/Yamls/pipeline-v2.yml: -------------------------------------------------------------------------------- 1 | name: Floom Pipeline 2 | 3 | jobs: 4 | data: 5 | source: 6 | - name: Fetch Documents from AWS S3 7 | uses: floom/data/source/s3 8 | with: 9 | aws-apnr: apnr://my-cv.us-east1.s3.aws.com 10 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 11 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 12 | 13 | ingest: 14 | needs: data.source 15 | steps: 16 | - name: Convert documnets into RAW text 17 | uses: floom/plugins/pdf-to-text 18 | 19 | - name: Convert RAW text into JSON 20 | uses: floom/functions/text-to-json 21 | 22 | model: 23 | - name: Open AI Model Connector 24 | uses: floom/model/connectors/openai 25 | with: 26 | model: gpt-3.5-turbo 27 | api-key: ${{ secrets.OPENAI_API_KEY }} 28 | 29 | prompt: 30 | needs: model 31 | # runs-on: floom-local/floom-core 32 | steps: 33 | - name: Execute Chat Decision Tree 34 | uses: pipeline-v2-chat-decision.yml 35 | store_output: decision_output 36 | 37 | - name: Execute Standalone AI Function 38 | uses: floom/functions/standalone-ai-function 39 | needs: "${{ decision_output.is_standalone == true }}" 40 | 41 | - name: Execute History AI Function 42 | uses: floom/functions/history-ai-function 43 | needs: "${{ decision_output.is_standalone == false }}" 44 | 45 | response: 46 | needs: prompt 47 | steps: 48 | - name: Response Formatter 49 | uses: floom/response/formatter 50 | with: 51 | type: text 52 | language: en-us 53 | max-sentences: 3 54 | 55 | - name: Response Validator 56 | uses: floom/plugins/bad-words-filter 57 | with: 58 | disallow: ['pii', 'credit-cards'] 59 | language: en-us 60 | 61 | 62 | config: 63 | secrets: 64 | - name: AWS Secrets Manager 65 | uses: floom/config/secrets/aws-secrets-manager 66 | store_output: secrets 67 | 68 | cost: 69 | - name: Floom Cost Management 70 | uses: floom/plugins/cost-management 71 | with: 72 | limits: 73 | user.day: 1000 74 | pipeline.month: 600000 75 | 76 | cache: 77 | - name: Floom Cache 78 | uses: floom/plugins/cache 79 | with: 80 | cache-type: memory 81 | -------------------------------------------------------------------------------- /Floom.Core/Utils/FileUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using Amazon.S3; 3 | using Amazon.S3.Model; 4 | using Floom.Data; 5 | 6 | namespace Floom.Utils; 7 | 8 | public static class FileUtils 9 | { 10 | public static async Task ConvertIFormFileToByteArrayAsync(IFormFile file) 11 | { 12 | if (file == null || file.Length == 0) 13 | return new byte[0]; 14 | 15 | using (var memoryStream = new MemoryStream()) 16 | { 17 | await file.CopyToAsync(memoryStream); 18 | return memoryStream.ToArray(); 19 | } 20 | } 21 | 22 | public static async Task CalculateChecksumAsync(IFormFile file) 23 | { 24 | using (var stream = file.OpenReadStream()) 25 | { 26 | using (var sha256 = SHA256.Create()) 27 | { 28 | var hash = await sha256.ComputeHashAsync(stream); 29 | return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); 30 | } 31 | } 32 | } 33 | 34 | public static async Task ReadFileAsync(FloomAsset floomAsset) 35 | { 36 | await using var fileStream = new FileStream(floomAsset.StoredPath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true); 37 | using var memoryStream = new MemoryStream(); 38 | await fileStream.CopyToAsync(memoryStream); 39 | var fileBytes = memoryStream.ToArray(); 40 | return fileBytes; 41 | } 42 | 43 | public static async Task ReadFileCloudAsync(FloomAsset floomAsset) 44 | { 45 | var s3Client = new AmazonS3Client(); 46 | var bucketName = Environment.GetEnvironmentVariable("FLOOM_S3_BUCKET") ?? "empty_bucket"; 47 | var fileKey = floomAsset.StoredName; 48 | 49 | var request = new GetObjectRequest 50 | { 51 | BucketName = bucketName, 52 | Key = fileKey 53 | }; 54 | 55 | using (var response = await s3Client.GetObjectAsync(request)) 56 | { 57 | using (var responseStream = response.ResponseStream) 58 | { 59 | using (var memoryStream = new MemoryStream()) 60 | { 61 | await responseStream.CopyToAsync(memoryStream); 62 | return memoryStream.ToArray(); 63 | } 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Floom.Core/Auth/ApiKeyAuthorizationAttribute.cs: -------------------------------------------------------------------------------- 1 | using Floom.Repository; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Filters; 4 | 5 | namespace Floom.Auth; 6 | 7 | public class ApiKeyAuthorizationAttribute : Attribute, IAsyncAuthorizationFilter 8 | { 9 | public const string ApiKey = "API_KEY_DETAILS"; 10 | private readonly bool _shouldEnforceAuthorization; 11 | 12 | public ApiKeyAuthorizationAttribute() 13 | { 14 | if (Environment.GetEnvironmentVariable("FLOOM_AUTHENTICATION") != null) 15 | { 16 | var useAuthentication = Environment.GetEnvironmentVariable("FLOOM_AUTHENTICATION"); 17 | _shouldEnforceAuthorization = useAuthentication == "true"; 18 | } 19 | else 20 | { 21 | _shouldEnforceAuthorization = false; 22 | } 23 | } 24 | 25 | public async Task OnAuthorizationAsync(AuthorizationFilterContext context) 26 | { 27 | // Check if the AllowAnonymous attribute is present 28 | if (context.ActionDescriptor.EndpointMetadata.Any(em => em is AllowAnonymousAttribute)) 29 | { 30 | // If AllowAnonymous is present, skip the authorization check 31 | return; 32 | } 33 | 34 | if (!_shouldEnforceAuthorization) 35 | { 36 | // If authorization is disabled, skip the check. 37 | return; 38 | } 39 | 40 | if (!context.HttpContext.Request.Headers.TryGetValue("Api-Key", out var apiKeyValues)) 41 | { 42 | context.Result = new UnauthorizedResult(); 43 | return; 44 | } 45 | 46 | var repositoryFactory = context.HttpContext.RequestServices.GetRequiredService(); 47 | var repository = repositoryFactory.Create(); 48 | 49 | var apiKey = apiKeyValues.FirstOrDefault(); // Convert StringValues to a regular string 50 | if (string.IsNullOrEmpty(apiKey)) 51 | { 52 | context.Result = new UnauthorizedResult(); 53 | return; 54 | } 55 | 56 | var existingApiKey = await repository.Get(apiKey, "key"); 57 | 58 | if (existingApiKey == null) 59 | { 60 | context.Result = new UnauthorizedResult(); 61 | return; 62 | } 63 | 64 | context.HttpContext.Items[ApiKey] = existingApiKey; 65 | 66 | await Task.CompletedTask; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Floom.Tests/ModelsUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace FloomTests; 5 | 6 | [TestClass] 7 | public class ModelsUnitTest 8 | { 9 | // private Mock> _mockLogger; 10 | // private Mock _mockModelsService; 11 | // private ModelsController _controller; 12 | 13 | public ModelsUnitTest() 14 | { 15 | // _mockLogger = new Mock>(); 16 | // _mockModelsService = new Mock(); 17 | // var expectedModels = new List 18 | // { 19 | // new ModelDtoV1() 20 | // { 21 | // id= "open-ai-model2", 22 | // } 23 | // }; 24 | // _mockModelsService.Setup(service => service.GetAll()).ReturnsAsync(expectedModels); 25 | // _controller = new ModelsController(_mockLogger.Object, _mockModelsService.Object); 26 | } 27 | 28 | [TestMethod] 29 | public async Task TestMethod1() 30 | { 31 | // Arrange 32 | // var mockRequest = new ModelDtoV1(); 33 | 34 | // Act 35 | // var result = await _controller.Apply(mockRequest); 36 | 37 | // Assert 38 | // Assert the expected behavior. This could be checking the status code, the content of the response, etc. 39 | // Assert.IsNotNull(result); 40 | } 41 | 42 | [TestMethod] 43 | public async Task TestApplyEndpoint() 44 | { 45 | // Arrange 46 | var client = new HttpClient(); 47 | var request = new HttpRequestMessage 48 | { 49 | Method = HttpMethod.Post, 50 | RequestUri = new Uri("http://localhost:80/v1/Models/Apply"), 51 | Headers = 52 | { 53 | { "Api-Key", "kTOWPaeL8gikO6IzISudkDygyaZHjxf4" }, 54 | }, 55 | Content = new StringContent( 56 | @"{ 57 | ""id"": ""open-ai-model2"", 58 | ""model"": ""gpt-3.5-turbo-1106"", 59 | ""vendor"": ""OpenAI"", 60 | ""apiKey"": ""TEST"" 61 | }", Encoding.UTF8, "application/json") 62 | }; 63 | 64 | // Act 65 | var response = await client.SendAsync(request); 66 | 67 | // Assert 68 | Assert.IsTrue(response.IsSuccessStatusCode); 69 | 70 | // var models = await _controller.Get(); 71 | 72 | // Assert.IsInstanceOfType(models.Result, typeof(OkObjectResult)); 73 | 74 | // foreach (var model in models.Value) 75 | { 76 | // Assert.IsNotNull(model.id, "Model should not be null"); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /Floom.Core/Repository/Repository.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using Floom.Utils; 3 | using MongoDB.Driver; 4 | 5 | namespace Floom.Repository; 6 | 7 | public interface IRepository where T : DatabaseEntity 8 | { 9 | Task Insert(T entity); 10 | Task UpsertEntity(T entity, string uid, string uniqueKey); 11 | Task Delete(string id, string uniqueKey); 12 | Task Get(string id, string uniqueKey); 13 | Task> GetAll(); 14 | Task> GetAll(string id, string uniqueKey); 15 | Task FindByCondition(Expression> condition); 16 | Task FindByAttributesAsync(Dictionary attributes); 17 | Task> ListByConditionAsync(Expression> condition); 18 | // New method to support MongoDB filter queries 19 | Task> ListByFilterAsync(FilterDefinition filter); 20 | } 21 | 22 | public class Repository : IRepository where T : DatabaseEntity 23 | { 24 | private readonly IDatabase _database; 25 | 26 | public Repository(IDatabase database) 27 | { 28 | _database = database; 29 | } 30 | 31 | public Task Insert(T entity) 32 | { 33 | entity.createdAt = DateTime.UtcNow; 34 | entity.AddCreatedByApiKey(HttpContextHelper.GetApiKeyFromHttpContext()); 35 | return _database.Create(entity); 36 | } 37 | 38 | public async Task UpsertEntity(T databaseEntity, string uid, string column) 39 | { 40 | await _database.Upsert(databaseEntity, uid, column); 41 | } 42 | 43 | public async Task Delete(string name, string uniqueKey) 44 | { 45 | await _database.Delete(name, uniqueKey); 46 | } 47 | 48 | public async Task Get(string value, string key) 49 | { 50 | return await _database.Read(value, key); 51 | } 52 | 53 | public async Task> GetAll() 54 | { 55 | return await _database.ReadAll(); 56 | } 57 | 58 | public async Task> GetAll(string id, string uniqueKey) 59 | { 60 | return await _database.ReadAll(id, uniqueKey); 61 | } 62 | 63 | public async Task FindByCondition(Expression> condition) 64 | { 65 | return await _database.ReadByCondition(condition); 66 | } 67 | 68 | public async Task FindByAttributesAsync(Dictionary attributes) 69 | { 70 | return await _database.ReadByAttributes(attributes); 71 | } 72 | 73 | public async Task> ListByConditionAsync(Expression> condition) 74 | { 75 | return await _database.ReadAllByCondition(condition); 76 | } 77 | 78 | public async Task> ListByFilterAsync(FilterDefinition filter) 79 | { 80 | return await _database.ReadAllByFilter(filter); 81 | } 82 | } -------------------------------------------------------------------------------- /Floom.Core/Utils/MongoUtils.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | 3 | namespace Floom.Utils; 4 | 5 | public static class MongoUtils 6 | { 7 | public static BsonDocument ConvertToBsonDocument(Dictionary configuration) 8 | { 9 | var bsonDocument = new BsonDocument(); 10 | foreach (var kvp in configuration) 11 | { 12 | BsonValue bsonValue; 13 | 14 | // Check the type of the value and convert accordingly 15 | switch (kvp.Value) 16 | { 17 | case string stringValue: 18 | bsonValue = new BsonString(stringValue); 19 | break; 20 | case IEnumerable stringEnumerable: 21 | bsonValue = new BsonArray(stringEnumerable.Select(s => new BsonString(s))); 22 | break; 23 | case Dictionary subDict: 24 | bsonValue = ConvertToBsonDocument(subDict); // Recursive call for nested dictionaries 25 | break; 26 | // Add more cases as needed for other types 27 | default: 28 | bsonValue = BsonValue.Create(kvp.Value); // Fallback for simple types or null values 29 | break; 30 | } 31 | 32 | bsonDocument.Add(kvp.Key, bsonValue); 33 | } 34 | 35 | return bsonDocument; 36 | } 37 | 38 | public static Dictionary ConvertBsonDocumentToDictionary(BsonDocument bsonDocument) 39 | { 40 | var dictionary = new Dictionary(); 41 | 42 | foreach (var element in bsonDocument) 43 | { 44 | object value = ConvertBsonValue(element.Value); 45 | dictionary.Add(element.Name, value); 46 | } 47 | 48 | return dictionary; 49 | } 50 | 51 | private static object ConvertBsonValue(BsonValue bsonValue) 52 | { 53 | switch (bsonValue.BsonType) 54 | { 55 | case BsonType.Document: 56 | return ConvertBsonDocumentToDictionary(bsonValue.AsBsonDocument); 57 | case BsonType.Array: 58 | return bsonValue.AsBsonArray.Select(ConvertBsonValue).ToList(); 59 | case BsonType.String: 60 | return bsonValue.AsString; 61 | case BsonType.Int32: 62 | return bsonValue.AsInt32; 63 | case BsonType.Int64: 64 | return bsonValue.AsInt64; 65 | case BsonType.Boolean: 66 | return bsonValue.AsBoolean; 67 | case BsonType.Double: 68 | return bsonValue.AsDouble; 69 | case BsonType.DateTime: 70 | return bsonValue.ToUniversalTime(); 71 | // Add more cases as necessary for other BsonTypes you expect to handle 72 | default: 73 | return BsonTypeMapper.MapToDotNetValue(bsonValue); // Fallback for types not explicitly handled 74 | } 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /Floom.Core/Repository/MongoDb/MongoDbInitializer.cs: -------------------------------------------------------------------------------- 1 | using Floom.Logs; 2 | using MongoDB.Bson; 3 | using MongoDB.Driver; 4 | 5 | namespace Floom.Repository.MongoDb; 6 | 7 | public class MongoDbInitializer 8 | { 9 | private readonly IMongoClient _client; 10 | private readonly ILogger _logger; 11 | 12 | public MongoDbInitializer(IMongoClient client) 13 | { 14 | _client = client; 15 | _logger = FloomLoggerFactory.CreateLogger(GetType()); 16 | } 17 | 18 | public async Task Initialize(string database) 19 | { 20 | if (await EnsureMongoDBConnection()) 21 | { 22 | var dbList = _client.ListDatabases().ToList().Select(db => db["name"].AsString); 23 | if (!dbList.Contains(database)) 24 | { 25 | _logger.LogWarning("Database {Database} does not exist. Creating it...", database); 26 | var db = _client.GetDatabase(database); 27 | var collection = db.GetCollection("DummyCollection"); 28 | 29 | collection.InsertOne(new { DummyField = "DummyValue" }); 30 | collection.DeleteOne(Builders.Filter.Eq("DummyField", "DummyValue")); 31 | db.DropCollection("DummyCollection"); 32 | 33 | _logger.LogInformation("Database {Database} created.", database); 34 | } 35 | else 36 | { 37 | _logger.LogInformation("Database {Database} already exists.", database); 38 | } 39 | } 40 | else 41 | { 42 | _logger.LogError("Failed to establish a connection to MongoDB after multiple attempts."); 43 | } 44 | } 45 | 46 | private async Task EnsureMongoDBConnection() 47 | { 48 | int attempts = 5; 49 | for (int attempt = 1; attempt <= attempts; attempt++) 50 | { 51 | _logger.LogInformation("Attempting to connect to MongoDB, attempt {Attempt} of {Attempts}", attempt, attempts); 52 | if (await TestConnection()) 53 | { 54 | _logger.LogInformation("Successfully connected to MongoDB."); 55 | return true; 56 | } 57 | 58 | if (attempt < attempts) 59 | { 60 | _logger.LogWarning("Failed to connect to MongoDB, retrying in 15 seconds..."); 61 | await Task.Delay(TimeSpan.FromSeconds(15)); 62 | } 63 | } 64 | 65 | return false; 66 | } 67 | 68 | public async Task TestConnection() 69 | { 70 | try 71 | { 72 | var database = _client.GetDatabase("admin"); 73 | var command = new BsonDocument("ping", 1); 74 | await database.RunCommandAsync(command); 75 | return true; 76 | } 77 | catch (Exception ex) 78 | { 79 | _logger.LogError(ex, "An error occurred while testing the MongoDB connection."); 80 | return false; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /Floom.Core/Floom.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 83f35f74-cbe3-4641-b6bf-3273ab6aa71a 8 | Linux 9 | . 10 | 1.1.8 11 | Floom 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | PreserveNewest 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Floom.Core/Auth/UsersService.cs: -------------------------------------------------------------------------------- 1 | using Floom.Controllers; 2 | using Floom.Repository; 3 | 4 | namespace Floom.Auth; 5 | 6 | public class RegisterUserResponse 7 | { 8 | public string ApiKey { get; set; } 9 | public string Username { get; set; } 10 | public string Nickname { get; set; } 11 | } 12 | 13 | public interface IUsersService 14 | { 15 | Task RegisterGuestUserAsync(); 16 | Task RegisterOrLoginUserAsync(string provider, string email, string? firstName = null, string? lastName = null); 17 | 18 | Task LogoutUserByApiKeyAsync(string apiKey); 19 | } 20 | 21 | public class UsersService : IUsersService 22 | { 23 | private readonly IRepository _userRepository; 24 | private readonly IRepository _apiKeyRepository; 25 | 26 | public UsersService(IRepositoryFactory repositoryFactory) 27 | { 28 | _userRepository = repositoryFactory.Create(); 29 | _apiKeyRepository = repositoryFactory.Create(); 30 | } 31 | 32 | public async Task RegisterGuestUserAsync() 33 | { 34 | var user = new UserEntity 35 | { 36 | validated = false, 37 | type = "guest", 38 | username = FloomUsernameGenerator.GenerateTemporaryUsername(), 39 | nickname = FloomUsernameGenerator.GenerateTemporaryNickname() 40 | }; 41 | return await RegisterUserAsync(user); 42 | } 43 | 44 | public async Task RegisterOrLoginUserAsync(string provider, string email, string? firstName = null, string? lastName = null) 45 | { 46 | var existingUser = await _userRepository.Get(email, "emailAddress"); 47 | 48 | if (existingUser != null && existingUser.registrationProvider == provider) 49 | { 50 | return await GenerateApiKeyForUserAsync(existingUser); 51 | } 52 | 53 | var user = new UserEntity 54 | { 55 | registrationProvider = provider, 56 | validated = true, 57 | type = "user", 58 | emailAddress = email, 59 | username = FloomUsernameGenerator.GenerateTemporaryUsername(), 60 | nickname = FloomUsernameGenerator.GenerateTemporaryNickname(), 61 | firstName = firstName, 62 | lastName = lastName 63 | }; 64 | return await RegisterUserAsync(user); 65 | } 66 | 67 | private async Task RegisterUserAsync(UserEntity user) 68 | { 69 | await _userRepository.Insert(user); 70 | return await GenerateApiKeyForUserAsync(user); 71 | } 72 | 73 | private async Task GenerateApiKeyForUserAsync(UserEntity user) 74 | { 75 | var apiKey = new ApiKeyEntity 76 | { 77 | userId = user.Id, 78 | key = ApiKeyUtils.GenerateApiKey() 79 | }; 80 | 81 | await _apiKeyRepository.Insert(apiKey); 82 | 83 | return new RegisterUserResponse 84 | { 85 | ApiKey = apiKey.key, 86 | Username = user.username, 87 | Nickname = user.nickname 88 | }; 89 | } 90 | 91 | async Task IUsersService.LogoutUserByApiKeyAsync(string apiKey) 92 | { 93 | await _apiKeyRepository.Delete(apiKey, "key"); 94 | 95 | return true; 96 | } 97 | } -------------------------------------------------------------------------------- /Floom.Core/Repository/MongoDb/MongoDatabase.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using MongoDB.Bson; 3 | using MongoDB.Driver; 4 | 5 | namespace Floom.Repository; 6 | 7 | public class MongoDatabase : IDatabase where T : DatabaseEntity 8 | { 9 | protected IMongoCollection _collection; 10 | 11 | public MongoDatabase(IMongoClient mongoClient, string collectionName) 12 | { 13 | var database = mongoClient.GetDatabase("Floom"); 14 | _collection = database.GetCollection(collectionName); 15 | } 16 | 17 | public Task Create(T entity) 18 | { 19 | if(string.IsNullOrEmpty(entity.Id)) 20 | { 21 | entity.Id = ObjectId.GenerateNewId().ToString(); 22 | } 23 | return _collection.InsertOneAsync(entity); 24 | } 25 | 26 | public async Task Read(string value, string uniqueKey = "_id") 27 | { 28 | var filter = Builders.Filter.Eq(uniqueKey, value); 29 | 30 | return await _collection.Find(filter).FirstOrDefaultAsync(); 31 | } 32 | 33 | public async Task ReadByCondition(Expression> condition) 34 | { 35 | return await _collection.Find(condition).FirstOrDefaultAsync(); 36 | } 37 | 38 | public async Task ReadByAttributes(Dictionary attributes) 39 | { 40 | var filters = new List>(); 41 | 42 | foreach (var attribute in attributes) 43 | { 44 | var filter = Builders.Filter.Eq(attribute.Key, attribute.Value); 45 | filters.Add(filter); 46 | } 47 | 48 | var combinedFilter = Builders.Filter.And(filters); 49 | 50 | return await _collection.Find(combinedFilter).FirstOrDefaultAsync(); 51 | } 52 | 53 | public async Task> ReadAll() 54 | { 55 | return await _collection.Find(_ => true).ToListAsync(); 56 | } 57 | 58 | public async Task> ReadAll(string id, string uniqueKey = "_id") 59 | { 60 | FilterDefinition filter; 61 | if (uniqueKey.Equals("_id")) 62 | { 63 | filter = Builders.Filter.Eq(uniqueKey, new ObjectId(id)); 64 | } 65 | else 66 | { 67 | filter = Builders.Filter.Eq(uniqueKey, id); 68 | } 69 | 70 | return await _collection.Find(filter).ToListAsync(); 71 | 72 | } 73 | 74 | public async Task> ReadAllByCondition(Expression> condition) 75 | { 76 | return await _collection.Find(condition).ToListAsync(); 77 | } 78 | 79 | public async Task Upsert(T entity, string uid, string column) 80 | { 81 | var filter = Builders.Filter.Eq(column, uid); 82 | 83 | var existingItem = await _collection.Find(filter).FirstOrDefaultAsync(); 84 | 85 | if (existingItem != null) 86 | { 87 | entity.Id = existingItem.Id; 88 | var updateFilter = Builders.Filter.Eq("_id", ((dynamic)existingItem).Id); 89 | await _collection.ReplaceOneAsync(updateFilter, entity); 90 | } 91 | else 92 | { 93 | await Create(entity); 94 | } 95 | } 96 | 97 | public async Task Delete(string value, string uniqueKey = "name") 98 | { 99 | var filter = Builders.Filter.Eq(uniqueKey, value); 100 | 101 | await _collection.DeleteOneAsync(filter); 102 | } 103 | 104 | public async Task> ReadAllByFilter(FilterDefinition filter) 105 | { 106 | return await _collection.Find(filter).ToListAsync(); 107 | } 108 | } -------------------------------------------------------------------------------- /Floom.Python/main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | from fastapi import FastAPI, UploadFile, File, Form, HTTPException 5 | from pydantic import BaseModel 6 | from typing import Any, Dict 7 | import tempfile 8 | import os 9 | import json 10 | import importlib.util 11 | from contextlib import contextmanager 12 | from mangum import Mangum 13 | 14 | # Set up logging 15 | logger = logging.getLogger() 16 | logger.setLevel(logging.INFO) 17 | handler = logging.StreamHandler(sys.stdout) 18 | handler.setLevel(logging.INFO) 19 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 20 | handler.setFormatter(formatter) 21 | logger.addHandler(handler) 22 | 23 | app = FastAPI() 24 | 25 | 26 | class Config(BaseModel): 27 | input: str 28 | variables: Dict[str, Any] 29 | config: Dict[str, Any] 30 | env: Dict[str, Any] 31 | 32 | 33 | @contextmanager 34 | def set_env_vars(env_vars: Dict[str, str]): 35 | original_env_vars = {key: os.environ.get(key) for key in env_vars} 36 | os.environ.update(env_vars) 37 | try: 38 | yield 39 | finally: 40 | for key, value in original_env_vars.items(): 41 | if value is None: 42 | del os.environ[key] 43 | else: 44 | os.environ[key] = value 45 | 46 | 47 | def load_module_from_file(file_path): 48 | spec = importlib.util.spec_from_file_location("module.name", file_path) 49 | module = importlib.util.module_from_spec(spec) 50 | spec.loader.exec_module(module) 51 | return module 52 | 53 | 54 | @app.post("/prompt") 55 | async def prompt(file: UploadFile = File(...), config: str = Form(...)): 56 | logger.info("Received request with file: %s", file.filename) 57 | # Step 1: Check file extension 58 | if not file.filename.endswith(".py"): 59 | logger.error("Invalid file extension") 60 | raise HTTPException(status_code=400, detail="Only Python files are allowed.") 61 | 62 | # Step 2: Parse JSON config 63 | try: 64 | config_data = json.loads(config) 65 | except json.JSONDecodeError: 66 | logger.error("Invalid JSON in config") 67 | raise HTTPException(status_code=400, detail="Invalid JSON in config.") 68 | config = Config(**config_data) 69 | 70 | # Flatten the config to include input, variables, and env in one dictionary 71 | flattened_config = {"input": config.input} 72 | flattened_config.update(config.variables) 73 | 74 | # Step 3: Save file to temporary location 75 | with tempfile.NamedTemporaryFile(delete=False, suffix=".py") as temp_file: 76 | temp_file.write(await file.read()) 77 | temp_file_path = temp_file.name 78 | logger.info("Saved file to temporary location: %s", temp_file_path) 79 | 80 | # Step 4: Execute the chain with the config and context manager 81 | try: 82 | with set_env_vars(config.env): 83 | module = load_module_from_file(temp_file_path) 84 | 85 | if not hasattr(module, 'chain'): 86 | logger.error("The provided Python file does not contain 'chain' variable") 87 | raise HTTPException(status_code=400, 88 | detail="The provided Python file does not contain 'chain' variable.") 89 | 90 | chain = module.chain 91 | # 92 | result = chain.invoke(flattened_config) 93 | output = result 94 | except Exception as e: 95 | logger.error("Error during execution: %s", str(e)) 96 | raise HTTPException(status_code=500, detail=str(e)) 97 | finally: 98 | os.remove(temp_file_path) 99 | logger.info("Temporary file removed: %s", temp_file_path) 100 | 101 | return {"result": output} 102 | 103 | 104 | handler = Mangum(app) 105 | 106 | if __name__ == "__main__": 107 | import uvicorn 108 | uvicorn.run(app, host="0.0.0.0", port=8000) 109 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy to EC2 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build-and-deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | # Checkout the code 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | 17 | # Set up .NET Core environment 18 | - name: Setup .NET Core 19 | uses: actions/setup-dotnet@v1 20 | with: 21 | dotnet-version: '7.0.309' 22 | 23 | # Restore and Build the project 24 | - name: Restore and Build Project 25 | run: | 26 | CORE_OUTPUT_DIR="./build/Floom.Core" 27 | 28 | # Clean up previous build 29 | rm -rf "./build" 30 | mkdir -p "$CORE_OUTPUT_DIR/" 31 | 32 | # Restore dependencies 33 | echo "Restoring dependencies for Floom.Core..." 34 | dotnet restore "Floom.Core/Floom.Core.csproj" 35 | 36 | # Build and publish Floom.Core project 37 | echo "Building and publishing the project..." 38 | dotnet publish "Floom.Core/Floom.Core.csproj" -c Release -o "$CORE_OUTPUT_DIR" 39 | 40 | # Zip the build artifacts 41 | echo "Zipping the build artifacts..." 42 | cd "./build" 43 | zip -r "../floom_core_build.zip" "Floom.Core" 44 | cd .. 45 | 46 | # Validate the zip file to make sure it's not zero bytes 47 | echo "Checking if the zip file is created and not empty..." 48 | if [ ! -s floom_core_build.zip ]; then 49 | echo "Error: The zip file is empty or not created." 50 | exit 1 51 | fi 52 | 53 | # Check the size of the zip file (should be around 50MB) 54 | echo "Checking if the zip file size is close to 50MB..." 55 | ZIP_FILE_SIZE=$(du -m floom_core_build.zip | cut -f1) # File size in MB 56 | echo "Zip file size: ${ZIP_FILE_SIZE} MB" 57 | if [ "$ZIP_FILE_SIZE" -lt 45 ] || [ "$ZIP_FILE_SIZE" -gt 55 ]; then 58 | echo "Error: Zip file size is not within the expected range (45MB - 55MB)." 59 | exit 1 60 | fi 61 | 62 | # Check the contents of the zip file to ensure it has the Floom.Core directory 63 | echo "Checking contents of the zip file..." 64 | unzip -l floom_core_build.zip | grep "Floom.Core/" > /dev/null 65 | if [ $? -ne 0 ]; then 66 | echo "Error: Floom.Core directory not found in the zip file." 67 | exit 1 68 | fi 69 | 70 | echo "Zip file created successfully and contains Floom.Core directory with the correct size." 71 | 72 | # Upload the build artifact to the remote server 73 | - name: Upload build artifact to EC2 74 | run: | 75 | REMOTE_HOST="${{ secrets.REMOTE_HOST }}" 76 | PEM_FILE="floom-gateway-pair.pem" 77 | 78 | # Create PEM file from secret 79 | mkdir -p Certificates 80 | echo "${{ secrets.PEM_FILE_CONTENT }}" > Certificates/$PEM_FILE 81 | chmod 600 Certificates/$PEM_FILE 82 | 83 | # Disable host key checking and copy the zip file to the remote EC2 server 84 | echo "Copying the build archive to the remote server ($REMOTE_HOST)..." 85 | scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i "Certificates/$PEM_FILE" floom_core_build.zip "$REMOTE_HOST:~/" 86 | 87 | # Deploy on the remote server (SSH) 88 | - name: Deploy on EC2 Server 89 | run: | 90 | REMOTE_HOST="${{ secrets.REMOTE_HOST }}" 91 | REMOTE_DIR="/var/floom" 92 | SERVICE_NAME="floom.service" 93 | BUILD_ARCHIVE="floom_core_build.zip" 94 | PEM_FILE="floom-gateway-pair.pem" 95 | 96 | echo "Connecting to the remote server and deploying..." 97 | ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i "Certificates/$PEM_FILE" "$REMOTE_HOST" bash -c " 98 | set -e 99 | echo 'Stopping the service...' 100 | sudo systemctl stop $SERVICE_NAME 101 | 102 | echo 'Cleaning up old files...' 103 | sudo rm -rf $REMOTE_DIR/* 104 | 105 | echo 'Creating necessary directories...' 106 | sudo mkdir -p $REMOTE_DIR 107 | 108 | echo 'Unzipping the new build...' 109 | sudo unzip -o ~/$BUILD_ARCHIVE -d $REMOTE_DIR 110 | 111 | echo 'Deployment completed successfully. Files are unzipped into Floom.Core.' 112 | 113 | echo 'Removing the zip file...' 114 | rm ~/$BUILD_ARCHIVE 115 | 116 | echo 'Starting the service...' 117 | sudo systemctl start $SERVICE_NAME 118 | 119 | echo 'Deployment completed successfully.' 120 | " 121 | 122 | 123 | 124 | # Clean up local zip file 125 | - name: Clean up local zip file 126 | run: | 127 | echo "Cleaning up local zip file..." 128 | rm floom_core_build.zip 129 | -------------------------------------------------------------------------------- /Floom.Core/Dockerfiles/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # ______ _ _____ 2 | # | ____| | /\ |_ _| 3 | # | |__ | | ___ ___ _ __ ___ / \ | | 4 | # | __| | |/ _ \ / _ \| '_ ` _ \ / /\ \ | | 5 | # | | | | (_) | (_) | | | | | |_ / ____ \ _| |_ 6 | # |_| |_|\___/ \___/|_| |_| |_(_)_/ \_\_____| 7 | 8 | # Visit https://floom.ai/ for more info. 9 | # Floom's GitHub: https://github.com/FloomAI | Floom: https://github.com/FloomAI/Floom 10 | 11 | # Run "docker compose up -d" in this file's directory 12 | 13 | # Floom default installation comes with: 14 | # 1. MongoDB (for management, logging, auditing, monitoring, authentication etc.) 15 | # 2. Milvus Vector Database (vector database for quality search, embeddings store, smart caching etc.) 16 | # ----------------------------------------------------------------------------------------------------- 17 | # The default setup is sufficient for dev/test environments. 18 | # For production, Floom recommends using production-grade managed databases: MongoDB and Milvus/Pinecone. 19 | # ----------------------------------------------------------------------------------------------------- 20 | # To use an external management database (MongoDB): 21 | # Modify the FLOOM_DB_USER, FLOOM_DB_PASSWORD and FLOOM_DB_ADDRESS environment variables in 'floom' service. 22 | # ----------------------------------------------------------------------------------------------------- 23 | # To use an external vector database (Milvus/Pinecone): 24 | # Modify VDB_VENDOR, VDB_APIKEY, VDB_ENDPOINT, VDB_ENVIRONMENT, VDB_PORT environment variables in 'floom' service. 25 | 26 | version: '3.8' 27 | 28 | services: 29 | 30 | floom: 31 | image: floomai/floom:latest 32 | container_name: floom-core 33 | environment: 34 | - FLOOM_DB_ADDRESS=mongo:27017 35 | - FLOOM_DB_USER=root 36 | - FLOOM_DB_PASSWORD=MyFloom 37 | - FLOOM_DEPENDENCIES_PATH=./DLLs 38 | - FLOOM_ENVIRONMENT=local 39 | - FLOOM_AUTHENTICATION=false 40 | - FLOOM_DATABASE_TYPE=mongodb 41 | - FLOOM_VDB_VENDOR=Milvus 42 | - FLOOM_VDB_ENDPOINT=standalone 43 | - FLOOM_VDB_PORT=19530 44 | - FLOOM_VDB_USERNAME=root 45 | - FLOOM_VDB_PASSWORD=Milvus 46 | volumes: 47 | - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/floom:/app/logs 48 | healthcheck: 49 | test: ["CMD", "curl", "-f", "http://localhost:80/v1/Misc/Health"] 50 | interval: 10s 51 | timeout: 10s 52 | retries: 2 53 | ports: 54 | - "4050:4050" 55 | depends_on: 56 | mongo: 57 | condition: service_healthy 58 | 59 | # Floom comes with MongoDB for development and testing purposes. 60 | # You can configure your own MongoDB for production purposes and comment this one out. 61 | mongo: 62 | image: mongo 63 | restart: always 64 | environment: 65 | - MONGO_INITDB_ROOT_USERNAME=root 66 | - MONGO_INITDB_ROOT_PASSWORD=MyFloom 67 | volumes: 68 | - ../docker_volumes/mongo:/data/db 69 | healthcheck: 70 | test: [ "CMD", "mongosh", "--eval", "db.adminCommand('ping')" ] 71 | interval: 10s 72 | timeout: 10s 73 | retries: 10 74 | ports: 75 | - "4060:27017" 76 | 77 | etcd: 78 | container_name: milvus-etcd 79 | image: quay.io/coreos/etcd:v3.5.5 80 | environment: 81 | - ETCD_AUTO_COMPACTION_MODE=revision 82 | - ETCD_AUTO_COMPACTION_RETENTION=1000 83 | - ETCD_QUOTA_BACKEND_BYTES=4294967296 84 | - ETCD_SNAPSHOT_COUNT=50000 85 | volumes: 86 | - ../docker_volumes/etcd:/etcd 87 | command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd 88 | healthcheck: 89 | test: [ "CMD", "curl", "-f", "http://localhost:2379/health" ] 90 | interval: 30s 91 | timeout: 20s 92 | retries: 3 93 | 94 | minio: 95 | container_name: milvus-minio 96 | image: minio/minio:RELEASE.2023-03-20T20-16-18Z 97 | environment: 98 | MINIO_ACCESS_KEY: minioadmin 99 | MINIO_SECRET_KEY: minioadmin 100 | ports: 101 | - "9001:9001" 102 | - "9000:9000" 103 | volumes: 104 | - ../docker_volumes/minio:/minio_data 105 | command: minio server /minio_data --console-address ":9001" 106 | healthcheck: 107 | test: [ "CMD", "curl", "-f", "http://localhost:9000/minio/health/live" ] 108 | interval: 30s 109 | timeout: 20s 110 | retries: 3 111 | 112 | standalone: 113 | container_name: milvus-standalone 114 | image: milvusdb/milvus:v2.3.0 115 | command: [ "milvus", "run", "standalone" ] 116 | environment: 117 | - ETCD_ENDPOINTS=etcd:2379 118 | - MINIO_ADDRESS=minio:9000 119 | volumes: 120 | - ../docker_volumes/milvus:/var/lib/milvus 121 | healthcheck: 122 | test: [ "CMD", "curl", "-f", "http://localhost:9091/healthz" ] 123 | interval: 30s 124 | start_period: 90s 125 | timeout: 20s 126 | retries: 3 127 | ports: 128 | - "19530:19530" 129 | - "9091:9091" 130 | depends_on: 131 | - "etcd" 132 | - "minio" 133 | 134 | networks: 135 | default: 136 | name: floom -------------------------------------------------------------------------------- /Floom.Core/Dockerfiles/docker-compose-local.yml: -------------------------------------------------------------------------------- 1 | # ______ _ _____ 2 | # | ____| | /\ |_ _| 3 | # | |__ | | ___ ___ _ __ ___ / \ | | 4 | # | __| | |/ _ \ / _ \| '_ ` _ \ / /\ \ | | 5 | # | | | | (_) | (_) | | | | | |_ / ____ \ _| |_ 6 | # |_| |_|\___/ \___/|_| |_| |_(_)_/ \_\_____| 7 | 8 | # Visit https://floom.ai/ for more info. 9 | # Floom's GitHub: https://github.com/FloomAI | Floom: https://github.com/FloomAI/Floom 10 | 11 | # Run "docker compose up -d" in this file's directory 12 | 13 | # Floom default installation comes with: 14 | # 1. MongoDB (for management, logging, auditing, monitoring, authentication etc.) 15 | # 2. Milvus Vector Database (vector database for quality search, embeddings store, smart caching etc.) 16 | # ----------------------------------------------------------------------------------------------------- 17 | # The default setup is sufficient for dev/test environments. 18 | # For production, Floom recommends using production-grade managed databases: MongoDB and Milvus/Pinecone. 19 | # ----------------------------------------------------------------------------------------------------- 20 | # To use an external management database (MongoDB): 21 | # Modify the FLOOM_DB_USER, FLOOM_DB_PASSWORD and FLOOM_DB_ADDRESS environment variables in 'floom' service. 22 | # ----------------------------------------------------------------------------------------------------- 23 | # To use an external vector database (Milvus/Pinecone): 24 | # Modify VDB_VENDOR, VDB_APIKEY, VDB_ENDPOINT, VDB_ENVIRONMENT, VDB_PORT environment variables in 'floom' service. 25 | 26 | version: '3.8' 27 | 28 | services: 29 | 30 | floom: 31 | container_name: floom 32 | image: floomai/floom:v3.0.1 33 | build: 34 | context: . 35 | dockerfile: Dockerfile 36 | environment: 37 | - FLOOM_DB_ADDRESS=mongo:27017 38 | - FLOOM_DB_USER=root 39 | - FLOOM_DB_PASSWORD=MyFloom 40 | - FLOOM_DEPENDENCIES_PATH=./DLLs 41 | - FLOOM_ENVIRONMENT=local 42 | - FLOOM_AUTHENTICATION=false 43 | - FLOOM_DATABASE_TYPE=mongodb 44 | - FLOOM_VDB_VENDOR=Milvus 45 | - FLOOM_VDB_ENDPOINT=standalone 46 | - FLOOM_VDB_PORT=19530 47 | - FLOOM_VDB_USERNAME=root 48 | - FLOOM_VDB_PASSWORD=Milvus 49 | volumes: 50 | - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/floom:/app/logs 51 | healthcheck: 52 | test: ["CMD", "curl", "-f", "http://localhost:80/v1/Misc/Health"] 53 | interval: 10s 54 | timeout: 10s 55 | retries: 2 56 | ports: 57 | - "4050:4050" 58 | depends_on: 59 | - "mongo" 60 | 61 | # Floom comes with MongoDB for development and testing purposes. 62 | # You can configure your own MongoDB for production purposes and comment this one out. 63 | mongo: 64 | image: mongo 65 | restart: always 66 | environment: 67 | - MONGO_INITDB_ROOT_USERNAME=root 68 | - MONGO_INITDB_ROOT_PASSWORD=MyFloom 69 | volumes: 70 | - ../docker_volumes/mongo:/data/db 71 | healthcheck: 72 | test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ] 73 | interval: 30s 74 | timeout: 10s 75 | retries: 3 76 | ports: 77 | - "4060:27017" 78 | 79 | etcd: 80 | container_name: milvus-etcd 81 | image: quay.io/coreos/etcd:v3.5.5 82 | environment: 83 | - ETCD_AUTO_COMPACTION_MODE=revision 84 | - ETCD_AUTO_COMPACTION_RETENTION=1000 85 | - ETCD_QUOTA_BACKEND_BYTES=4294967296 86 | - ETCD_SNAPSHOT_COUNT=50000 87 | volumes: 88 | - ../docker_volumes/etcd:/etcd 89 | command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd 90 | healthcheck: 91 | test: [ "CMD", "curl", "-f", "http://localhost:2379/health" ] 92 | interval: 30s 93 | timeout: 20s 94 | retries: 3 95 | 96 | minio: 97 | container_name: milvus-minio 98 | image: minio/minio:RELEASE.2023-03-20T20-16-18Z 99 | environment: 100 | MINIO_ACCESS_KEY: minioadmin 101 | MINIO_SECRET_KEY: minioadmin 102 | ports: 103 | - "9001:9001" 104 | - "9000:9000" 105 | volumes: 106 | - ../docker_volumes/minio:/minio_data 107 | command: minio server /minio_data --console-address ":9001" 108 | healthcheck: 109 | test: [ "CMD", "curl", "-f", "http://localhost:9000/minio/health/live" ] 110 | interval: 30s 111 | timeout: 20s 112 | retries: 3 113 | 114 | standalone: 115 | container_name: milvus-standalone 116 | image: milvusdb/milvus:v2.3.0 117 | command: [ "milvus", "run", "standalone" ] 118 | environment: 119 | - ETCD_ENDPOINTS=etcd:2379 120 | - MINIO_ADDRESS=minio:9000 121 | volumes: 122 | - ../docker_volumes/milvus:/var/lib/milvus 123 | healthcheck: 124 | test: [ "CMD", "curl", "-f", "http://localhost:9091/healthz" ] 125 | interval: 30s 126 | start_period: 90s 127 | timeout: 20s 128 | retries: 3 129 | ports: 130 | - "19530:19530" 131 | - "9091:9091" 132 | depends_on: 133 | - "etcd" 134 | - "minio" 135 | 136 | networks: 137 | default: 138 | name: floom -------------------------------------------------------------------------------- /Floom.Core/Controllers/FunctionsController.cs: -------------------------------------------------------------------------------- 1 | using Floom.Auth; 2 | using Floom.Functions; 3 | using Floom.Utils; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace Floom.Controllers; 7 | 8 | [ApiController] 9 | [Route("/v{version:apiVersion}/[controller]")] 10 | [ApiVersion("1.0")] 11 | [ApiKeyAuthorization] 12 | public class FunctionsController : ControllerBase 13 | { 14 | private readonly IFunctionsService _functionsService; 15 | 16 | public FunctionsController(IFunctionsService functionsService) 17 | { 18 | _functionsService = functionsService; 19 | } 20 | 21 | [HttpPost("deploy")] 22 | public async Task DeployFunction([FromForm] IFormFile file) 23 | { 24 | // 1. Save the file to disk 25 | var filePath = Path.GetTempFileName(); 26 | try 27 | { 28 | using (var stream = System.IO.File.Create(filePath)) 29 | { 30 | await file.CopyToAsync(stream); 31 | } 32 | 33 | var userId = HttpContextHelper.GetUserIdFromHttpContext(); 34 | 35 | // 2. Deploy the function 36 | var functionName = await _functionsService.DeployFunctionAsync(filePath, userId); 37 | 38 | // return JSON with message, function name, which says, function X deployed successfully 39 | return Ok(new { message = $"Function {functionName} deployed successfully" }); 40 | } 41 | finally 42 | { 43 | // Ensure the temporary file is deleted 44 | if (System.IO.File.Exists(filePath)) 45 | { 46 | System.IO.File.Delete(filePath); 47 | } 48 | } 49 | } 50 | 51 | [HttpPost("run")] 52 | [AllowAnonymous] 53 | public async Task RunFunction([FromBody] RunFunctionRequest request) 54 | { 55 | var userId = HttpContextHelper.GetUserIdFromHttpContext(); 56 | var result = await _functionsService.RunFunctionAsync(userId, request.function, request.prompt, request.parameters); 57 | return Ok(result); 58 | } 59 | 60 | [HttpGet("list")] 61 | public async Task ListFunctions() 62 | { 63 | var userId = HttpContextHelper.GetUserIdFromHttpContext(); 64 | var functions = await _functionsService.ListFunctionsAsync(userId); 65 | return Ok(functions); 66 | } 67 | 68 | [HttpGet("featured")] 69 | [AllowAnonymous] 70 | public async Task ListPublicFeaturedFunctions() 71 | { 72 | var publicFeaturedFunctions = await _functionsService.ListPublicFeaturedFunctionsAsync(); 73 | return Ok(publicFeaturedFunctions); 74 | } 75 | 76 | [HttpGet("{name}")] 77 | [AllowAnonymous] 78 | public async Task GetFunctionByName(string name) 79 | { 80 | var userId = HttpContextHelper.GetUserIdFromHttpContext(); 81 | var function = await _functionsService.GetFunctionByNameAsync(userId, name); 82 | if (function == null) 83 | { 84 | return NotFound(new { message = $"Function {name} not found" }); 85 | } 86 | return Ok(function); 87 | } 88 | 89 | [HttpPost("search")] 90 | [AllowAnonymous] 91 | public async Task SearchPublicFunctions([FromBody] SearchRequest request) 92 | { 93 | var functions = await _functionsService.SearchPublicFunctionsAsync(request.query); 94 | return Ok(functions); 95 | } 96 | 97 | [HttpPost("addRoles")] 98 | public async Task AddRolesToFunction([FromBody] ModifyRolesRequest request) 99 | { 100 | try 101 | { 102 | var userId = HttpContextHelper.GetUserIdFromHttpContext(); 103 | await _functionsService.AddRolesToFunctionAsync(request.functionName, request.userId, userId); 104 | return Ok(new { message = $"Roles 'Public' and 'Featured' added to function {request.functionName}." }); 105 | } 106 | catch (UnauthorizedAccessException ex) 107 | { 108 | return Forbid(ex.Message); 109 | } 110 | catch (Exception ex) 111 | { 112 | return BadRequest(ex.Message); 113 | } 114 | } 115 | 116 | [HttpPost("removeRoles")] 117 | public async Task RemoveRolesFromFunction([FromBody] ModifyRolesRequest request) 118 | { 119 | try 120 | { 121 | var userId = HttpContextHelper.GetUserIdFromHttpContext(); 122 | await _functionsService.RemoveRolesToFunctionAsync(request.functionName, request.userId, userId); 123 | return Ok(new { message = $"Roles 'Public' and 'Featured' removed from function {request.functionName}." }); 124 | } 125 | catch (UnauthorizedAccessException ex) 126 | { 127 | return Forbid(ex.Message); 128 | } 129 | catch (Exception ex) 130 | { 131 | return BadRequest(ex.Message); 132 | } 133 | } 134 | } 135 | 136 | public class ModifyRolesRequest 137 | { 138 | public string functionName { get; set; } 139 | public string userId { get; set; } 140 | } 141 | 142 | public class RunFunctionRequest 143 | { 144 | public string function { get; set; } 145 | public string prompt { get; set; } 146 | public Dictionary? parameters { get; set; } 147 | } 148 | 149 | public class SearchRequest 150 | { 151 | public string query { get; set; } 152 | } -------------------------------------------------------------------------------- /Floom.Core/Utils/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Text; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Floom.Utils 6 | { 7 | public static class Extensions 8 | { 9 | public static bool OnlyIdHasValue(this T obj) 10 | { 11 | PropertyInfo idProperty = typeof(T).GetProperty("id"); 12 | 13 | if (idProperty == null) 14 | throw new ArgumentException("The 'id' property does not exist in the class."); 15 | 16 | var idValue = idProperty.GetValue(obj)?.ToString(); 17 | 18 | if (string.IsNullOrEmpty(idValue)) // Check if id is not set 19 | return false; 20 | 21 | // Check if all other properties are null or empty 22 | foreach (var property in typeof(T).GetProperties()) 23 | { 24 | if (property.Name != "id") 25 | { 26 | var value = property.GetValue(obj)?.ToString(); 27 | if (!string.IsNullOrEmpty(value)) 28 | return false; 29 | } 30 | } 31 | 32 | return true; 33 | } 34 | 35 | public static async Task GetRawBodyAsync( 36 | this HttpRequest request, 37 | Encoding? encoding = null) 38 | { 39 | if (!request.Body.CanSeek) 40 | { 41 | // We only do this if the stream isn't *already* seekable, 42 | // as EnableBuffering will create a new stream instance 43 | // each time it's called 44 | request.EnableBuffering(); 45 | } 46 | 47 | request.Body.Position = 0; 48 | 49 | var reader = new StreamReader(request.Body, encoding ?? Encoding.UTF8); 50 | 51 | var body = await reader.ReadToEndAsync().ConfigureAwait(false); 52 | 53 | request.Body.Position = 0; 54 | 55 | return body; 56 | } 57 | 58 | public static bool Compare(object obj1, object obj2) 59 | { 60 | if (obj1 == null || obj2 == null) 61 | return obj1 == null && obj2 == null; 62 | 63 | Type type1 = obj1.GetType(); 64 | Type type2 = obj2.GetType(); 65 | 66 | // Check if the underlying types are different 67 | if (GetUnderlyingType(type1) != GetUnderlyingType(type2)) 68 | return false; 69 | 70 | PropertyInfo[] properties = type1.GetProperties(); 71 | 72 | foreach (PropertyInfo property in properties) 73 | { 74 | Type propertyType = property.PropertyType; 75 | object value1 = property.GetValue(obj1); 76 | object value2 = type2.GetProperty(property.Name)?.GetValue(obj2); 77 | 78 | // Compare property values (considering nullability and underlying types) 79 | if (!AreValuesEqual(propertyType, value1, value2)) 80 | return false; 81 | 82 | // Recursive comparison for nested objects 83 | if (!AreNestedObjectsEqual(propertyType, value1, value2)) 84 | return false; 85 | } 86 | 87 | return true; 88 | } 89 | 90 | private static bool AreValuesEqual(Type propertyType, object value1, object value2) 91 | { 92 | if (propertyType.IsValueType) 93 | { 94 | if (GetUnderlyingType(propertyType) != null) 95 | { 96 | // Value type is nullable 97 | return Nullable.Equals(value1, value2); 98 | } 99 | else 100 | { 101 | // Non-nullable value type 102 | return Equals(value1, value2); 103 | } 104 | } 105 | else 106 | { 107 | // Reference type 108 | return Equals(value1, value2); 109 | } 110 | } 111 | 112 | public static string TrimNewLines(this string input) 113 | { 114 | return input.Trim('\n'); 115 | } 116 | 117 | public static string RemoveAnswerPrefix(this string input) 118 | { 119 | // Define the regular expression pattern to match the variations of "Answer:" at the beginning of the string 120 | string pattern = @"^(\s*answer\s*:?\s*)"; 121 | 122 | // Use Regex.Replace to remove the matched pattern from the input string 123 | string result = Regex.Replace(input, pattern, "", RegexOptions.IgnoreCase); 124 | 125 | return result; 126 | } 127 | 128 | private static bool AreNestedObjectsEqual(Type propertyType, object value1, object value2) 129 | { 130 | if (propertyType.IsValueType || propertyType == typeof(string)) 131 | { 132 | // Value types or strings are not considered nested objects 133 | return true; 134 | } 135 | 136 | // Handle null values 137 | if (value1 == null || value2 == null) 138 | return value1 == null && value2 == null; 139 | 140 | // Recursive comparison for nested objects 141 | return Compare(value1, value2); 142 | } 143 | 144 | private static Type GetUnderlyingType(Type type) 145 | { 146 | return Nullable.GetUnderlyingType(type) ?? type; 147 | } 148 | 149 | public static string CompileWithVariables(this string input, Dictionary variables) 150 | { 151 | //Fill Variables 152 | string compiledInput = input; 153 | if (variables == null) 154 | { 155 | return input; 156 | } 157 | 158 | foreach (var variableKvp in variables) 159 | { 160 | compiledInput = compiledInput.Replace("{{" + variableKvp.Key + "}}", variableKvp.Value); 161 | } 162 | 163 | return compiledInput.Trim(); 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /Floom.Core/Server/DynamicApiRoutingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.RegularExpressions; 3 | using System.Text.Json; 4 | 5 | namespace Floom.Server; 6 | 7 | public class DynamicApiRoutingMiddleware 8 | { 9 | private readonly RequestDelegate _next; 10 | private readonly ILogger _logger; 11 | private readonly HttpClient _httpClient; 12 | 13 | public DynamicApiRoutingMiddleware(RequestDelegate next, ILogger logger, HttpClient httpClient) 14 | { 15 | _next = next; 16 | _logger = logger; 17 | _httpClient = httpClient; 18 | } 19 | 20 | public async Task InvokeAsync(HttpContext context) 21 | { 22 | var host = context.Request.Host.Host; 23 | _logger.LogInformation($"DynamicApiRoutingMiddleware invoked for host: {host}"); 24 | if (Regex.IsMatch(host, @"^.+\.pipeline\.floom\.ai$")) 25 | { 26 | await ModifyAndForwardRequestAsync(context); 27 | } 28 | else 29 | { 30 | // Not a matching request, proceed to the next middleware 31 | await _next(context); 32 | } 33 | } 34 | 35 | private async Task ModifyAndForwardRequestAsync(HttpContext context) 36 | { 37 | // Extract the pipelineId and username from the host 38 | var regexInput = context.Request.Host.Host; 39 | var match = Regex.Match(regexInput, @"^(?.+)\.pipeline\.floom\.ai$"); 40 | if (!match.Success) 41 | { 42 | _logger.LogError("Failed to parse hostname for routing."); 43 | context.Response.StatusCode = StatusCodes.Status400BadRequest; 44 | return; 45 | } 46 | var pipelineId = match.Groups["pipelineId"].Value; 47 | 48 | var lastIndex = pipelineId.LastIndexOf('-'); 49 | 50 | // Split into the actual pipelineId and username based on the last '-' 51 | var actualPipelineId = pipelineId.Substring(0, lastIndex); 52 | var username = pipelineId.Substring(lastIndex + 1); 53 | 54 | // Read the incoming request body 55 | string requestBody; 56 | context.Request.EnableBuffering(); 57 | using (var reader = new StreamReader(context.Request.Body, Encoding.UTF8, true, 1024, true)) 58 | { 59 | requestBody = await reader.ReadToEndAsync(); 60 | context.Request.Body.Position = 0; // Reset the stream for any downstream middleware 61 | } 62 | var jsonDocument = JsonDocument.Parse(requestBody); 63 | string? inputString = null; 64 | if (jsonDocument.RootElement.TryGetProperty("prompt", out var inputElement)) 65 | { 66 | inputString = inputElement.GetString(); 67 | } 68 | object? responseTypeJson = null; 69 | if (jsonDocument.RootElement.TryGetProperty("responseType", out var responseTypeElement)) 70 | { 71 | responseTypeJson = responseTypeElement; 72 | } 73 | // Construct the new request URL and body 74 | var newUrl = "http://localhost:4050/v1/pipelines/run"; 75 | // Construct the payload object 76 | /* 77 | var payload = new RunFloomPipelineRequest() 78 | { 79 | pipelineId = actualPipelineId, 80 | username = username, 81 | prompt = inputString 82 | }; 83 | 84 | if (responseTypeJson != null) 85 | { 86 | payload.responseType = responseTypeJson; 87 | }*/ 88 | 89 | try 90 | { 91 | /* 92 | var newBody = JsonSerializer.Serialize(payload); 93 | 94 | var requestMessage = new HttpRequestMessage(HttpMethod.Post, newUrl) 95 | { 96 | Content = new StringContent(newBody, Encoding.UTF8, "application/json") 97 | }; 98 | 99 | // Copy headers from the incoming request to the outgoing request 100 | foreach (var header in context.Request.Headers) 101 | { 102 | // Check for headers that should not be copied directly 103 | if (!header.Key.Equals("Host", StringComparison.OrdinalIgnoreCase) && 104 | !header.Key.Equals("Content-Length", StringComparison.OrdinalIgnoreCase) && 105 | !header.Key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase)) 106 | { 107 | requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); 108 | } 109 | } 110 | 111 | // Forward the modified request 112 | var response = await _httpClient.SendAsync(requestMessage); 113 | 114 | context.Response.StatusCode = (int)response.StatusCode; 115 | 116 | // Explicitly copy the Content-Type header (and any other headers you wish to preserve) 117 | if (response.Content.Headers.ContentType != null) 118 | { 119 | context.Response.ContentType = response.Content.Headers.ContentType.ToString(); 120 | } 121 | 122 | // Optionally, forward other specific headers here 123 | foreach (var header in response.Headers) 124 | { 125 | // Avoid copying restricted headers like 'Transfer-Encoding' 126 | if (!context.Response.Headers.ContainsKey(header.Key) && 127 | !header.Key.Equals("Transfer-Encoding", StringComparison.OrdinalIgnoreCase)) 128 | { 129 | context.Response.Headers[header.Key] = header.Value.ToArray(); 130 | } 131 | } 132 | 133 | // Stream the proxied response content back to the original caller 134 | using (var responseStream = await response.Content.ReadAsStreamAsync()) 135 | { 136 | await responseStream.CopyToAsync(context.Response.Body); 137 | }*/ 138 | } 139 | catch (HttpRequestException ex) 140 | { 141 | _logger.LogError($"Error forwarding request: {ex.Message}"); 142 | context.Response.StatusCode = StatusCodes.Status500InternalServerError; 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /Floom.Core/Repository/DynamoDB/DynamoDbDatabase.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using Amazon.DynamoDBv2; 3 | using Amazon.DynamoDBv2.DataModel; 4 | using Amazon.DynamoDBv2.Model; 5 | using Floom.Utils; 6 | using MongoDB.Driver; 7 | 8 | namespace Floom.Repository.DynamoDB; 9 | 10 | public class DynamoDbDatabase : IDatabase where T : DatabaseEntity, new() 11 | { 12 | private readonly DynamoDBContext _context; 13 | private readonly IAmazonDynamoDB _dynamoDbClient; 14 | private readonly string _tableName; 15 | 16 | public DynamoDbDatabase(IAmazonDynamoDB client, string tableName) 17 | { 18 | _dynamoDbClient = client; 19 | var contextConfig = new DynamoDBContextConfig 20 | { 21 | IgnoreNullValues = true 22 | }; 23 | _context = new DynamoDBContext(client, contextConfig); 24 | _tableName = tableName; 25 | } 26 | 27 | public async Task Create(T entity) 28 | { 29 | // Check if the entity's ID is not set or empty, then assign a new GUID 30 | if (string.IsNullOrEmpty(entity.Id)) 31 | { 32 | entity.Id = Guid.NewGuid().ToString(); 33 | entity.createdAt = DateTime.UtcNow; 34 | } 35 | entity.AddCreatedByApiKey(HttpContextHelper.GetApiKeyFromHttpContext()); 36 | 37 | var config = new DynamoDBOperationConfig 38 | { 39 | OverrideTableName = _tableName 40 | }; 41 | 42 | await _context.SaveAsync(entity, config); 43 | } 44 | 45 | public async Task Read(string value, string property = "Id") 46 | { 47 | if (property == "Id") 48 | { 49 | var request = new GetItemRequest 50 | { 51 | TableName = _tableName, 52 | Key = new Dictionary 53 | { 54 | { "Id", new AttributeValue { S = value } } // Assuming the Id is of type String 55 | } 56 | }; 57 | 58 | var response = await _dynamoDbClient.GetItemAsync(request); 59 | if (response.Item == null || !response.IsItemSet) 60 | { 61 | return null; 62 | } 63 | 64 | // Assuming you have a method to convert a Dictionary to your entity type T 65 | return DynamoDbMapper.ConvertToEntity(response.Item); 66 | } 67 | else 68 | { 69 | // Query using GSI 70 | var indexName = $"{property}-index"; 71 | var queryRequest = new QueryRequest 72 | { 73 | TableName = _tableName, 74 | IndexName = indexName, 75 | KeyConditionExpression = $"{property} = :v", 76 | ExpressionAttributeValues = new Dictionary 77 | { 78 | { ":v", new AttributeValue { S = value } } 79 | } 80 | }; 81 | 82 | var queryResponse = await _dynamoDbClient.QueryAsync(queryRequest); 83 | if (queryResponse.Items.Count == 0) 84 | { 85 | return null; 86 | } 87 | 88 | // Assuming you have a method to convert a Dictionary to your entity type T 89 | return DynamoDbMapper.ConvertToEntity(queryResponse.Items[0]); 90 | } 91 | } 92 | 93 | 94 | public async Task ReadByCondition(Expression> condition) 95 | { 96 | var search = _context.ScanAsync(new List()); 97 | var allItems = await search.GetRemainingAsync(); 98 | return allItems.AsQueryable().Where(condition).FirstOrDefault(); 99 | } 100 | 101 | public Task ReadByAttributes(Dictionary attributes) 102 | { 103 | var request = new QueryRequest 104 | { 105 | TableName = "Assets", 106 | IndexName = "ChecksumIndex", // Specify the GSI name 107 | KeyConditionExpression = "Checksum = :v_Checksum", 108 | ExpressionAttributeValues = new Dictionary 109 | { 110 | // {":v_Checksum", new AttributeValue { S = checksum }} 111 | }, 112 | ProjectionExpression = "Id" // Retrieve only the 'Id' attribute 113 | }; 114 | 115 | var response = _context.QueryAsync(request); 116 | return Task.FromResult(response.GetRemainingAsync().Result.FirstOrDefault()); 117 | } 118 | 119 | public async Task> ReadAll() 120 | { 121 | var search = _context.ScanAsync(new List()); 122 | return await search.GetRemainingAsync(); 123 | } 124 | 125 | public async Task> ReadAll(string id, string uniqueKey = "Id") 126 | { 127 | // This method is not directly supported as DynamoDB does not support fetching all based on a non-primary key without a full scan 128 | return uniqueKey == "Id" ? new List { await _context.LoadAsync(id) } : await ReadAllByCondition(item => typeof(T).GetProperty(uniqueKey).GetValue(item, null).ToString() == id); 129 | } 130 | 131 | private async Task> ReadAllByCondition(Expression> condition) 132 | { 133 | var search = _context.ScanAsync(new List()); 134 | var allItems = await search.GetRemainingAsync(); 135 | return allItems.AsQueryable().Where(condition).ToList(); 136 | } 137 | 138 | public async Task Upsert(T entity, string uid, string column) 139 | { 140 | // DynamoDB SaveAsync acts as upsert 141 | await Create(entity); 142 | } 143 | 144 | public async Task Delete(string id, string uniqueKey = "Id") 145 | { 146 | if (uniqueKey == "Id") 147 | { 148 | await _context.DeleteAsync(id); 149 | } 150 | else 151 | { 152 | // Deleting by a condition other than the Id requires fetching the item first then deleting it by Id 153 | var itemToDelete = await ReadByCondition(item => typeof(T).GetProperty(uniqueKey).GetValue(item, null).ToString() == id); 154 | if (itemToDelete != null) 155 | { 156 | await _context.DeleteAsync(itemToDelete); 157 | } 158 | } 159 | } 160 | 161 | public Task> ReadAllByFilter(FilterDefinition filter) 162 | { 163 | throw new NotImplementedException(); 164 | } 165 | 166 | Task> IDatabase.ReadAllByCondition(Expression> condition) 167 | { 168 | throw new NotImplementedException(); 169 | } 170 | } -------------------------------------------------------------------------------- /Floom.Core/Controllers/AccountController.cs: -------------------------------------------------------------------------------- 1 | using Floom.Auth; 2 | using Microsoft.AspNetCore.Authentication; 3 | using Microsoft.AspNetCore.Authentication.Cookies; 4 | using Microsoft.AspNetCore.Authentication.Google; 5 | using Microsoft.AspNetCore.Mvc; 6 | using System.Security.Claims; 7 | using Newtonsoft.Json.Linq; 8 | using Microsoft.AspNetCore.Cors; 9 | using Floom.Utils; 10 | namespace Floom.Controllers; 11 | 12 | [ApiController] 13 | [Route("/v{version:apiVersion}/[controller]")] 14 | [ApiVersion("1.0")] 15 | // [EnableCors] 16 | public class AccountController : ControllerBase 17 | { 18 | private readonly IUsersService _service; 19 | 20 | public AccountController(IUsersService service) 21 | { 22 | _service = service; 23 | } 24 | 25 | [HttpPost("RegisterGuest")] 26 | public async Task Register() 27 | { 28 | var apiKey = await _service.RegisterGuestUserAsync(); 29 | return Ok(apiKey); 30 | } 31 | 32 | 33 | [HttpPost("google-login")] 34 | public async Task GoogleCallback([FromBody] GoogleAuthRequest request) 35 | { 36 | var clientId = Environment.GetEnvironmentVariable("FLOOM_GOOGLE_CLIENT_ID"); 37 | var clientSecret = Environment.GetEnvironmentVariable("FLOOM_GOOGLE_CLIENT_SECRET"); 38 | 39 | using (var httpClient = new HttpClient()) 40 | { 41 | Console.WriteLine("Starting token request to Google."); 42 | var tokenResponse = await httpClient.PostAsync("https://oauth2.googleapis.com/token", new FormUrlEncodedContent(new[] 43 | { 44 | new KeyValuePair("client_id", clientId), 45 | new KeyValuePair("client_secret", clientSecret), 46 | new KeyValuePair("code", request.Code), 47 | new KeyValuePair("redirect_uri", "https://console.floom.ai"), // Your redirect URI 48 | new KeyValuePair("grant_type", "authorization_code") 49 | })); 50 | 51 | var tokenResponseBody = await tokenResponse.Content.ReadAsStringAsync(); 52 | Console.WriteLine("Token response received: " + tokenResponseBody); 53 | tokenResponse.EnsureSuccessStatusCode(); 54 | 55 | var tokenData = JObject.Parse(tokenResponseBody); 56 | var accessToken = tokenData["access_token"].ToString(); 57 | Console.WriteLine("Access token: " + accessToken); 58 | 59 | httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken); 60 | httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("floom.ai"); 61 | 62 | Console.WriteLine("Starting user info request to Google."); 63 | var userResponse = await httpClient.GetAsync("https://www.googleapis.com/oauth2/v2/userinfo"); 64 | var userResponseBody = await userResponse.Content.ReadAsStringAsync(); 65 | Console.WriteLine("User info response received: " + userResponseBody); 66 | 67 | userResponse.EnsureSuccessStatusCode(); 68 | var user = JObject.Parse(userResponseBody); 69 | var email = user["email"].ToString(); 70 | var firstName = user["given_name"]?.ToString(); 71 | var lastName = user["family_name"]?.ToString(); 72 | Console.WriteLine($"User email: {email}, First Name: {firstName}, Last Name: {lastName}"); 73 | var response = await _service.RegisterOrLoginUserAsync("google", email, firstName, lastName); 74 | 75 | var sessionToken = response.ApiKey; 76 | Console.WriteLine("Session token: " + sessionToken); 77 | 78 | return Ok(new { sessionToken }); 79 | } 80 | 81 | } 82 | 83 | [HttpPost("github-login")] 84 | public async Task GitHubLogin([FromBody] GitHubAuthRequest request) 85 | { 86 | var clientId = Environment.GetEnvironmentVariable("FLOOM_GITHUB_CLIENT_ID"); 87 | var clientSecret = Environment.GetEnvironmentVariable("FLOOM_GITHUB_CLIENT_SECRET"); 88 | 89 | using (var httpClient = new HttpClient()) 90 | { 91 | // Exchange code for access token 92 | var tokenResponse = await httpClient.PostAsync("https://github.com/login/oauth/access_token", new FormUrlEncodedContent(new[] 93 | { 94 | new KeyValuePair("client_id", clientId), 95 | new KeyValuePair("client_secret", clientSecret), 96 | new KeyValuePair("code", request.Code), 97 | new KeyValuePair("state", request.State) 98 | })); 99 | 100 | tokenResponse.EnsureSuccessStatusCode(); 101 | var tokenResponseBody = await tokenResponse.Content.ReadAsStringAsync(); 102 | var queryParams = System.Web.HttpUtility.ParseQueryString(tokenResponseBody); 103 | var accessToken = queryParams["access_token"]; 104 | 105 | httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken); 106 | httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("floom.ai"); 107 | 108 | // Fetch user information 109 | var userResponse = await httpClient.GetAsync("https://api.github.com/user"); 110 | userResponse.EnsureSuccessStatusCode(); 111 | var userResponseBody = await userResponse.Content.ReadAsStringAsync(); 112 | var user = JObject.Parse(userResponseBody); 113 | 114 | var fullName = user["name"]?.ToString(); // GitHub's "name" field 115 | var names = fullName?.Split(' ', 2); // Attempt to split into first and last names 116 | var firstName = names?.Length > 0 ? names[0] : null; 117 | var lastName = names?.Length > 1 ? names[1] : null; 118 | 119 | // Fetch user emails 120 | var emailResponse = await httpClient.GetAsync("https://api.github.com/user/emails"); 121 | emailResponse.EnsureSuccessStatusCode(); 122 | var emailResponseBody = await emailResponse.Content.ReadAsStringAsync(); 123 | var emails = JArray.Parse(emailResponseBody); 124 | 125 | // Extract the primary email address 126 | var primaryEmail = emails.FirstOrDefault(e => (bool)e["primary"])?["email"]?.ToString(); 127 | 128 | if (string.IsNullOrEmpty(primaryEmail)) 129 | { 130 | return BadRequest(new { message = "No primary email found." }); 131 | } 132 | 133 | // Save user data to your database 134 | var response = await _service.RegisterOrLoginUserAsync("github", primaryEmail, firstName, lastName); 135 | 136 | var sessionToken = response.ApiKey; 137 | 138 | return Ok(new { sessionToken }); 139 | } 140 | } 141 | 142 | [HttpGet("logout")] 143 | [ApiKeyAuthorization] 144 | public async Task Logout() 145 | { 146 | var apiKey = HttpContextHelper.GetApiKeyFromHttpContext(); 147 | Console.WriteLine($"Logging out user with API key: {apiKey}"); 148 | await _service.LogoutUserByApiKeyAsync(apiKey); 149 | return Ok("Logged out"); 150 | } 151 | } 152 | 153 | public class GitHubAuthRequest 154 | { 155 | public string Code { get; set; } 156 | public string State { get; set; } 157 | } 158 | 159 | public class GoogleAuthRequest 160 | { 161 | public string Code { get; set; } 162 | public string State { get; set; } 163 | } -------------------------------------------------------------------------------- /Floom.Core/Program.cs: -------------------------------------------------------------------------------- 1 | using Floom.Assets; 2 | using Floom.Audit; 3 | using Floom.Auth; 4 | using Floom.Config; 5 | using Floom.Functions; 6 | using Floom.Logs; 7 | using Floom.Repository; 8 | using Floom.Repository.DynamoDB; 9 | using Floom.Repository.MongoDb; 10 | using Microsoft.AspNetCore.Mvc; 11 | using Microsoft.AspNetCore.Mvc.Versioning; 12 | using MongoDB.Driver; 13 | using Microsoft.AspNetCore.Authentication.Cookies; 14 | using Floom.Server; 15 | using Microsoft.AspNetCore.HttpOverrides; 16 | using MongoDB.Bson.Serialization.Conventions; 17 | 18 | var allowedOrigins = new[] { "https://console.floom.ai", "https://www.floom.ai", "https://floom.ai" }; 19 | // var allowedOrigins = new[] { "http://localhost:3000" }; 20 | 21 | var builder = WebApplication.CreateBuilder(args); 22 | 23 | // Configure forwarded headers options 24 | builder.Services.Configure(options => 25 | { 26 | options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; 27 | 28 | // Only loopback proxies are allowed by default. 29 | // Clear that restriction because forwarders are enabled by explicit configuration. 30 | options.KnownNetworks.Clear(); 31 | options.KnownProxies.Clear(); 32 | }); 33 | 34 | builder.Services.AddRouting(options => { options.LowercaseUrls = true; }); 35 | 36 | // Register convention pack to ignore extra elements 37 | var conventionPack = new ConventionPack { new IgnoreExtraElementsConvention(true) }; 38 | ConventionRegistry.Register("IgnoreExtraElements", conventionPack, type => true); 39 | 40 | // #region Versioning 41 | builder.Services.AddApiVersioning(o => 42 | { 43 | o.AssumeDefaultVersionWhenUnspecified = true; 44 | o.DefaultApiVersion = new ApiVersion(1, 0); 45 | o.ReportApiVersions = true; 46 | o.ApiVersionReader = ApiVersionReader.Combine( 47 | new UrlSegmentApiVersionReader(), 48 | new QueryStringApiVersionReader("api-version"), 49 | new HeaderApiVersionReader("X-Version"), 50 | new MediaTypeApiVersionReader("ver")); 51 | }); 52 | 53 | builder.Services.AddHttpContextAccessor(); 54 | // #endregion 55 | 56 | builder.Services.AddHttpClient(); 57 | builder.Services.AddSingleton(MongoConfiguration.CreateMongoClient()); 58 | 59 | // Adding services to DI container 60 | builder.Services.AddScoped(); 61 | builder.Services.AddTransient(provider => new Lazy(provider.GetRequiredService)); 62 | builder.Services.AddScoped(); 63 | builder.Services.AddSingleton(); 64 | builder.Services.AddSingleton(); 65 | builder.Services.AddTransient(); 66 | 67 | builder.Services.AddControllers(); 68 | builder.Services.AddEndpointsApiExplorer(); 69 | builder.Services.AddSwaggerGen(); 70 | 71 | builder.Services.AddHttpsRedirection(options => 72 | { 73 | options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect; 74 | options.HttpsPort = 443; 75 | }); 76 | 77 | // add localhost:3000 to the list of allowed origins, also add www.floom.ai and floom.ai 78 | 79 | // builder.Services.AddCors(options => 80 | // { 81 | // options.AddDefaultPolicy(builder => 82 | // { 83 | // builder.WithOrigins(allowedOrigins) 84 | // .AllowAnyHeader() 85 | // .AllowAnyMethod() 86 | // .AllowCredentials(); 87 | // }); 88 | // }); 89 | 90 | // Determine the current environment 91 | var environment = builder.Environment; 92 | Console.WriteLine($"Floom Is Running On Environment: {environment.EnvironmentName}"); 93 | 94 | // Configure Google authentication 95 | builder.Services.AddAuthentication(options => 96 | { 97 | options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; 98 | options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; 99 | options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; 100 | }) 101 | .AddCookie(options => 102 | { 103 | options.Cookie.SameSite = SameSiteMode.None; 104 | if (environment.IsProduction()) 105 | { 106 | options.Cookie.SecurePolicy = CookieSecurePolicy.Always; 107 | }} 108 | ); 109 | 110 | builder.Services.AddAuthorization(); 111 | 112 | var app = builder.Build(); 113 | 114 | app.UseForwardedHeaders(); 115 | //app.UseCors(); // Add CORS middleware before authentication and authorization 116 | 117 | app.Use(async (context, next) => 118 | { 119 | Console.WriteLine("Handling request: " + context.Request.Method + " " + context.Request.Path); 120 | 121 | // Handle preflight requests 122 | // if (context.Request.Method == "OPTIONS") 123 | // { 124 | // Console.WriteLine("Handling preflight request (OPTIONS)"); 125 | // var origin = context.Request.Headers["Origin"].ToString(); 126 | // if (allowedOrigins.Contains(origin)) 127 | // { 128 | // context.Response.Headers["Access-Control-Allow-Origin"] = origin; 129 | // context.Response.Headers["Access-Control-Allow-Credentials"] = "true"; 130 | // context.Response.Headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"; 131 | // context.Response.Headers["Access-Control-Allow-Headers"] = "Content-Type, Api-Key"; 132 | // } 133 | // 134 | // Console.WriteLine("Returning 200 for preflight request"); 135 | // 136 | // context.Response.StatusCode = StatusCodes.Status200OK; 137 | // return; 138 | // } 139 | 140 | // if (!context.Response.HasStarted) 141 | // { 142 | // context.Response.OnStarting(() => 143 | // { 144 | // Console.WriteLine("Planning to adding CORS headers to response"); 145 | // 146 | // var origin = context.Request.Headers["Origin"].ToString(); 147 | // 148 | // if (allowedOrigins.Contains(origin)) 149 | // { 150 | // Console.WriteLine("Adding CORS headers to response for origin: " + origin); 151 | // context.Response.Headers["Access-Control-Allow-Origin"] = origin; 152 | // context.Response.Headers["Access-Control-Allow-Credentials"] = "true"; 153 | // } 154 | // 155 | // return Task.CompletedTask; 156 | // }); 157 | // } 158 | 159 | await next(); 160 | Console.WriteLine("Finished handling request"); 161 | }); 162 | 163 | if (app.Environment.IsDevelopment()) 164 | { 165 | app.UseSwagger(); 166 | app.UseSwaggerUI(); 167 | } 168 | 169 | //app.UseHttpsRedirection(); 170 | app.UseAuthentication(); // Add authentication middleware 171 | app.UseAuthorization(); 172 | 173 | app.MapControllers(); 174 | app.UseMiddleware(); 175 | 176 | var loggerFactory = LoggerFactory.Create(builder => 177 | { 178 | builder.AddConsole(); 179 | }); 180 | 181 | FloomLoggerFactory.Configure(loggerFactory); 182 | 183 | app.Lifetime.ApplicationStarted.Register(FloomInitCallback); 184 | Console.WriteLine("Starting app"); 185 | app.Run("http://*:4050"); // port inside docker 186 | 187 | async void FloomInitCallback() 188 | { 189 | var repositoryFactory = app.Services.GetRequiredService(); 190 | 191 | // Generate Database 192 | var databaseType = Environment.GetEnvironmentVariable("FLOOM_DATABASE_TYPE"); 193 | if (databaseType is "mongodb") 194 | { 195 | var client = app.Services.GetRequiredService(); 196 | var dbInitializer = new MongoDbInitializer(client); 197 | await dbInitializer.Initialize("Floom"); 198 | } 199 | else if (databaseType is "dynamodb") 200 | { 201 | var dynamoDbClient = DynamoDbConfiguration.CreateCloudDynamoDbClient(); 202 | var dbInitializer = new DynamoDbInitializer(dynamoDbClient); 203 | await dbInitializer.Initialize(); 204 | } 205 | 206 | // Generate Initial Api Key 207 | // var apiKeyInitializer = new ApiKeyInitializer(repositoryFactory); 208 | // apiKeyInitializer.Initialize(); 209 | 210 | // Generate Floom Assets Repository 211 | FloomAssetsRepository.Instance.Initialize(repositoryFactory); 212 | 213 | // Generate Floom Audit Service 214 | FloomAuditService.Instance.Initialize(repositoryFactory); 215 | } 216 | -------------------------------------------------------------------------------- /Floom.Core/Assets/FloomAssetsRepository.cs: -------------------------------------------------------------------------------- 1 | using Amazon.S3; 2 | using Amazon.S3.Model; 3 | using Amazon.S3.Transfer; 4 | using Floom.Base; 5 | using Floom.Data; 6 | using Floom.Logs; 7 | using Floom.Repository; 8 | using Floom.Utils; 9 | using MongoDB.Bson; 10 | 11 | namespace Floom.Assets; 12 | 13 | public class FloomAssetsRepository : FloomSingletonBase 14 | { 15 | private readonly ILogger _logger; 16 | private IRepository _repository; 17 | private const string SubDirectory = "floom_user_files"; 18 | private readonly string _filesDirectory = Path.Combine(Directory.GetParent(Directory.GetCurrentDirectory()).FullName, SubDirectory); 19 | private static readonly object LockObject = new(); 20 | private static bool _isInitialized; 21 | 22 | // Private constructor to prevent instance creation outside the class. 23 | public FloomAssetsRepository() 24 | { 25 | _logger = FloomLoggerFactory.CreateLogger(GetType()); 26 | } 27 | 28 | public override void Initialize(IRepositoryFactory repositoryFactory) 29 | { 30 | lock (LockObject) 31 | { 32 | if (_isInitialized) 33 | { 34 | throw new InvalidOperationException("FloomAssetsRepository is already Initialized."); 35 | } 36 | 37 | _repository = repositoryFactory.Create(); 38 | _isInitialized = true; 39 | } 40 | } 41 | 42 | public async Task CreateAsset(IFormFile file) 43 | { 44 | var floomEnvironment = Environment.GetEnvironmentVariable("FLOOM_ENVIRONMENT"); 45 | if (floomEnvironment == "local") 46 | { 47 | return await CreateAssetLocally(file); 48 | } 49 | else if (floomEnvironment == "cloud") 50 | { 51 | return await CreateAssetInAwsBucket(file); 52 | } 53 | else 54 | { 55 | _logger.LogError("Invalid FLOOM_ENVIRONMENT value."); 56 | return null; 57 | } 58 | } 59 | 60 | public async Task CreateAssetLocally(IFormFile file) 61 | { 62 | try 63 | { 64 | var checksum = await FileUtils.CalculateChecksumAsync(file); 65 | 66 | var existingAsset = await _repository.FindByCondition(a => a.originalName == file.FileName && a.checksum == checksum); 67 | 68 | if (existingAsset != null) 69 | { 70 | _logger.LogInformation($"File already exists: {existingAsset.Id}"); 71 | return existingAsset.Id; // Return existing asset ID 72 | } 73 | 74 | var assetId = ObjectId.GenerateNewId().ToString(); 75 | var fileExtension = Path.GetExtension(file.FileName); 76 | 77 | if (!Directory.Exists(_filesDirectory)) 78 | { 79 | Directory.CreateDirectory(_filesDirectory); 80 | } 81 | 82 | var storedFile = $"{assetId}{fileExtension}"; 83 | var filePath = Path.Combine(_filesDirectory, storedFile); 84 | 85 | using (var stream = new FileStream(filePath, FileMode.Create)) 86 | { 87 | await file.CopyToAsync(stream); 88 | } 89 | 90 | var fileDocument = new AssetEntity 91 | { 92 | Id = assetId, 93 | originalName = file.FileName, 94 | storedName = storedFile, 95 | storedPath = filePath, 96 | extension = fileExtension, 97 | size = file.Length, 98 | checksum = checksum 99 | }; 100 | 101 | await _repository.Insert(fileDocument); 102 | 103 | return assetId; 104 | } 105 | catch (Exception ex) 106 | { 107 | _logger.LogError(ex, "Error occurred while creating file."); 108 | return null; 109 | } 110 | } 111 | 112 | public async Task CreateAssetInAwsBucket(IFormFile file) 113 | { 114 | try 115 | { 116 | var checksum = await FileUtils.CalculateChecksumAsync(file); 117 | 118 | var existingAsset = await _repository.FindByCondition(a => a.originalName == file.FileName && a.checksum == checksum); 119 | 120 | if (existingAsset != null) 121 | { 122 | _logger.LogInformation($"File already exists: {existingAsset.Id}"); 123 | return existingAsset.Id; // Return existing asset ID 124 | } 125 | 126 | var assetId = ObjectId.GenerateNewId().ToString(); 127 | var fileExtension = Path.GetExtension(file.FileName); 128 | var storedFile = $"{assetId}{fileExtension}"; 129 | 130 | // Specify your bucket name 131 | var bucketName = Environment.GetEnvironmentVariable("FLOOM_S3_BUCKET") ?? "empty_bucket"; 132 | 133 | // Create a client 134 | using (var client = new AmazonS3Client()) 135 | { 136 | using (var newMemoryStream = new MemoryStream()) 137 | { 138 | await file.CopyToAsync(newMemoryStream); 139 | 140 | var uploadRequest = new TransferUtilityUploadRequest 141 | { 142 | InputStream = newMemoryStream, 143 | Key = storedFile, 144 | BucketName = bucketName, 145 | CannedACL = S3CannedACL.Private 146 | }; 147 | 148 | var transferUtility = new TransferUtility(client); 149 | await transferUtility.UploadAsync(uploadRequest); 150 | } 151 | } 152 | 153 | var fileDocument = new AssetEntity 154 | { 155 | Id = assetId, 156 | originalName = file.FileName, 157 | storedName = storedFile, 158 | storedPath = $"s3://{bucketName}/{storedFile}", 159 | extension = fileExtension, 160 | size = file.Length, 161 | checksum = checksum 162 | }; 163 | 164 | await _repository.Insert(fileDocument); 165 | 166 | return assetId; 167 | } 168 | catch (Exception ex) 169 | { 170 | _logger.LogError(ex, "Error occurred while creating file."); 171 | return null; 172 | } 173 | } 174 | 175 | 176 | public async Task UploadPythonFileToAwsBucket(IFormFile file) 177 | { 178 | try 179 | { 180 | var checksum = await FileUtils.CalculateChecksumAsync(file); 181 | 182 | var assetId = ObjectId.GenerateNewId().ToString(); 183 | var fileExtension = Path.GetExtension(file.FileName); 184 | var storedFile = $"{assetId}{fileExtension}"; 185 | 186 | // Specify your bucket name 187 | var bucketName = Environment.GetEnvironmentVariable("FLOOM_S3_FUNCTIONS_BUCKET") ?? "empty_bucket"; 188 | 189 | // Create a client 190 | using (var client = new AmazonS3Client()) 191 | { 192 | using (var newMemoryStream = new MemoryStream()) 193 | { 194 | await file.CopyToAsync(newMemoryStream); 195 | 196 | var uploadRequest = new TransferUtilityUploadRequest 197 | { 198 | InputStream = newMemoryStream, 199 | Key = storedFile, 200 | BucketName = bucketName, 201 | CannedACL = S3CannedACL.Private 202 | }; 203 | 204 | var transferUtility = new TransferUtility(client); 205 | await transferUtility.UploadAsync(uploadRequest); 206 | } 207 | } 208 | 209 | // Get file URL from S3 210 | var fileUrl = $"https://{bucketName}.s3.amazonaws.com/{storedFile}"; 211 | 212 | return fileUrl; 213 | } 214 | catch (Exception ex) 215 | { 216 | _logger.LogError(ex, "Error occurred while creating file."); 217 | return null; 218 | } 219 | } 220 | 221 | public async Task DownloadFileFromS3Async(string fileUrl) 222 | { 223 | var uri = new Uri(fileUrl); 224 | var bucketName = uri.Host.Split('.')[0]; 225 | var key = uri.AbsolutePath.Substring(1); // Remove leading '/' 226 | 227 | using (var client = new AmazonS3Client()) 228 | { 229 | var request = new GetObjectRequest 230 | { 231 | BucketName = bucketName, 232 | Key = key 233 | }; 234 | 235 | var response = await client.GetObjectAsync(request); 236 | var memoryStream = new MemoryStream(); 237 | await response.ResponseStream.CopyToAsync(memoryStream); 238 | memoryStream.Position = 0; // Reset stream position for reading 239 | return memoryStream; 240 | } 241 | } 242 | 243 | 244 | public async Task GetAssetById(string assetId) 245 | { 246 | var assetEntity = await _repository.Get(assetId, "_id"); 247 | return FloomAsset.FromEntity(assetEntity); 248 | } 249 | } -------------------------------------------------------------------------------- /Floom.Core/Tests/Postman/FirePrompt.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "5c232553-0afa-4956-b87f-79139fc66509", 4 | "name": "FirePrompt", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 6 | "_exporter_id": "667159" 7 | }, 8 | "item": [ 9 | { 10 | "name": "Models", 11 | "item": [ 12 | { 13 | "name": "Apply (docs)", 14 | "protocolProfileBehavior": { 15 | "disabledSystemHeaders": { 16 | "content-type": true 17 | } 18 | }, 19 | "request": { 20 | "method": "POST", 21 | "header": [ 22 | { 23 | "key": "Api-Key", 24 | "value": "{{Api-Key}}", 25 | "type": "text" 26 | }, 27 | { 28 | "key": "Content-Type", 29 | "value": "text/yaml", 30 | "type": "text" 31 | } 32 | ], 33 | "body": { 34 | "mode": "raw", 35 | "raw": "schema: v1\r\nkind: Model\r\nid: docs-model\r\nvendor: OpenAI\r\nmodel: text-davinci-003\r\napiKey: TEST", 36 | "options": { 37 | "raw": { 38 | "language": "text" 39 | } 40 | } 41 | }, 42 | "url": { 43 | "raw": "{{Api-Url}}/v1/Models/Apply", 44 | "host": [ 45 | "{{Api-Url}}" 46 | ], 47 | "path": [ 48 | "v1", 49 | "Models", 50 | "Apply" 51 | ] 52 | } 53 | }, 54 | "response": [] 55 | }, 56 | { 57 | "name": "Apply (image)", 58 | "protocolProfileBehavior": { 59 | "disabledSystemHeaders": { 60 | "content-type": true 61 | } 62 | }, 63 | "request": { 64 | "method": "POST", 65 | "header": [ 66 | { 67 | "key": "Api-Key", 68 | "value": "{{Api-Key}}", 69 | "type": "text" 70 | }, 71 | { 72 | "key": "Content-Type", 73 | "value": "text/yaml", 74 | "type": "text" 75 | } 76 | ], 77 | "body": { 78 | "mode": "raw", 79 | "raw": "schema: v1\r\nkind: Model\r\nid: create-image-model\r\nvendor: OpenAI\r\nmodel: dall-e\r\napiKey: TEST", 80 | "options": { 81 | "raw": { 82 | "language": "text" 83 | } 84 | } 85 | }, 86 | "url": { 87 | "raw": "{{Api-Url}}/v1/Models/Apply", 88 | "host": [ 89 | "{{Api-Url}}" 90 | ], 91 | "path": [ 92 | "v1", 93 | "Models", 94 | "Apply" 95 | ] 96 | } 97 | }, 98 | "response": [] 99 | }, 100 | { 101 | "name": "Get", 102 | "protocolProfileBehavior": { 103 | "disabledSystemHeaders": { 104 | "content-type": true 105 | } 106 | }, 107 | "request": { 108 | "method": "POST", 109 | "header": [ 110 | { 111 | "key": "Api-Key", 112 | "value": "{{Api-Key}}", 113 | "type": "text" 114 | }, 115 | { 116 | "key": "Content-Type", 117 | "value": "text/yaml", 118 | "type": "text" 119 | } 120 | ], 121 | "body": { 122 | "mode": "raw", 123 | "raw": "name: new-ds1", 124 | "options": { 125 | "raw": { 126 | "language": "text" 127 | } 128 | } 129 | }, 130 | "url": { 131 | "raw": "{{Api-Url}}/v1/Data", 132 | "host": [ 133 | "{{Api-Url}}" 134 | ], 135 | "path": [ 136 | "v1", 137 | "Data" 138 | ] 139 | } 140 | }, 141 | "response": [] 142 | } 143 | ] 144 | }, 145 | { 146 | "name": "Pipelines", 147 | "item": [ 148 | { 149 | "name": "Apply (docs)", 150 | "protocolProfileBehavior": { 151 | "disabledSystemHeaders": { 152 | "content-type": true 153 | } 154 | }, 155 | "request": { 156 | "method": "POST", 157 | "header": [ 158 | { 159 | "key": "Api-Key", 160 | "value": "{{Api-Key}}", 161 | "type": "text" 162 | }, 163 | { 164 | "key": "Content-Type", 165 | "value": "text/yaml", 166 | "type": "text" 167 | } 168 | ], 169 | "body": { 170 | "mode": "raw", 171 | "raw": "schema: v1\r\nkind: Pipeline\r\nid: docs-pipeline\r\nmodel: docs-model\r\nprompt: docs-prompt\r\nresponse: docs-response\r\nchatHistory: true\r\ndata:\r\n- docs-data", 172 | "options": { 173 | "raw": { 174 | "language": "text" 175 | } 176 | } 177 | }, 178 | "url": { 179 | "raw": "{{Api-Url}}/v1/Pipelines/Apply", 180 | "host": [ 181 | "{{Api-Url}}" 182 | ], 183 | "path": [ 184 | "v1", 185 | "Pipelines", 186 | "Apply" 187 | ] 188 | } 189 | }, 190 | "response": [] 191 | }, 192 | { 193 | "name": "Apply (image)", 194 | "protocolProfileBehavior": { 195 | "disabledSystemHeaders": { 196 | "content-type": true 197 | } 198 | }, 199 | "request": { 200 | "method": "POST", 201 | "header": [ 202 | { 203 | "key": "Api-Key", 204 | "value": "{{Api-Key}}", 205 | "type": "text" 206 | }, 207 | { 208 | "key": "Content-Type", 209 | "value": "text/yaml", 210 | "type": "text" 211 | } 212 | ], 213 | "body": { 214 | "mode": "raw", 215 | "raw": "schema: v1\r\nkind: Pipeline\r\nid: create-image-pipeline\r\nmodel: create-image-model\r\nprompt: create-image-prompt\r\nresponse: create-image-response", 216 | "options": { 217 | "raw": { 218 | "language": "text" 219 | } 220 | } 221 | }, 222 | "url": { 223 | "raw": "{{Api-Url}}/v1/Pipelines", 224 | "host": [ 225 | "{{Api-Url}}" 226 | ], 227 | "path": [ 228 | "v1", 229 | "Pipelines" 230 | ] 231 | } 232 | }, 233 | "response": [] 234 | }, 235 | { 236 | "name": "Run", 237 | "protocolProfileBehavior": { 238 | "disabledSystemHeaders": { 239 | "content-type": true 240 | } 241 | }, 242 | "request": { 243 | "method": "POST", 244 | "header": [ 245 | { 246 | "key": "Api-Key", 247 | "value": "{{Api-Key}}", 248 | "type": "text" 249 | }, 250 | { 251 | "key": "Content-Type", 252 | "value": "application/json", 253 | "type": "text" 254 | } 255 | ], 256 | "body": { 257 | "mode": "raw", 258 | "raw": "{\r\n \"pipelineId\": \"docs-pipeline\",\r\n \"input\": \"Who's first US president?\",\r\n \"variables\":\r\n {\r\n \"first_name\": \"max\"\r\n }\r\n}", 259 | "options": { 260 | "raw": { 261 | "language": "json" 262 | } 263 | } 264 | }, 265 | "url": { 266 | "raw": "{{Api-Url}}/v1/Pipelines/Run", 267 | "host": [ 268 | "{{Api-Url}}" 269 | ], 270 | "path": [ 271 | "v1", 272 | "Pipelines", 273 | "Run" 274 | ] 275 | } 276 | }, 277 | "response": [] 278 | }, 279 | { 280 | "name": "Get", 281 | "protocolProfileBehavior": { 282 | "disabledSystemHeaders": { 283 | "content-type": true 284 | } 285 | }, 286 | "request": { 287 | "method": "POST", 288 | "header": [ 289 | { 290 | "key": "Api-Key", 291 | "value": "{{Api-Key}}", 292 | "type": "text" 293 | }, 294 | { 295 | "key": "Content-Type", 296 | "value": "text/yaml", 297 | "type": "text" 298 | } 299 | ], 300 | "body": { 301 | "mode": "raw", 302 | "raw": "name: new-ds1", 303 | "options": { 304 | "raw": { 305 | "language": "text" 306 | } 307 | } 308 | }, 309 | "url": { 310 | "raw": "{{Api-Url}}/v1/Data", 311 | "host": [ 312 | "{{Api-Url}}" 313 | ], 314 | "path": [ 315 | "v1", 316 | "Data" 317 | ] 318 | } 319 | }, 320 | "response": [] 321 | } 322 | ] 323 | }, 324 | { 325 | "name": "Files", 326 | "item": [ 327 | { 328 | "name": "Create (docs)", 329 | "protocolProfileBehavior": { 330 | "disabledSystemHeaders": {} 331 | }, 332 | "request": { 333 | "method": "POST", 334 | "header": [ 335 | { 336 | "key": "Api-Key", 337 | "value": "{{Api-Key}}", 338 | "type": "text" 339 | }, 340 | { 341 | "key": "Content-Type", 342 | "value": "text/yaml", 343 | "type": "text", 344 | "disabled": true 345 | } 346 | ], 347 | "body": { 348 | "mode": "formdata", 349 | "formdata": [ 350 | { 351 | "key": "file", 352 | "type": "file", 353 | "src": "/C:/Users/Unknown/Downloads/2013_1s/2013_1s.pdf" 354 | } 355 | ] 356 | }, 357 | "url": { 358 | "raw": "{{Api-Url}}/v1/Files", 359 | "host": [ 360 | "{{Api-Url}}" 361 | ], 362 | "path": [ 363 | "v1", 364 | "Files" 365 | ] 366 | } 367 | }, 368 | "response": [] 369 | } 370 | ] 371 | }, 372 | { 373 | "name": "Prompts", 374 | "item": [ 375 | { 376 | "name": "Apply (docs)", 377 | "protocolProfileBehavior": { 378 | "disabledSystemHeaders": { 379 | "content-type": true 380 | } 381 | }, 382 | "request": { 383 | "method": "POST", 384 | "header": [ 385 | { 386 | "key": "Api-Key", 387 | "value": "{{Api-Key}}", 388 | "type": "text" 389 | }, 390 | { 391 | "key": "Content-Type", 392 | "value": "text/yaml", 393 | "type": "text" 394 | } 395 | ], 396 | "body": { 397 | "mode": "raw", 398 | "raw": "schema: v1\r\nkind: Prompt\r\nid: docs-prompt\r\ntype: text\r\nsystem: \"You are a helpful assitant of BMW, a washing machine maker. Use the user's first name which is {{first_name}}.\"", 399 | "options": { 400 | "raw": { 401 | "language": "text" 402 | } 403 | } 404 | }, 405 | "url": { 406 | "raw": "{{Api-Url}}/v1/Prompts/Apply", 407 | "host": [ 408 | "{{Api-Url}}" 409 | ], 410 | "path": [ 411 | "v1", 412 | "Prompts", 413 | "Apply" 414 | ] 415 | } 416 | }, 417 | "response": [] 418 | }, 419 | { 420 | "name": "Apply (image)", 421 | "protocolProfileBehavior": { 422 | "disabledSystemHeaders": { 423 | "content-type": true 424 | } 425 | }, 426 | "request": { 427 | "method": "POST", 428 | "header": [ 429 | { 430 | "key": "Api-Key", 431 | "value": "{{Api-Key}}", 432 | "type": "text" 433 | }, 434 | { 435 | "key": "Content-Type", 436 | "value": "text/yaml", 437 | "type": "text" 438 | } 439 | ], 440 | "body": { 441 | "mode": "raw", 442 | "raw": "schema: v1\r\nkind: Prompt\r\nid: create-image-prompt\r\ntype: text\r\nuser: \"Create a beatiful {{item}}\"", 443 | "options": { 444 | "raw": { 445 | "language": "text" 446 | } 447 | } 448 | }, 449 | "url": { 450 | "raw": "{{Api-Url}}/v1/Prompts/Apply", 451 | "host": [ 452 | "{{Api-Url}}" 453 | ], 454 | "path": [ 455 | "v1", 456 | "Prompts", 457 | "Apply" 458 | ] 459 | } 460 | }, 461 | "response": [] 462 | } 463 | ] 464 | }, 465 | { 466 | "name": "Responses", 467 | "item": [ 468 | { 469 | "name": "Apply (docs)", 470 | "protocolProfileBehavior": { 471 | "disabledSystemHeaders": { 472 | "content-type": true 473 | } 474 | }, 475 | "request": { 476 | "method": "POST", 477 | "header": [ 478 | { 479 | "key": "Api-Key", 480 | "value": "{{Api-Key}}", 481 | "type": "text" 482 | }, 483 | { 484 | "key": "Content-Type", 485 | "value": "text/yaml", 486 | "type": "text" 487 | } 488 | ], 489 | "body": { 490 | "mode": "raw", 491 | "raw": "schema: v1\r\nkind: Response\r\nid: docs-response\r\ntype: text \r\nlanguage: English\r\nmaxSentences: 3\r\nmaxCharacters: 1500\r\ntemperature: 0.9\r\nexamples:\r\n - \"Turn your ignition key and press the main button.\" \r\n - \"Turn on the radio using the red button.\" \r\n - \"Open the engine bay, fill 5 Liters of oil.\" ", 492 | "options": { 493 | "raw": { 494 | "language": "text" 495 | } 496 | } 497 | }, 498 | "url": { 499 | "raw": "{{Api-Url}}/v1/Responses/Apply", 500 | "host": [ 501 | "{{Api-Url}}" 502 | ], 503 | "path": [ 504 | "v1", 505 | "Responses", 506 | "Apply" 507 | ] 508 | } 509 | }, 510 | "response": [] 511 | }, 512 | { 513 | "name": "Apply (image)", 514 | "protocolProfileBehavior": { 515 | "disabledSystemHeaders": { 516 | "content-type": true 517 | } 518 | }, 519 | "request": { 520 | "method": "POST", 521 | "header": [ 522 | { 523 | "key": "Api-Key", 524 | "value": "{{Api-Key}}", 525 | "type": "text" 526 | }, 527 | { 528 | "key": "Content-Type", 529 | "value": "text/yaml", 530 | "type": "text" 531 | } 532 | ], 533 | "body": { 534 | "mode": "raw", 535 | "raw": "schema: v1\r\nkind: Response\r\nid: create-image-response\r\ntype: image\r\nresolution: 512x512\r\noptions: 2\r\nformat: jpg\r\nquality: 0.7", 536 | "options": { 537 | "raw": { 538 | "language": "text" 539 | } 540 | } 541 | }, 542 | "url": { 543 | "raw": "{{Api-Url}}/v1/Responses/Apply", 544 | "host": [ 545 | "{{Api-Url}}" 546 | ], 547 | "path": [ 548 | "v1", 549 | "Responses", 550 | "Apply" 551 | ] 552 | } 553 | }, 554 | "response": [] 555 | } 556 | ] 557 | }, 558 | { 559 | "name": "Embeddings", 560 | "item": [ 561 | { 562 | "name": "Apply (docs)", 563 | "protocolProfileBehavior": { 564 | "disabledSystemHeaders": { 565 | "content-type": true 566 | } 567 | }, 568 | "request": { 569 | "method": "POST", 570 | "header": [ 571 | { 572 | "key": "Api-Key", 573 | "value": "{{Api-Key}}", 574 | "type": "text" 575 | }, 576 | { 577 | "key": "Content-Type", 578 | "value": "text/yaml", 579 | "type": "text" 580 | } 581 | ], 582 | "body": { 583 | "mode": "raw", 584 | "raw": "schema: v1\r\nkind: Embeddings\r\nid: docs-embeddings\r\ntype: text \r\nvendor: OpenAI\r\nmodel: text-embedding-ada-002\r\napiKey: TEST-", 585 | "options": { 586 | "raw": { 587 | "language": "text" 588 | } 589 | } 590 | }, 591 | "url": { 592 | "raw": "{{Api-Url}}/v1/Embeddings/Apply", 593 | "host": [ 594 | "{{Api-Url}}" 595 | ], 596 | "path": [ 597 | "v1", 598 | "Embeddings", 599 | "Apply" 600 | ] 601 | } 602 | }, 603 | "response": [] 604 | } 605 | ] 606 | }, 607 | { 608 | "name": "VectorStores", 609 | "item": [ 610 | { 611 | "name": "Apply (docs, pinecone)", 612 | "protocolProfileBehavior": { 613 | "disabledSystemHeaders": { 614 | "content-type": true 615 | } 616 | }, 617 | "request": { 618 | "method": "POST", 619 | "header": [ 620 | { 621 | "key": "Api-Key", 622 | "value": "{{Api-Key}}", 623 | "type": "text" 624 | }, 625 | { 626 | "key": "Content-Type", 627 | "value": "text/yaml", 628 | "type": "text" 629 | } 630 | ], 631 | "body": { 632 | "mode": "raw", 633 | "raw": "schema: v1\r\nkind: VectorStore\r\nid: docs-vectorstore\r\nvendor: Pinecone\r\napiKey: 0ea0f2ed-54bb-4d3e-878e-248af609a81b\r\nenvironment: us-west4-gcp-free", 634 | "options": { 635 | "raw": { 636 | "language": "text" 637 | } 638 | } 639 | }, 640 | "url": { 641 | "raw": "{{Api-Url}}/v1/VectorStores/Apply", 642 | "host": [ 643 | "{{Api-Url}}" 644 | ], 645 | "path": [ 646 | "v1", 647 | "VectorStores", 648 | "Apply" 649 | ] 650 | } 651 | }, 652 | "response": [] 653 | }, 654 | { 655 | "name": "Apply (docs, Milvus)", 656 | "protocolProfileBehavior": { 657 | "disabledSystemHeaders": { 658 | "content-type": true 659 | } 660 | }, 661 | "request": { 662 | "method": "POST", 663 | "header": [ 664 | { 665 | "key": "Api-Key", 666 | "value": "{{Api-Key}}", 667 | "type": "text" 668 | }, 669 | { 670 | "key": "Content-Type", 671 | "value": "text/yaml", 672 | "type": "text" 673 | } 674 | ], 675 | "body": { 676 | "mode": "raw", 677 | "raw": "schema: v1\r\nkind: VectorStore\r\nid: docs-vectorstore\r\nvendor: Milvus\r\nendpoint: 192.168.1.14\r\nport: 19530\r\nusername: root\r\npassword: Milvus", 678 | "options": { 679 | "raw": { 680 | "language": "text" 681 | } 682 | } 683 | }, 684 | "url": { 685 | "raw": "{{Api-Url}}/v1/VectorStores/Apply", 686 | "host": [ 687 | "{{Api-Url}}" 688 | ], 689 | "path": [ 690 | "v1", 691 | "VectorStores", 692 | "Apply" 693 | ] 694 | } 695 | }, 696 | "response": [] 697 | } 698 | ] 699 | }, 700 | { 701 | "name": "Data", 702 | "item": [ 703 | { 704 | "name": "Apply (docs)", 705 | "protocolProfileBehavior": { 706 | "disabledSystemHeaders": { 707 | "content-type": true 708 | } 709 | }, 710 | "request": { 711 | "method": "POST", 712 | "header": [ 713 | { 714 | "key": "Api-Key", 715 | "value": "{{Api-Key}}", 716 | "type": "text" 717 | }, 718 | { 719 | "key": "Content-Type", 720 | "value": "text/yaml", 721 | "type": "text" 722 | } 723 | ], 724 | "body": { 725 | "mode": "raw", 726 | "raw": "schema: v1\r\nkind: Data\r\nid: docs-data\r\ntype: file\r\nfileId: 1b5dde8e-0ea5-494b-90d9-d4cdb4528204\r\nsplit: pages\r\nembeddings: docs-embeddings\r\nvectorStore: docs-vectorstore", 727 | "options": { 728 | "raw": { 729 | "language": "text" 730 | } 731 | } 732 | }, 733 | "url": { 734 | "raw": "{{Api-Url}}/v1/Data/Apply", 735 | "host": [ 736 | "{{Api-Url}}" 737 | ], 738 | "path": [ 739 | "v1", 740 | "Data", 741 | "Apply" 742 | ] 743 | } 744 | }, 745 | "response": [] 746 | }, 747 | { 748 | "name": "Get", 749 | "protocolProfileBehavior": { 750 | "disabledSystemHeaders": { 751 | "content-type": true 752 | } 753 | }, 754 | "request": { 755 | "method": "POST", 756 | "header": [ 757 | { 758 | "key": "Api-Key", 759 | "value": "{{Api-Key}}", 760 | "type": "text" 761 | }, 762 | { 763 | "key": "Content-Type", 764 | "value": "text/yaml", 765 | "type": "text" 766 | } 767 | ], 768 | "body": { 769 | "mode": "raw", 770 | "raw": "name: new-ds1", 771 | "options": { 772 | "raw": { 773 | "language": "text" 774 | } 775 | } 776 | }, 777 | "url": { 778 | "raw": "{{Api-Url}}/v1/Data", 779 | "host": [ 780 | "{{Api-Url}}" 781 | ], 782 | "path": [ 783 | "v1", 784 | "Data" 785 | ] 786 | } 787 | }, 788 | "response": [] 789 | } 790 | ] 791 | }, 792 | { 793 | "name": "Old", 794 | "item": [ 795 | { 796 | "name": "New Request", 797 | "request": { 798 | "method": "GET", 799 | "header": [] 800 | }, 801 | "response": [] 802 | } 803 | ] 804 | } 805 | ] 806 | } -------------------------------------------------------------------------------- /Floom.Core/Functions/FunctionsService.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Compression; 2 | using System.Net.Http.Headers; 3 | using System.Text; 4 | using Floom.Assets; 5 | using Floom.Auth; 6 | using Floom.Repository; 7 | using MongoDB.Bson; 8 | using MongoDB.Driver; 9 | using Newtonsoft.Json; 10 | using YamlDotNet.Serialization; 11 | using YamlDotNet.Serialization.NamingConventions; 12 | 13 | namespace Floom.Functions; 14 | 15 | public interface IFunctionsService 16 | { 17 | Task DeployFunctionAsync(string filePath, string userId); 18 | Task RunFunctionAsync(string userId, string functionName, string userPrompt, Dictionary? parameters); 19 | Task> ListFunctionsAsync(string userId); 20 | Task GetFunctionByNameAsync(string userId, string functionName); 21 | Task> ListPublicFeaturedFunctionsAsync(); 22 | Task> SearchPublicFunctionsAsync(string query); 23 | Task AddRolesToFunctionAsync(string functionName, string functionUserId, string userId); 24 | Task RemoveRolesToFunctionAsync(string functionName, string functionUserId, string userId); 25 | } 26 | 27 | public class FunctionsService : IFunctionsService 28 | { 29 | private readonly FloomAssetsRepository _floomAssetsRepository; 30 | private readonly IRepository _repository; 31 | 32 | private readonly IRepository _userRepository; 33 | 34 | public FunctionsService(FloomAssetsRepository floomAssetsRepository, IRepositoryFactory repositoryFactory, HttpClient httpClient) 35 | { 36 | _floomAssetsRepository = floomAssetsRepository; 37 | _repository = repositoryFactory.Create(); 38 | _userRepository = repositoryFactory.Create(); 39 | } 40 | 41 | public async Task FindFunctionByNameAndUserIdAsync(string functionName, string functionUserId) 42 | { 43 | return await _repository.FindByCondition(f => f.name == functionName && f.userId == functionUserId); 44 | } 45 | 46 | public async Task UpdateFunctionAsync(FunctionEntity functionEntity) 47 | { 48 | await _repository.UpsertEntity(functionEntity, functionEntity.Id, "Id"); 49 | } 50 | 51 | public async Task AddRolesToFunctionAsync(string functionName, string functionUserId, string userId) 52 | { 53 | // Get the user by ID 54 | var user = await _userRepository.Get(userId, "_id"); 55 | 56 | if (user is not { emailAddress: "nadavnuni1@gmail.com" }) 57 | { 58 | 59 | throw new UnauthorizedAccessException("You are not authorized to perform this action."); 60 | } 61 | 62 | // Find the function by name 63 | var functionEntity = await FindFunctionByNameAndUserIdAsync(functionName, functionUserId); 64 | if (functionEntity == null) 65 | { 66 | throw new Exception("Function not found."); 67 | } 68 | 69 | // Add the roles "Public" and "Featured" to the function 70 | if (functionEntity.roles == null) 71 | { 72 | functionEntity.roles = new string[] { Roles.Public, Roles.Featured }; 73 | } 74 | else 75 | { 76 | var rolesList = functionEntity.roles.ToList(); 77 | if (!rolesList.Contains(Roles.Public)) rolesList.Add(Roles.Public); 78 | if (!rolesList.Contains(Roles.Featured)) rolesList.Add(Roles.Featured); 79 | functionEntity.roles = rolesList.ToArray(); 80 | } 81 | 82 | // Update the function in the repository 83 | await UpdateFunctionAsync(functionEntity); 84 | } 85 | 86 | public async Task RemoveRolesToFunctionAsync(string functionName, string functionUserId, string userId) 87 | { 88 | // Get the user by ID 89 | var user = await _userRepository.Get(userId, "_id"); 90 | 91 | if (user is not { emailAddress: "nadavnuni1@gmail.com" }) 92 | { 93 | 94 | throw new UnauthorizedAccessException("You are not authorized to perform this action."); 95 | } 96 | 97 | // Find the function by name 98 | var functionEntity = await FindFunctionByNameAndUserIdAsync(functionName, functionUserId); 99 | if (functionEntity == null) 100 | { 101 | throw new Exception("Function not found."); 102 | } 103 | 104 | // Remove the roles "Public" and "Featured" from the function 105 | if (functionEntity.roles == null) 106 | { 107 | return; 108 | } 109 | 110 | var rolesList = functionEntity.roles.ToList(); 111 | if (rolesList.Contains(Roles.Public)) rolesList.Remove(Roles.Public); 112 | if (rolesList.Contains(Roles.Featured)) rolesList.Remove(Roles.Featured); 113 | functionEntity.roles = rolesList.ToArray(); 114 | 115 | // Update the function in the repository 116 | await UpdateFunctionAsync(functionEntity); 117 | } 118 | 119 | public async Task DeployFunctionAsync(string filePath, string userId) 120 | { 121 | var tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); 122 | try 123 | { 124 | // 1. Unzip the file 125 | Directory.CreateDirectory(tempDirectory); 126 | ZipFile.ExtractToDirectory(filePath, tempDirectory); 127 | 128 | // 2. Upload prompt.py to S3 129 | var promptFilePath = Path.Combine(tempDirectory, "prompt.py"); 130 | if (!File.Exists(promptFilePath)) 131 | { 132 | throw new FileNotFoundException("prompt.py is missing in the zip file."); 133 | } 134 | using var promptStream = new FileStream(promptFilePath, FileMode.Open); 135 | var promptFile = new FormFile(promptStream, 0, promptStream.Length, null, "prompt.py"); 136 | var promptFileUrl = await _floomAssetsRepository.UploadPythonFileToAwsBucket(promptFile); 137 | 138 | // 3. Upload data.py to S3 if it exists 139 | string dataFileUrl = null; 140 | var dataFilePath = Path.Combine(tempDirectory, "data.py"); 141 | if (File.Exists(dataFilePath)) 142 | { 143 | using var dataStream = new FileStream(dataFilePath, FileMode.Open); 144 | var dataFile = new FormFile(dataStream, 0, dataStream.Length, null, "data.py"); 145 | dataFileUrl = await _floomAssetsRepository.UploadPythonFileToAwsBucket(dataFile); 146 | } 147 | 148 | // 4. Parse manifest.yml 149 | var manifestFilePath = Path.Combine(tempDirectory, "manifest.yml"); 150 | if (!File.Exists(manifestFilePath)) 151 | { 152 | throw new FileNotFoundException("manifest.yml is missing in the zip file."); 153 | } 154 | 155 | Manifest? manifest = null; 156 | try 157 | { 158 | var manifestContent = await File.ReadAllTextAsync(manifestFilePath); 159 | var deserializer = new DeserializerBuilder() 160 | .WithNamingConvention(CamelCaseNamingConvention.Instance) 161 | .Build(); 162 | var manifestDto = deserializer.Deserialize(manifestContent); 163 | manifest = manifestDto.manifest; 164 | } 165 | catch (Exception ex) 166 | { 167 | throw new Exception("Failed to parse manifest.yml", ex); 168 | } 169 | 170 | // 5. Normalize function name and check if it exists for the user 171 | var normalizedFunctionName = FunctionsUtils.NormalizeFunctionName(manifest.name); 172 | var existingFunction = await _repository.FindByCondition(f => f.name == normalizedFunctionName); 173 | // if existing function of the same name exists and belongs to the same user, update it 174 | if (existingFunction != null && existingFunction.userId == userId) 175 | { 176 | // Update existing function 177 | existingFunction.runtimeLanguage = manifest.runtime.language; 178 | existingFunction.runtimeFramework = manifest.runtime.framework; 179 | existingFunction.promptUrl = promptFileUrl; 180 | existingFunction.dataUrl = dataFileUrl; 181 | 182 | // Handle translated fields 183 | existingFunction.description = new TranslatedField 184 | { 185 | en = manifest.description 186 | }; 187 | 188 | if (manifest.parameters == null) 189 | { 190 | existingFunction.parameters = new List(); 191 | } 192 | else 193 | { 194 | List parameters = manifest.parameters?.Select(dto => new Parameter 195 | { 196 | name = dto.name, 197 | description = new TranslatedField 198 | { 199 | en = dto.description 200 | }, 201 | required = dto.required, 202 | defaultValue = dto.defaultValue 203 | }).ToList() ?? new List(); 204 | 205 | existingFunction.parameters = parameters; 206 | } 207 | 208 | await _repository.UpsertEntity(existingFunction, existingFunction.Id, "Id"); 209 | return existingFunction.name; 210 | } 211 | else 212 | { 213 | // Save new function entity 214 | 215 | var functionEntity = new FunctionEntity 216 | { 217 | name = normalizedFunctionName, 218 | runtimeLanguage = manifest.runtime.language, 219 | runtimeFramework = manifest.runtime.framework, 220 | promptUrl = promptFileUrl, 221 | dataUrl = dataFileUrl, 222 | userId = userId, 223 | description = new TranslatedField 224 | { 225 | en = manifest.description 226 | } 227 | }; 228 | 229 | if (manifest.parameters == null) 230 | { 231 | functionEntity.parameters = new List(); 232 | } 233 | else 234 | { 235 | List parameters = manifest.parameters?.Select(dto => new Parameter 236 | { 237 | name = dto.name, 238 | description = new TranslatedField 239 | { 240 | en = dto.description 241 | }, 242 | required = dto.required, 243 | defaultValue = dto.defaultValue 244 | }).ToList() ?? new List(); 245 | functionEntity.parameters = parameters; 246 | } 247 | 248 | await _repository.Insert(functionEntity); 249 | 250 | return functionEntity.name; 251 | } 252 | } 253 | finally 254 | { 255 | // Ensure the temporary directory and its contents are deleted 256 | if (Directory.Exists(tempDirectory)) 257 | { 258 | Directory.Delete(tempDirectory, true); 259 | } 260 | } 261 | } 262 | 263 | public async Task RunFunctionAsync(string? userId, string functionName, string userPrompt, Dictionary? parameters) 264 | { 265 | if (string.IsNullOrEmpty(functionName)) 266 | { 267 | throw new ArgumentNullException(nameof(functionName)); 268 | } 269 | 270 | if (string.IsNullOrEmpty(userPrompt)) 271 | { 272 | throw new ArgumentNullException(nameof(userPrompt)); 273 | } 274 | 275 | var normalizedFunctionName = FunctionsUtils.NormalizeFunctionName(functionName); 276 | var functionEntity = await _repository.FindByCondition(f => f.name == normalizedFunctionName); 277 | 278 | if (functionEntity == null) 279 | { 280 | throw new Exception("Function not found."); 281 | } 282 | 283 | // Check if the function is public or if the user is the owner of the function 284 | if (functionEntity.IsPublic() || (userId != null && userId == functionEntity.userId)) 285 | { 286 | return await RunFunctionInternal(functionEntity, userPrompt, parameters); 287 | } 288 | 289 | throw new Exception("User does not have access to this function."); 290 | } 291 | 292 | private async Task RunFunctionInternal(FunctionEntity functionEntity, string userPrompt, Dictionary? parameters) 293 | { 294 | // 2. Get the promptUrl file 295 | var promptFileUrl = functionEntity.promptUrl; 296 | 297 | // 3. Download the prompt.py file 298 | Console.WriteLine("Downloading prompt file from S3"); 299 | var promptFileStream = await _floomAssetsRepository.DownloadFileFromS3Async(promptFileUrl); 300 | Console.WriteLine("Downloaded prompt file from S3"); 301 | var promptFilePath = Path.GetTempFileName(); 302 | // copy file to file stream 303 | using (var fileStream = new FileStream(promptFilePath, FileMode.Create, FileAccess.Write)) 304 | { 305 | await promptFileStream.CopyToAsync(fileStream); 306 | } 307 | 308 | try 309 | { 310 | // Prepare and send the HTTP request 311 | var client = new HttpClient(); 312 | var requestTemp = new HttpRequestMessage(HttpMethod.Post, "https://roy1dayo5i.execute-api.us-east-1.amazonaws.com/prompt"); 313 | var form = new MultipartFormDataContent(); 314 | 315 | // Add the file 316 | var fileContent = new StreamContent(new FileStream(promptFilePath, FileMode.Open, FileAccess.Read)); 317 | fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data"); 318 | form.Add(fileContent, "file", "prompt.py"); 319 | 320 | // convert parameterDtos to dictionary and set as variables 321 | // Add the config 322 | // parameters is a dictionary of key-value pairs, could be null, in case of null do not add to the config 323 | var config = new 324 | { 325 | input = userPrompt, 326 | variables = parameters, 327 | config = new { }, 328 | env = new { OPENAI_API_KEY = Environment.GetEnvironmentVariable("OPENAI_API_KEY") } 329 | }; 330 | var configJson = JsonConvert.SerializeObject(config); 331 | form.Add(new StringContent(configJson, Encoding.UTF8, "application/json"), "config"); 332 | 333 | requestTemp.Content = form; 334 | 335 | var response = await client.SendAsync(requestTemp); 336 | var responseContent = await response.Content.ReadAsStringAsync(); 337 | 338 | return responseContent; 339 | } 340 | finally 341 | { 342 | // Clean up the temporary file 343 | if (File.Exists(promptFilePath)) 344 | { 345 | File.Delete(promptFilePath); 346 | } 347 | } 348 | } 349 | 350 | public async Task> ListFunctionsAsync(string userId) 351 | { 352 | var functions = await _repository.ListByConditionAsync(f => f.userId == userId); 353 | 354 | var result = new List(); 355 | 356 | foreach (var function in functions) 357 | { 358 | // Fetch the user details for each function's userId 359 | var user = await _userRepository.Get(function.userId, "_id"); 360 | 361 | var authorName = !string.IsNullOrEmpty(user?.nickname) ? user.nickname : user?.username; 362 | 363 | result.Add(new BaseFunctionDto 364 | { 365 | name = function.name ?? string.Empty, // Set to empty string if null 366 | description = function.description, // Ensure only English description is used 367 | runtimeLanguage = function.runtimeLanguage ?? string.Empty, // Set to empty string if null 368 | runtimeFramework = function.runtimeFramework ?? string.Empty, // Set to empty string if null 369 | author = string.IsNullOrEmpty(authorName) ? null : authorName, // Set to null if empty 370 | version = function.version ?? string.Empty, // Set to empty string if null 371 | rating = function.rating ?? 0.0, // Set to 0 if null (assuming rating is a numeric type) 372 | downloads = function.downloads ?? new List(), // Set to empty list if null 373 | parameters = function.parameters?.Select(p => new ParameterDto 374 | { 375 | name = p.name ?? string.Empty, // Set to empty string if null 376 | description = p.description, // Fetch only the English description 377 | required = p.required, // Boolean field 378 | defaultValue = p.defaultValue switch 379 | { 380 | string str => str, // Keep as string if it's a string 381 | IEnumerable array => array.ToArray(), // Convert to array if it's IEnumerable 382 | _ => null // Otherwise, set to null 383 | } 384 | }).ToList() ?? new List() // Initialize as empty list if parameters is null 385 | }); 386 | } 387 | 388 | return result; 389 | } 390 | 391 | public async Task GetFunctionByNameAsync(string? userId, string functionName) 392 | { 393 | // if userId is null, then function must have public role 394 | // otherwise, function must have the same userId 395 | 396 | var normalizedFunctionName = FunctionsUtils.NormalizeFunctionName(functionName); 397 | var function = await _repository.FindByCondition(f => f.name == normalizedFunctionName && f.roles.Contains(Roles.Public)); 398 | 399 | if(function == null) 400 | { 401 | return null; 402 | } 403 | 404 | var result = new BaseFunctionDto() 405 | { 406 | name = function.name, 407 | description = function.description, 408 | runtimeLanguage = function.runtimeLanguage ?? string.Empty, 409 | runtimeFramework = function.runtimeFramework ?? string.Empty, 410 | version = function.version ?? string.Empty, 411 | rating = function.rating ?? 0.0, 412 | downloads = function.downloads ?? new List(), 413 | parameters = function.parameters?.Select(p => new ParameterDto 414 | { 415 | name = p.name ?? string.Empty, 416 | description = p.description, 417 | required = p.required, 418 | defaultValue = p.defaultValue switch 419 | { 420 | string str => str, 421 | IEnumerable array => array.ToArray(), 422 | _ => null 423 | } 424 | }).ToList() ?? new List() 425 | }; 426 | 427 | return result; 428 | } 429 | 430 | 431 | public async Task> ListPublicFeaturedFunctionsAsync() 432 | { 433 | var publicFeaturedFunctions = await _repository.ListByConditionAsync( 434 | f => f.roles != null && f.roles.Contains(Roles.Public) && f.roles.Contains(Roles.Featured)); 435 | 436 | var result = new List(); 437 | 438 | foreach (var function in publicFeaturedFunctions) 439 | { 440 | var user = await _userRepository.Get(function.userId, "_id"); 441 | if (user == null) 442 | { 443 | continue; 444 | } 445 | 446 | var authorName = !string.IsNullOrEmpty(user.nickname) ? user.nickname : user.username; 447 | 448 | result.Add(new BaseFunctionDto 449 | { 450 | name = function.name, 451 | title = new TranslatedField 452 | { 453 | en = function.title?.en ?? string.Empty, 454 | fr = function.title?.fr ?? string.Empty, 455 | es = function.title?.es ?? string.Empty 456 | }, 457 | description = new TranslatedField 458 | { 459 | en = function.description?.en ?? string.Empty, 460 | fr = function.description?.fr ?? string.Empty, 461 | es = function.description?.es ?? string.Empty 462 | }, 463 | promptPlaceholder = new TranslatedField() 464 | { 465 | en = function.promptPlaceholder?.en ?? string.Empty, 466 | fr = function.promptPlaceholder?.fr ?? string.Empty, 467 | es = function.promptPlaceholder?.es ?? string.Empty 468 | }, 469 | runtimeLanguage = function.runtimeLanguage ?? string.Empty, 470 | runtimeFramework = function.runtimeFramework ?? string.Empty, 471 | author = string.IsNullOrEmpty(authorName) ? null : authorName, 472 | version = function.version ?? string.Empty, 473 | rating = function.rating ?? 0f, 474 | downloads = function.downloads ?? new List(), 475 | parameters = function.parameters?.Select(p => new ParameterDto 476 | { 477 | name = p.name ?? string.Empty, 478 | description = new TranslatedField 479 | { 480 | en = p.description?.en ?? string.Empty, 481 | fr = p.description?.fr ?? string.Empty, 482 | es = p.description?.es ?? string.Empty 483 | }, 484 | required = p.required, 485 | defaultValue = p.defaultValue is string 486 | ? p.defaultValue 487 | : p.defaultValue is IEnumerable array 488 | ? array.ToArray() 489 | : null 490 | }).ToList() ?? new List() 491 | }); 492 | } 493 | 494 | return result; 495 | } 496 | 497 | public async Task> SearchPublicFunctionsAsync(string query) 498 | { 499 | if (query == null || query.Length < 5) 500 | { 501 | return new List(); 502 | } 503 | 504 | // Search for functions with the "Public" role 505 | // Search for function that their description or name contains the query 506 | 507 | var regexQuery = new BsonRegularExpression(query, "i"); // 'i' makes the regex case-insensitive 508 | 509 | // Create filters for both the title and description fields 510 | var titleFilter = Builders.Filter.Regex("title.en", regexQuery); 511 | var descriptionFilter = Builders.Filter.Regex("description.en", regexQuery); 512 | var nameFilter = Builders.Filter.Regex("name", regexQuery); 513 | 514 | // Create a role filter to match "Public" roles 515 | var roleFilter = Builders.Filter.Eq("roles", Roles.Public); 516 | 517 | // Combine the filters using OR (|) for title and description and AND (&) for roles 518 | var finalFilter = Builders.Filter.And( 519 | roleFilter, 520 | Builders.Filter.Or(nameFilter, titleFilter, descriptionFilter) 521 | ); 522 | 523 | // Fetch the results using the combined filter 524 | var searchResults = await _repository.ListByFilterAsync(finalFilter); 525 | 526 | var result = new List(); 527 | 528 | foreach (var function in searchResults) 529 | { 530 | var user = await _userRepository.Get(function.userId, "_id"); 531 | if (user == null) 532 | { 533 | continue; 534 | } 535 | 536 | var authorName = !string.IsNullOrEmpty(user.nickname) ? user.nickname : user.username; 537 | 538 | result.Add(new BaseFunctionDto 539 | { 540 | name = function.name, 541 | author = authorName, 542 | rating = function.rating ?? 0f, 543 | title = new TranslatedField 544 | { 545 | en = function.title?.en ?? string.Empty, 546 | fr = function.title?.fr ?? string.Empty, 547 | es = function.title?.es ?? string.Empty 548 | }, 549 | description = new TranslatedField 550 | { 551 | en = function.description?.en ?? string.Empty, 552 | fr = function.description?.fr ?? string.Empty, 553 | es = function.description?.es ?? string.Empty 554 | } 555 | }); 556 | } 557 | 558 | return result; 559 | } 560 | } 561 | 562 | public class LambdaRunConfig 563 | { 564 | public string Input { get; set; } 565 | public Dictionary Variables { get; set; } 566 | public Dictionary ConfigSettings { get; set; } 567 | public Dictionary Env { get; set; } 568 | } 569 | 570 | partial class ManifestDto 571 | { 572 | public Manifest manifest; 573 | } 574 | 575 | public class Manifest 576 | { 577 | public string name { get; set; } 578 | public string? description { get; set; } 579 | 580 | public Runtime runtime { get; set; } 581 | 582 | public Entrypoint entrypoint { get; set; } 583 | 584 | public List? parameters { get; set; } 585 | } 586 | 587 | public class ManifestParameter 588 | { 589 | public string name { get; set; } 590 | public string description { get; set; } 591 | public bool required { get; set; } 592 | public object? defaultValue { get; set; } 593 | } 594 | 595 | public class Runtime 596 | { 597 | public string language { get; set; } 598 | public string framework { get; set; } 599 | } 600 | 601 | public class Entrypoint 602 | { 603 | public string prompt { get; set; } 604 | } --------------------------------------------------------------------------------