diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 982f6573..97fb1a70 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -156,12 +156,12 @@ jobs: echo "VFULL: $VFULL" ARTIFACT="mor-launch-$TAG_NAME-ubuntu-x64.zip" echo "Artifact: $ARTIFACT" - LLAMACPP=llama-b3256-bin-ubuntu-x64.zip + LLAMACPP=llama-b4354-bin-ubuntu-x64.zip MODEL=tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf - wget -nv https://github.com/ggerganov/llama.cpp/releases/download/b3256/$LLAMACPP + wget -nv https://github.com/ggerganov/llama.cpp/releases/download/b4354/$LLAMACPP wget -nv https://huggingface.co/TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF/resolve/main/$MODEL unzip -o -j $LLAMACPP build/bin/llama-server - echo '{"run":["./llama-server -m ./'"$MODEL"'","./proxy-router","./morpheus-ui-'"$VFULL"'-x86_64-linux.AppImage"]}' > mor-launch.json + echo '{"run":["./llama-server --no-webui -m ./'"$MODEL"'","./proxy-router","./morpheus-ui-'"$VFULL"'-x86_64-linux.AppImage"]}' > mor-launch.json echo "Contents of mor-launch.json: " cat mor-launch.json mv ./cli/mor-cli mor-cli @@ -235,12 +235,12 @@ jobs: run: | ARTIFACT="mor-launch-$TAG_NAME-macos-x64.zip" echo "Artifact: $ARTIFACT" - LLAMACPP=llama-b3256-bin-macos-x64.zip + LLAMACPP=llama-b4354-bin-macos-x64.zip MODEL=tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf - wget -nv https://github.com/ggerganov/llama.cpp/releases/download/b3256/$LLAMACPP + wget -nv https://github.com/ggerganov/llama.cpp/releases/download/b4354/$LLAMACPP wget -nv https://huggingface.co/TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF/resolve/main/$MODEL unzip -o -j $LLAMACPP build/bin/llama-server - echo '{"run":["./llama-server -m ./'$MODEL'","./proxy-router","./MorpheusUI.app/Contents/MacOS/MorpheusUI"]}' > mor-launch.json + echo '{"run":["./llama-server --no-webui -m ./'$MODEL'","./proxy-router","./MorpheusUI.app/Contents/MacOS/MorpheusUI"]}' > mor-launch.json echo "Contents of mor-launch.json: " cat mor-launch.json mv ./cli/mor-cli mor-cli @@ -316,12 +316,12 @@ jobs: run: | ARTIFACT="mor-launch-$TAG_NAME-macos-arm64.zip" echo "Artifact: $ARTIFACT" - LLAMACPP=llama-b3256-bin-macos-arm64.zip + LLAMACPP=llama-b4354-bin-macos-arm64.zip MODEL=tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf - wget -nv https://github.com/ggerganov/llama.cpp/releases/download/b3256/$LLAMACPP + wget -nv https://github.com/ggerganov/llama.cpp/releases/download/b4354/$LLAMACPP wget -nv https://huggingface.co/TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF/resolve/main/$MODEL unzip -o -j $LLAMACPP build/bin/llama-server - echo '{"run":["./llama-server -m ./'$MODEL'","./proxy-router","./MorpheusUI.app/Contents/MacOS/MorpheusUI"]}' > mor-launch.json + echo '{"run":["./llama-server --no-webui -m ./'$MODEL'","./proxy-router","./MorpheusUI.app/Contents/MacOS/MorpheusUI"]}' > mor-launch.json echo "Contents of mor-launch.json: " cat mor-launch.json mv ./cli/mor-cli mor-cli @@ -403,12 +403,12 @@ jobs: echo "VFULL: $VFULL" ARTIFACT="mor-launch-$TAG_NAME-win-x64.zip" echo "Artifact: $ARTIFACT" - LLAMACPP=llama-b3256-bin-win-avx2-x64.zip + LLAMACPP=llama-b4354-bin-win-avx2-x64.zip MODEL=tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf - wget -nv https://github.com/ggerganov/llama.cpp/releases/download/b3256/$LLAMACPP + wget -nv https://github.com/ggerganov/llama.cpp/releases/download/b4354/$LLAMACPP wget -nv https://huggingface.co/TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF/resolve/main/$MODEL unzip -o -j $LLAMACPP llama-server.exe llama.dll ggml.dll - echo '{"run":["./llama-server.exe -m ./'"$MODEL"'","./proxy-router.exe","./morpheus-ui-'"$VFULL"'-x64-win.exe"]}' > mor-launch.json + echo '{"run":["./llama-server.exe --no-webui -m ./'"$MODEL"'","./proxy-router.exe","./morpheus-ui-'"$VFULL"'-x64-win.exe"]}' > mor-launch.json echo "Contents of mor-launch.json: " cat mor-launch.json mv .env .env.tmp diff --git a/docs/proxy-router.all.env b/docs/proxy-router.all.env index f9e4c862..dcb06301 100644 --- a/docs/proxy-router.all.env +++ b/docs/proxy-router.all.env @@ -53,12 +53,9 @@ LOG_IS_PROD=false LOG_JSON=false # Log levels for various components (one of debug info warn error dpanic panic fatal) LOG_LEVEL_APP=debug -LOG_LEVEL_CONNECTION=info -LOG_LEVEL_PROXY=info -LOG_LEVEL_SCHEDULER=info -LOG_LEVEL_CONTRACT=debug -LOG_LEVEL_RPC=info -LOG_LEVEL_BADGER=info +LOG_LEVEL_TCP=info +LOG_LEVEL_ETH_RPC=info +LOG_LEVEL_STORAGE=info # Proxy Configurations # Address for the proxy (default is "0.0.0.0:3333" if not set) diff --git a/proxy-router/cmd/main.go b/proxy-router/cmd/main.go index 5556ecfc..21d02700 100644 --- a/proxy-router/cmd/main.go +++ b/proxy-router/cmd/main.go @@ -98,42 +98,28 @@ func start() error { appLog := log.Named("APP") - proxyLog, err := lib.NewLogger(cfg.Log.LevelProxy, cfg.Log.Color, cfg.Log.IsProd, cfg.Log.JSON, mainLogFilePath) + tcpLog, err := lib.NewLogger(cfg.Log.LevelTCP, cfg.Log.Color, cfg.Log.IsProd, cfg.Log.JSON, mainLogFilePath) if err != nil { return err } - connLog, err := lib.NewLogger(cfg.Log.LevelConnection, cfg.Log.Color, cfg.Log.IsProd, cfg.Log.JSON, mainLogFilePath) - if err != nil { - return err - } - - schedulerLogFactory := func(remoteAddr string) (lib.ILogger, error) { - fp := "" - if logFolderPath != "" { - fp = filepath.Join(logFolderPath, fmt.Sprintf("scheduler-%s.log", lib.SanitizeFilename(remoteAddr))) - } - return lib.NewLogger(cfg.Log.LevelScheduler, cfg.Log.Color, cfg.Log.IsProd, cfg.Log.JSON, fp) - } - contractLogStorage := lib.NewCollection[*interfaces.LogStorage]() - rpcLog, err := lib.NewLogger(cfg.Log.LevelRPC, cfg.Log.Color, cfg.Log.IsProd, cfg.Log.JSON, mainLogFilePath) + rpcLog, err := lib.NewLogger(cfg.Log.LevelEthRPC, cfg.Log.Color, cfg.Log.IsProd, cfg.Log.JSON, mainLogFilePath) if err != nil { return err } - badgerLog, err := lib.NewLogger(cfg.Log.LevelBadger, cfg.Log.Color, cfg.Log.IsProd, cfg.Log.JSON, mainLogFilePath) + storageLog, err := lib.NewLogger(cfg.Log.LevelStorage, cfg.Log.Color, cfg.Log.IsProd, cfg.Log.JSON, mainLogFilePath) if err != nil { return err } defer func() { - _ = connLog.Close() - _ = proxyLog.Close() + _ = tcpLog.Close() _ = log.Close() _ = rpcLog.Close() - _ = badgerLog.Close() + _ = storageLog.Close() }() appLog.Infof("proxy-router %s", config.BuildVersion) @@ -198,7 +184,7 @@ func start() error { appLog.Info("Wallet deleted") } - ethNodeStorage := ethclient.NewRPCClientStoreKeychain(keychainStorage, nil, log) + ethNodeStorage := ethclient.NewRPCClientStoreKeychain(keychainStorage, nil, rpcLog.Named("RPC")) err = ethNodeStorage.RemoveURLs() if err != nil { appLog.Warnf("Failed to remove eth node urls\n%s", err) @@ -227,16 +213,16 @@ func start() error { } appLog.Infof("connected to ethereum node: %s, chainID: %d", cfg.Blockchain.EthNodeAddress, chainID) - storage := storages.NewStorage(badgerLog, cfg.Proxy.StoragePath) + storage := storages.NewStorage(storageLog, cfg.Proxy.StoragePath) sessionStorage := storages.NewSessionStorage(storage) var wallet interfaces.Wallet if len(*cfg.Marketplace.WalletPrivateKey) > 0 { wallet = wlt.NewEnvWallet(*cfg.Marketplace.WalletPrivateKey) - log.Warnf("Using env wallet. Private key persistance unavailable") + appLog.Warnf("Using env wallet. Private key persistance unavailable") } else { wallet = wlt.NewKeychainWallet(keychainStorage) - log.Infof("Using keychain wallet") + appLog.Infof("Using keychain wallet") } var logWatcher contracts.LogWatcher @@ -248,7 +234,7 @@ func start() error { appLog.Infof("using polling for blockchain events") } - scorer, err := config.LoadRating(cfg.Proxy.RatingConfigPath, log) + scorer, err := config.LoadRating(cfg.Proxy.RatingConfigPath, appLog) if err != nil { return err } @@ -257,35 +243,35 @@ func start() error { chatStorage := chatstorage.NewChatStorage(chatStoragePath) multicallBackend := multicall.NewMulticall3Custom(ethClient, *cfg.Blockchain.Multicall3Addr) - sessionRouter := registries.NewSessionRouter(*cfg.Marketplace.DiamondContractAddress, ethClient, multicallBackend, log) - marketplace := registries.NewMarketplace(*cfg.Marketplace.DiamondContractAddress, ethClient, multicallBackend, log) + sessionRouter := registries.NewSessionRouter(*cfg.Marketplace.DiamondContractAddress, ethClient, multicallBackend, rpcLog) + marketplace := registries.NewMarketplace(*cfg.Marketplace.DiamondContractAddress, ethClient, multicallBackend, rpcLog) sessionRepo := sessionrepo.NewSessionRepositoryCached(sessionStorage, sessionRouter, marketplace) - proxyRouterApi := proxyapi.NewProxySender(chainID, wallet, contractLogStorage, sessionStorage, sessionRepo, log) + proxyRouterApi := proxyapi.NewProxySender(chainID, wallet, contractLogStorage, sessionStorage, sessionRepo, appLog) explorer := blockchainapi.NewExplorerClient(cfg.Blockchain.ExplorerApiUrl, *cfg.Marketplace.MorTokenAddress, cfg.Blockchain.ExplorerRetryDelay, cfg.Blockchain.ExplorerMaxRetries) - blockchainApi := blockchainapi.NewBlockchainService(ethClient, multicallBackend, *cfg.Marketplace.DiamondContractAddress, *cfg.Marketplace.MorTokenAddress, explorer, wallet, proxyRouterApi, sessionRepo, scorer, proxyLog, cfg.Blockchain.EthLegacyTx) + blockchainApi := blockchainapi.NewBlockchainService(ethClient, multicallBackend, *cfg.Marketplace.DiamondContractAddress, *cfg.Marketplace.MorTokenAddress, explorer, wallet, proxyRouterApi, sessionRepo, scorer, appLog, rpcLog, cfg.Blockchain.EthLegacyTx) proxyRouterApi.SetSessionService(blockchainApi) - modelConfigLoader := config.NewModelConfigLoader(cfg.Proxy.ModelsConfigPath, valid, blockchainApi, &aiengine.ConnectionChecker{}, log) + modelConfigLoader := config.NewModelConfigLoader(cfg.Proxy.ModelsConfigPath, valid, blockchainApi, &aiengine.ConnectionChecker{}, appLog) err = modelConfigLoader.Init() if err != nil { - log.Warnf("failed to load model config, running with empty: %s", err) + appLog.Warnf("failed to load model config, running with empty: %s", err) } - aiEngine := aiengine.NewAiEngine(proxyRouterApi, chatStorage, modelConfigLoader, log) + aiEngine := aiengine.NewAiEngine(proxyRouterApi, chatStorage, modelConfigLoader, appLog) - eventListener := blockchainapi.NewEventsListener(sessionRepo, sessionRouter, wallet, logWatcher, log) + eventListener := blockchainapi.NewEventsListener(sessionRepo, sessionRouter, wallet, logWatcher, appLog) - sessionExpiryHandler := blockchainapi.NewSessionExpiryHandler(blockchainApi, sessionStorage, wallet, log) - blockchainController := blockchainapi.NewBlockchainController(blockchainApi, log) + sessionExpiryHandler := blockchainapi.NewSessionExpiryHandler(blockchainApi, sessionStorage, wallet, appLog) + blockchainController := blockchainapi.NewBlockchainController(blockchainApi, appLog) ethConnectionValidator := system.NewEthConnectionValidator(*big.NewInt(int64(cfg.Blockchain.ChainID))) - proxyController := proxyapi.NewProxyController(proxyRouterApi, aiEngine, chatStorage, *cfg.Proxy.StoreChatContext.Bool, *cfg.Proxy.ForwardChatContext.Bool, log) + proxyController := proxyapi.NewProxyController(proxyRouterApi, aiEngine, chatStorage, *cfg.Proxy.StoreChatContext.Bool, *cfg.Proxy.ForwardChatContext.Bool, appLog) walletController := walletapi.NewWalletController(wallet) - systemController := system.NewSystemController(&cfg, wallet, rpcClientStore, sysConfig, appStartTime, chainID, log, ethConnectionValidator) + systemController := system.NewSystemController(&cfg, wallet, rpcClientStore, sysConfig, appStartTime, chainID, appLog, ethConnectionValidator) apiBus := apibus.NewApiBus(blockchainController, proxyController, walletController, systemController) - httpHandler := httphandlers.CreateHTTPServer(log, apiBus) - httpServer := transport.NewServer(cfg.Web.Address, httpHandler, log.Named("HTTP")) + httpHandler := httphandlers.CreateHTTPServer(appLog, apiBus) + httpServer := transport.NewServer(cfg.Web.Address, httpHandler, appLog.Named("HTTP")) // http server should shut down latest to keep pprof running serverErrCh := make(chan error, 1) @@ -295,9 +281,9 @@ func start() error { cancel() }() - log.Infof("API docs available at %s/swagger/index.html", cfg.Web.PublicUrl) + appLog.Infof("API docs available at %s/swagger/index.html", cfg.Web.PublicUrl) - proxy := proxyctl.NewProxyCtl(eventListener, wallet, chainID, log, connLog, cfg.Proxy.Address, schedulerLogFactory, sessionStorage, modelConfigLoader, valid, aiEngine, blockchainApi, sessionRepo, sessionExpiryHandler) + proxy := proxyctl.NewProxyCtl(eventListener, wallet, chainID, appLog, tcpLog, cfg.Proxy.Address, sessionStorage, modelConfigLoader, valid, aiEngine, blockchainApi, sessionRepo, sessionExpiryHandler) err = proxy.Run(ctx) cancelServer() diff --git a/proxy-router/docs/docs.go b/proxy-router/docs/docs.go index e6e04388..a10b7116 100644 --- a/proxy-router/docs/docs.go +++ b/proxy-router/docs/docs.go @@ -1125,7 +1125,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/system.ConfigResponse" + "$ref": "#/definitions/system.StatusRes" } } } @@ -1143,7 +1143,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/system.ConfigResponse" + "$ref": "#/definitions/system.StatusRes" } } } @@ -2486,6 +2486,14 @@ const docTemplate = `{ } } } + }, + "system.StatusRes": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + } } }, "externalDocs": { diff --git a/proxy-router/docs/swagger.json b/proxy-router/docs/swagger.json index 10440a9e..f6431ffe 100644 --- a/proxy-router/docs/swagger.json +++ b/proxy-router/docs/swagger.json @@ -1117,7 +1117,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/system.ConfigResponse" + "$ref": "#/definitions/system.StatusRes" } } } @@ -1135,7 +1135,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/system.ConfigResponse" + "$ref": "#/definitions/system.StatusRes" } } } @@ -2478,6 +2478,14 @@ } } } + }, + "system.StatusRes": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + } } }, "externalDocs": { diff --git a/proxy-router/docs/swagger.yaml b/proxy-router/docs/swagger.yaml index e448fe1c..d33dbaff 100644 --- a/proxy-router/docs/swagger.yaml +++ b/proxy-router/docs/swagger.yaml @@ -602,6 +602,11 @@ definitions: required: - urls type: object + system.StatusRes: + properties: + status: + type: string + type: object externalDocs: description: OpenAPI url: https://swagger.io/resources/open-api/ @@ -1337,7 +1342,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/system.ConfigResponse' + $ref: '#/definitions/system.StatusRes' summary: Delete Eth Node URLs tags: - system @@ -1358,7 +1363,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/system.ConfigResponse' + $ref: '#/definitions/system.StatusRes' summary: Set Eth Node URLs tags: - system diff --git a/proxy-router/internal/aiengine/ai_engine.go b/proxy-router/internal/aiengine/ai_engine.go index 6063330c..475c5fd9 100644 --- a/proxy-router/internal/aiengine/ai_engine.go +++ b/proxy-router/internal/aiengine/ai_engine.go @@ -45,7 +45,7 @@ func (a *AiEngine) GetAdapter(ctx context.Context, chatID, modelID, sessionID co return nil, fmt.Errorf("model not found: %s", modelID.Hex()) } var ok bool - engine, ok = ApiAdapterFactory(modelConfig.ApiType, modelConfig.ModelName, modelConfig.ApiURL, modelConfig.ApiKey, a.log) + engine, ok = ApiAdapterFactory(modelConfig.ApiType, modelConfig.ModelName, modelConfig.ApiURL, modelConfig.ApiKey, modelConfig.Parameters, a.log) if !ok { return nil, fmt.Errorf("api adapter not found: %s", modelConfig.ApiType) } diff --git a/proxy-router/internal/aiengine/claudeai.go b/proxy-router/internal/aiengine/claudeai.go new file mode 100644 index 00000000..8392af15 --- /dev/null +++ b/proxy-router/internal/aiengine/claudeai.go @@ -0,0 +1,207 @@ +package aiengine + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + c "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal" + gcs "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/chatstorage/genericchatstorage" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" + "github.com/sashabaranov/go-openai" +) + +// ClaudeAIResponse represents the top-level structure of the ClaudeAI JSON response. +type ClaudeAIResponse struct { + Content []ClaudeAIContent `json:"content"` + ID string `json:"id"` + Model string `json:"model"` + Role string `json:"role"` + StopReason string `json:"stop_reason"` + StopSequence *string `json:"stop_sequence"` + Type string `json:"type"` + Usage ClaudeAIUsage `json:"usage"` +} + +// ClaudeAIContent represents each item in the "content" array. +type ClaudeAIContent struct { + Text string `json:"text"` + Type string `json:"type"` +} + +// ClaudeAIUsage represents the usage statistics of the request/response. +type ClaudeAIUsage struct { + InputTokens int `json:"input_tokens"` + OutputTokens int `json:"output_tokens"` +} + +type ClaudeAIStreamResponse struct { + Type string `json:"type"` + Delta ClaudeAIStreamDelta `json:"delta"` + ContentBlock ClaudeAIStreamContentBlock `json:"content_block"` + Message ClaudeAIStreamMessage `json:"message"` +} + +type ClaudeAIStreamMessage struct { + ID string `json:"id"` + Role string `json:"role"` + Model string `json:"model"` +} + +type ClaudeAIStreamDelta struct { + Type string `json:"type"` + Text string `json:"text"` +} + +type ClaudeAIStreamContentBlock struct { + Type string `json:"type"` + Text string `json:"text"` +} + +const API_TYPE_CLAUDEAI = "claudeai" + +type ClaudeAI struct { + baseURL string + apiKey string + modelName string + client *http.Client + log lib.ILogger +} + +func NewClaudeAIEngine(modelName, baseURL, apiKey string, log lib.ILogger) *ClaudeAI { + return &ClaudeAI{ + baseURL: baseURL, + modelName: modelName, + apiKey: apiKey, + client: &http.Client{}, + log: log, + } +} + +func (a *ClaudeAI) Prompt(ctx context.Context, compl *openai.ChatCompletionRequest, cb gcs.CompletionCallback) error { + compl.Model = a.modelName + compl.MaxTokens = 1024 + requestBody, err := json.Marshal(compl) + if err != nil { + return fmt.Errorf("failed to encode request: %v", err) + } + + req, err := http.NewRequestWithContext(ctx, "POST", a.baseURL+"/messages", bytes.NewReader(requestBody)) + if err != nil { + return fmt.Errorf("failed to create request: %v", err) + } + + if a.apiKey != "" { + req.Header.Set("x-api-key", a.apiKey) + } + req.Header.Set("anthropic-version", "2023-06-01") + req.Header.Set(c.HEADER_CONTENT_TYPE, c.CONTENT_TYPE_JSON) + req.Header.Set(c.HEADER_CONNECTION, c.CONNECTION_KEEP_ALIVE) + if compl.Stream { + req.Header.Set(c.HEADER_ACCEPT, c.CONTENT_TYPE_EVENT_STREAM) + } + resp, err := a.client.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + if isContentTypeStream(resp.Header) { + return a.readStream(ctx, resp.Body, cb) + } + + return a.readResponse(ctx, resp.Body, cb) +} + +func (a *ClaudeAI) readResponse(ctx context.Context, body io.Reader, cb gcs.CompletionCallback) error { + var compl ClaudeAIResponse + if err := json.NewDecoder(body).Decode(&compl); err != nil { + return fmt.Errorf("failed to decode response: %v", err) + } + + var openaiCompl openai.ChatCompletionResponse + openaiCompl.ID = compl.ID + openaiCompl.Model = compl.Model + openaiCompl.Choices = make([]openai.ChatCompletionChoice, len(compl.Content)) + for i, content := range compl.Content { + openaiCompl.Choices[i].Message.Content = content.Text + openaiCompl.Choices[i].Message.Role = compl.Role + } + openaiCompl.Usage.PromptTokens = compl.Usage.InputTokens + openaiCompl.Usage.CompletionTokens = compl.Usage.OutputTokens + openaiCompl.Usage.TotalTokens = compl.Usage.InputTokens + compl.Usage.OutputTokens + + chunk := gcs.NewChunkText(&openaiCompl) + err := cb(ctx, chunk) + if err != nil { + return fmt.Errorf("callback failed: %v", err) + } + + return nil +} + +func (a *ClaudeAI) readStream(ctx context.Context, body io.Reader, cb gcs.CompletionCallback) error { + var model string + var role string + var messageID string + + scanner := bufio.NewScanner(body) + for scanner.Scan() { + line := scanner.Text() + + if strings.HasPrefix(line, StreamDataPrefix) { + data := line[len(StreamDataPrefix):] // Skip the "data: " prefix + + var compl ClaudeAIStreamResponse + if err := json.Unmarshal([]byte(data), &compl); err != nil { + return fmt.Errorf("error decoding response: %s\n%s", err, line) + } + if compl.Type == "message_stop" { + return nil + } + + if compl.Message.ID != "" { + messageID = compl.Message.ID + } + if compl.Message.Role != "" { + role = compl.Message.Role + } + if compl.Message.Model != "" { + model = compl.Message.Model + } + if compl.Delta.Text != "" || compl.ContentBlock.Text != "" { + openaiCompl := openai.ChatCompletionStreamResponse{} + openaiCompl.Choices = make([]openai.ChatCompletionStreamChoice, 1) + openaiCompl.Choices[0].Delta.Content = compl.Delta.Text + openaiCompl.Choices[0].Delta.Role = role + openaiCompl.ID = messageID + openaiCompl.Model = model + openaiCompl.Created = time.Now().Unix() + + // Call the callback function with the unmarshalled completion + chunk := gcs.NewChunkStreaming(&openaiCompl) + err := cb(ctx, chunk) + if err != nil { + return fmt.Errorf("callback failed: %v", err) + } + } + } + } + + if err := scanner.Err(); err != nil { + return fmt.Errorf("error reading stream: %v", err) + } + + return nil +} + +func (a *ClaudeAI) ApiType() string { + return API_TYPE_CLAUDEAI +} + +var _ AIEngineStream = &OpenAI{} diff --git a/proxy-router/internal/aiengine/factory.go b/proxy-router/internal/aiengine/factory.go index b9edd31e..61433828 100644 --- a/proxy-router/internal/aiengine/factory.go +++ b/proxy-router/internal/aiengine/factory.go @@ -2,7 +2,7 @@ package aiengine import "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" -func ApiAdapterFactory(apiType string, modelName string, url string, apikey string, log lib.ILogger) (AIEngineStream, bool) { +func ApiAdapterFactory(apiType string, modelName string, url string, apikey string, parameters ModelParameters, log lib.ILogger) (AIEngineStream, bool) { switch apiType { case API_TYPE_OPENAI: return NewOpenAIEngine(modelName, url, apikey, log), true @@ -12,6 +12,10 @@ func ApiAdapterFactory(apiType string, modelName string, url string, apikey stri return NewProdiaSDXLEngine(modelName, url, apikey, log), true case API_TYPE_PRODIA_V2: return NewProdiaV2Engine(modelName, url, apikey, log), true + case API_TYPE_HYPERBOLIC_SD: + return NewHyperbolicSDEngine(modelName, url, apikey, parameters, log), true + case API_TYPE_CLAUDEAI: + return NewClaudeAIEngine(modelName, url, apikey, log), true } return nil, false } diff --git a/proxy-router/internal/aiengine/hyperbolic_sd.go b/proxy-router/internal/aiengine/hyperbolic_sd.go new file mode 100644 index 00000000..868ee2c5 --- /dev/null +++ b/proxy-router/internal/aiengine/hyperbolic_sd.go @@ -0,0 +1,115 @@ +package aiengine + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + c "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal" + gcs "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/chatstorage/genericchatstorage" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" + "github.com/sashabaranov/go-openai" +) + +const API_TYPE_HYPERBOLIC_SD = "hyperbolic-sd" +const HYPERBOLIC_DEFAULT_BASE_URL = "https://api.hyperbolic.xyz/v1" + +type HyperbolicSD struct { + modelName string + apiURL string + apiKey string + parameters ModelParameters + + log lib.ILogger +} + +type HyperbolicImageGenerationResult struct { + Images []Image `json:"images"` +} + +type Image struct { + Image string `json:"image"` +} + +func NewHyperbolicSDEngine(modelName, apiURL, apiKey string, parameters ModelParameters, log lib.ILogger) *HyperbolicSD { + if apiURL == "" { + apiURL = HYPERBOLIC_DEFAULT_BASE_URL + } + return &HyperbolicSD{ + modelName: modelName, + apiURL: apiURL, + apiKey: apiKey, + log: log, + parameters: parameters, + } +} + +func (s *HyperbolicSD) Prompt(ctx context.Context, prompt *openai.ChatCompletionRequest, cb gcs.CompletionCallback) error { + body := map[string]string{ + "model_name": s.modelName, + "prompt": prompt.Messages[len(prompt.Messages)-1].Content, + "height": "512", + "width": "512", + "backend": "auto", + } + + for key, value := range s.parameters { + body[key] = value + } + + payload, err := json.Marshal(body) + if err != nil { + err = lib.WrapError(ErrImageGenerationInvalidRequest, err) + s.log.Error(err) + return err + } + + req, err := http.NewRequest("POST", fmt.Sprintf("%s/image/generation", s.apiURL), bytes.NewReader(payload)) + if err != nil { + err = lib.WrapError(ErrImageGenerationRequest, err) + s.log.Error(err) + } + + req.Header.Add(c.HEADER_ACCEPT, c.CONTENT_TYPE_JSON) + req.Header.Add(c.HEADER_CONTENT_TYPE, c.CONTENT_TYPE_JSON) + req.Header.Add(c.HEADER_AUTHORIZATION, fmt.Sprintf("%s %s", c.BEARER, s.apiKey)) + + res, err := http.DefaultClient.Do(req) + if err != nil { + err = lib.WrapError(ErrImageGenerationRequest, err) + s.log.Error(err) + return err + } + + defer res.Body.Close() + response, err := io.ReadAll(res.Body) + if err != nil { + err = lib.WrapError(ErrImageGenerationRequest, err) + s.log.Error(err) + return err + } + + result := HyperbolicImageGenerationResult{} + err = json.Unmarshal(response, &result) + if err != nil { + err = lib.WrapError(ErrImageGenerationRequest, err) + s.log.Error(err) + return err + } + + dataPrefix := "data:image/png;base64," + chunk := gcs.NewChunkImageRawContent(&gcs.ImageRawContentResult{ + ImageRawContent: dataPrefix + result.Images[0].Image, + }) + + return cb(ctx, chunk) +} + +func (s *HyperbolicSD) ApiType() string { + return API_TYPE_HYPERBOLIC_SD +} + +var _ AIEngineStream = &HyperbolicSD{} diff --git a/proxy-router/internal/aiengine/interfaces.go b/proxy-router/internal/aiengine/interfaces.go index 9424a252..1396cbde 100644 --- a/proxy-router/internal/aiengine/interfaces.go +++ b/proxy-router/internal/aiengine/interfaces.go @@ -11,3 +11,5 @@ type AIEngineStream interface { Prompt(ctx context.Context, prompt *openai.ChatCompletionRequest, cb genericchatstorage.CompletionCallback) error ApiType() string } + +type ModelParameters map[string]string diff --git a/proxy-router/internal/aiengine/openai.go b/proxy-router/internal/aiengine/openai.go index 72136aaa..3b2ec724 100644 --- a/proxy-router/internal/aiengine/openai.go +++ b/proxy-router/internal/aiengine/openai.go @@ -62,6 +62,7 @@ func (a *OpenAI) Prompt(ctx context.Context, compl *openai.ChatCompletionRequest } defer resp.Body.Close() + a.log.Debugf("AI Model responded with status code: %d", resp.StatusCode) if isContentTypeStream(resp.Header) { return a.readStream(ctx, resp.Body, cb) } diff --git a/proxy-router/internal/aiengine/prodia_sd.go b/proxy-router/internal/aiengine/prodia_sd.go index 782bf335..1b4adae0 100644 --- a/proxy-router/internal/aiengine/prodia_sd.go +++ b/proxy-router/internal/aiengine/prodia_sd.go @@ -50,8 +50,6 @@ func (s *ProdiaSD) Prompt(ctx context.Context, prompt *openai.ChatCompletionRequ return err } - s.log.Debugf("payload: %s", payload) - req, err := http.NewRequest("POST", fmt.Sprintf("%s/sd/generate", s.apiURL), bytes.NewReader(payload)) if err != nil { err = lib.WrapError(ErrImageGenerationRequest, err) @@ -77,8 +75,6 @@ func (s *ProdiaSD) Prompt(ctx context.Context, prompt *openai.ChatCompletionRequ return err } - s.log.Debugf("response: %s", response) - bodyStr := string(response) if strings.Contains(bodyStr, "Invalid Generation Parameters") { return lib.WrapError(ErrImageGenerationRequest, fmt.Errorf(bodyStr)) diff --git a/proxy-router/internal/aiengine/prodia_sdxl.go b/proxy-router/internal/aiengine/prodia_sdxl.go index ff856100..8f227343 100644 --- a/proxy-router/internal/aiengine/prodia_sdxl.go +++ b/proxy-router/internal/aiengine/prodia_sdxl.go @@ -75,8 +75,6 @@ func (s *ProdiaSDXL) Prompt(ctx context.Context, prompt *openai.ChatCompletionRe return err } - s.log.Debugf("response: %s", response) - bodyStr := string(response) if strings.Contains(bodyStr, "Invalid Generation Parameters") { return lib.WrapError(ErrImageGenerationRequest, fmt.Errorf(bodyStr)) diff --git a/proxy-router/internal/aiengine/prodia_v2.go b/proxy-router/internal/aiengine/prodia_v2.go index 9606b63f..b74c182a 100644 --- a/proxy-router/internal/aiengine/prodia_v2.go +++ b/proxy-router/internal/aiengine/prodia_v2.go @@ -66,7 +66,6 @@ func (s *ProdiaV2) Prompt(ctx context.Context, prompt *openai.ChatCompletionRequ s.log.Error(err) } - req.Header.Add(c.HEADER_ACCEPT, c.CONTENT_TYPE_VIDEO_MP4) req.Header.Add(c.HEADER_CONTENT_TYPE, c.CONTENT_TYPE_JSON) req.Header.Add(c.HEADER_AUTHORIZATION, fmt.Sprintf("Bearer %s", s.apiKey)) @@ -84,6 +83,7 @@ func (s *ProdiaV2) Prompt(ctx context.Context, prompt *openai.ChatCompletionRequ return lib.WrapError(ErrBadResponse, fmt.Errorf("status code: %d", res.StatusCode)) } + contentType := res.Header.Get(c.HEADER_CONTENT_TYPE) response, err := io.ReadAll(res.Body) if err != nil { err = lib.WrapError(ErrVideoGenerationRequest, err) @@ -93,10 +93,17 @@ func (s *ProdiaV2) Prompt(ctx context.Context, prompt *openai.ChatCompletionRequ sEnc := b64.StdEncoding.EncodeToString(response) - dataPrefix := "data:video/mp4;base64," - chunk := gcs.NewChunkVideo(&gcs.VideoGenerationResult{ - VideoRawContent: dataPrefix + sEnc, - }) + dataPrefix := fmt.Sprintf("data:%s;base64,", contentType) + var chunk gcs.Chunk + if contentType == "video/mp4" { + chunk = gcs.NewChunkVideo(&gcs.VideoGenerationResult{ + VideoRawContent: dataPrefix + sEnc, + }) + } else { + chunk = gcs.NewChunkImageRawContent(&gcs.ImageRawContentResult{ + ImageRawContent: dataPrefix + sEnc, + }) + } return cb(ctx, chunk) } diff --git a/proxy-router/internal/blockchainapi/event_listener.go b/proxy-router/internal/blockchainapi/event_listener.go index 9d54d6ba..e8949a65 100644 --- a/proxy-router/internal/blockchainapi/event_listener.go +++ b/proxy-router/internal/blockchainapi/event_listener.go @@ -16,7 +16,7 @@ type EventsListener struct { sessionRouter *registries.SessionRouter sessionRepo *sessionrepo.SessionRepositoryCached tsk *lib.Task - log *lib.Logger + log lib.ILogger wallet interfaces.Wallet logWatcher contracts.LogWatcher @@ -24,7 +24,7 @@ type EventsListener struct { addr common.Address } -func NewEventsListener(sessionRepo *sessionrepo.SessionRepositoryCached, sessionRouter *registries.SessionRouter, wallet interfaces.Wallet, logWatcher contracts.LogWatcher, log *lib.Logger) *EventsListener { +func NewEventsListener(sessionRepo *sessionrepo.SessionRepositoryCached, sessionRouter *registries.SessionRouter, wallet interfaces.Wallet, logWatcher contracts.LogWatcher, log lib.ILogger) *EventsListener { return &EventsListener{ log: log, sessionRouter: sessionRouter, diff --git a/proxy-router/internal/blockchainapi/service.go b/proxy-router/internal/blockchainapi/service.go index 605903ca..ed1341f6 100644 --- a/proxy-router/internal/blockchainapi/service.go +++ b/proxy-router/internal/blockchainapi/service.go @@ -73,7 +73,7 @@ var ( ErrBudget = errors.New("failed to parse token budget") ErrMyAddress = errors.New("failed to get my address") ErrInitSession = errors.New("failed to initiate session") - ErrApprove = errors.New("failed to approve") + ErrApprove = errors.New("failed to approve funds") ErrMarshal = errors.New("failed to marshal open session payload") ErrOpenOwnBid = errors.New("cannot open session with own bid") @@ -92,13 +92,14 @@ func NewBlockchainService( sessionRepo *sessionrepo.SessionRepositoryCached, scorerAlgo *rating.Rating, log lib.ILogger, + logEthRpc lib.ILogger, legacyTx bool, ) *BlockchainService { - providerRegistry := r.NewProviderRegistry(diamonContractAddr, ethClient, mc, log) - modelRegistry := r.NewModelRegistry(diamonContractAddr, ethClient, mc, log) - marketplace := r.NewMarketplace(diamonContractAddr, ethClient, mc, log) - sessionRouter := r.NewSessionRouter(diamonContractAddr, ethClient, mc, log) - morToken := r.NewMorToken(morTokenAddr, ethClient, log) + providerRegistry := r.NewProviderRegistry(diamonContractAddr, ethClient, mc, logEthRpc) + modelRegistry := r.NewModelRegistry(diamonContractAddr, ethClient, mc, logEthRpc) + marketplace := r.NewMarketplace(diamonContractAddr, ethClient, mc, logEthRpc) + sessionRouter := r.NewSessionRouter(diamonContractAddr, ethClient, mc, logEthRpc) + morToken := r.NewMorToken(morTokenAddr, ethClient, logEthRpc) return &BlockchainService{ ethClient: ethClient, @@ -860,7 +861,8 @@ func (s *BlockchainService) openSessionByBid(ctx context.Context, bidID common.H return common.Hash{}, ErrOpenOwnBid } - return s.tryOpenSession(ctx, bid, duration, supply, budget, userAddr, false, false) + hash, _, err := s.tryOpenSession(ctx, bid, duration, supply, budget, userAddr, false, false) + return hash, err } func (s *BlockchainService) OpenSessionByModelId(ctx context.Context, modelID common.Hash, duration *big.Int, directPayment bool, isFailoverEnabled bool, omitProvider common.Address) (common.Hash, error) { @@ -914,10 +916,14 @@ func (s *BlockchainService) OpenSessionByModelId(ctx context.Context, modelID co s.log.Infof("trying to open session with provider #%d %s", i, bid.Bid.Provider.String()) durationCopy := new(big.Int).Set(duration) - hash, err := s.tryOpenSession(ctx, &bid.Bid, durationCopy, supply, budget, userAddr, directPayment, isFailoverEnabled) + hash, tryNext, err := s.tryOpenSession(ctx, &bid.Bid, durationCopy, supply, budget, userAddr, directPayment, isFailoverEnabled) if err != nil { s.log.Errorf("failed to open session with provider %s: %s", bid.Bid.Provider.String(), err.Error()) - continue + if tryNext { + continue + } else { + return common.Hash{}, err + } } return hash, nil @@ -971,10 +977,10 @@ func (s *BlockchainService) GetAllBidsWithRating(ctx context.Context, modelAgent return ids, bids, providerModelStats, providers, nil } -func (s *BlockchainService) tryOpenSession(ctx context.Context, bid *structs.Bid, duration, supply, budget *big.Int, userAddr common.Address, directPayment bool, failoverEnabled bool) (common.Hash, error) { +func (s *BlockchainService) tryOpenSession(ctx context.Context, bid *structs.Bid, duration, supply, budget *big.Int, userAddr common.Address, directPayment bool, failoverEnabled bool) (common.Hash, bool, error) { provider, err := s.providerRegistry.GetProviderById(ctx, bid.Provider) if err != nil { - return common.Hash{}, lib.WrapError(ErrProvider, err) + return common.Hash{}, false, lib.WrapError(ErrProvider, err) } sessionCost := (&big.Int{}).Mul(&bid.PricePerSecond.Int, duration) @@ -999,32 +1005,32 @@ func (s *BlockchainService) tryOpenSession(ctx context.Context, bid *structs.Bid initRes, err := s.proxyService.InitiateSession(ctx, userAddr, bid.Provider, amountTransferred, bid.Id, provider.Endpoint) if err != nil { - return common.Hash{}, lib.WrapError(ErrInitSession, err) + return common.Hash{}, true, lib.WrapError(ErrInitSession, err) } _, err = s.Approve(ctx, s.diamonContractAddr, amountTransferred) if err != nil { - return common.Hash{}, lib.WrapError(ErrApprove, err) + return common.Hash{}, false, lib.WrapError(ErrApprove, err) } hash, err := s.OpenSession(ctx, initRes.Approval, initRes.ApprovalSig, amountTransferred, directPayment) if err != nil { - return common.Hash{}, err + return common.Hash{}, false, err } session, err := s.sessionRepo.GetSession(ctx, hash) if err != nil { - return hash, fmt.Errorf("failed to get session: %s", err.Error()) + return hash, false, fmt.Errorf("failed to get session: %s", err.Error()) } session.SetFailoverEnabled(failoverEnabled) err = s.sessionRepo.SaveSession(ctx, session) if err != nil { - return hash, fmt.Errorf("failed to store session: %s", err.Error()) + return hash, false, fmt.Errorf("failed to store session: %s", err.Error()) } - return hash, nil + return hash, false, nil } func (s *BlockchainService) GetMyAddress(ctx context.Context) (common.Address, error) { diff --git a/proxy-router/internal/chatstorage/genericchatstorage/chat_responses.go b/proxy-router/internal/chatstorage/genericchatstorage/chat_responses.go index f016d943..dfd65121 100644 --- a/proxy-router/internal/chatstorage/genericchatstorage/chat_responses.go +++ b/proxy-router/internal/chatstorage/genericchatstorage/chat_responses.go @@ -140,6 +140,12 @@ type ImageGenerationResult struct { type ImageGenerationCallback func(completion *ImageGenerationResult) error +type ImageRawContentResult struct { + ImageRawContent string `json:"imageRawContent"` +} + +type ImageRawContentCallback func(completion *ImageRawContentResult) error + type VideoGenerationResult struct { VideoRawContent string `json:"videoRawContent"` } diff --git a/proxy-router/internal/chatstorage/genericchatstorage/completion.go b/proxy-router/internal/chatstorage/genericchatstorage/completion.go index b75fe402..fd75519d 100644 --- a/proxy-router/internal/chatstorage/genericchatstorage/completion.go +++ b/proxy-router/internal/chatstorage/genericchatstorage/completion.go @@ -169,6 +169,36 @@ func (c *ChunkVideo) Data() interface{} { return c.data } +type ChunkImageRawContent struct { + data *ImageRawContentResult +} + +func NewChunkImageRawContent(data *ImageRawContentResult) *ChunkImageRawContent { + return &ChunkImageRawContent{ + data: data, + } +} + +func (c *ChunkImageRawContent) IsStreaming() bool { + return false +} + +func (c *ChunkImageRawContent) Tokens() int { + return 1 +} + +func (c *ChunkImageRawContent) Type() ChunkType { + return ChunkTypeImage +} + +func (c *ChunkImageRawContent) String() string { + return c.data.ImageRawContent +} + +func (c *ChunkImageRawContent) Data() interface{} { + return c.data +} + type Chunk interface { IsStreaming() bool Tokens() int @@ -182,3 +212,4 @@ var _ Chunk = &ChunkImage{} var _ Chunk = &ChunkControl{} var _ Chunk = &ChunkStreaming{} var _ Chunk = &ChunkVideo{} +var _ Chunk = &ChunkImageRawContent{} diff --git a/proxy-router/internal/config/config.go b/proxy-router/internal/config/config.go index 34074314..66bdc89b 100644 --- a/proxy-router/internal/config/config.go +++ b/proxy-router/internal/config/config.go @@ -42,17 +42,14 @@ type Config struct { WalletPrivateKey *lib.HexString `env:"WALLET_PRIVATE_KEY" flag:"wallet-private-key" desc:"if set, will use this private key to sign transactions, otherwise it will be retrieved from the system keychain"` } Log struct { - Color bool `env:"LOG_COLOR" flag:"log-color"` - FolderPath string `env:"LOG_FOLDER_PATH" flag:"log-folder-path" validate:"omitempty,dirpath" desc:"enables file logging and sets the folder path"` - IsProd bool `env:"LOG_IS_PROD" flag:"log-is-prod" validate:"" desc:"affects the format of the log output"` - JSON bool `env:"LOG_JSON" flag:"log-json"` - LevelApp string `env:"LOG_LEVEL_APP" flag:"log-level-app" validate:"omitempty,oneof=debug info warn error dpanic panic fatal"` - LevelConnection string `env:"LOG_LEVEL_CONNECTION" flag:"log-level-connection" validate:"omitempty,oneof=debug info warn error dpanic panic fatal"` - LevelProxy string `env:"LOG_LEVEL_PROXY" flag:"log-level-proxy" validate:"omitempty,oneof=debug info warn error dpanic panic fatal"` - LevelScheduler string `env:"LOG_LEVEL_SCHEDULER" flag:"log-level-scheduler" validate:"omitempty,oneof=debug info warn error dpanic panic fatal"` - LevelContract string `env:"LOG_LEVEL_CONTRACT" flag:"log-level-contract" validate:"omitempty,oneof=debug info warn error dpanic panic fatal"` - LevelRPC string `env:"LOG_LEVEL_RPC" flag:"log-level-rpc" validate:"omitempty,oneof=debug info warn error dpanic panic fatal"` - LevelBadger string `env:"LOG_LEVEL_BADGER" flag:"log-level-badger" validate:"omitempty,oneof=debug info warn error dpanic panic fatal"` + Color bool `env:"LOG_COLOR" flag:"log-color"` + FolderPath string `env:"LOG_FOLDER_PATH" flag:"log-folder-path" validate:"omitempty,dirpath" desc:"enables file logging and sets the folder path"` + IsProd bool `env:"LOG_IS_PROD" flag:"log-is-prod" validate:"" desc:"affects the format of the log output"` + JSON bool `env:"LOG_JSON" flag:"log-json"` + LevelApp string `env:"LOG_LEVEL_APP" flag:"log-level-app" validate:"omitempty,oneof=debug info warn error dpanic panic fatal"` + LevelTCP string `env:"LOG_LEVEL_TCP" flag:"log-level-tcp" validate:"omitempty,oneof=debug info warn error dpanic panic fatal"` + LevelEthRPC string `env:"LOG_LEVEL_ETH_RPC" flag:"log-level-eth-rpc" validate:"omitempty,oneof=debug info warn error dpanic panic fatal"` + LevelStorage string `env:"LOG_LEVEL_STORAGE" flag:"log-level-storage" validate:"omitempty,oneof=debug info warn error dpanic panic fatal"` } Proxy struct { Address string `env:"PROXY_ADDRESS" flag:"proxy-address" validate:"required,hostname_port"` @@ -101,26 +98,17 @@ func (cfg *Config) SetDefaults() { // Log - if cfg.Log.LevelConnection == "" { - cfg.Log.LevelConnection = "info" - } - if cfg.Log.LevelProxy == "" { - cfg.Log.LevelProxy = "info" - } - if cfg.Log.LevelScheduler == "" { - cfg.Log.LevelScheduler = "info" - } - if cfg.Log.LevelContract == "" { - cfg.Log.LevelContract = "debug" + if cfg.Log.LevelTCP == "" { + cfg.Log.LevelTCP = "info" } if cfg.Log.LevelApp == "" { cfg.Log.LevelApp = "debug" } - if cfg.Log.LevelRPC == "" { - cfg.Log.LevelRPC = "info" + if cfg.Log.LevelEthRPC == "" { + cfg.Log.LevelEthRPC = "info" } - if cfg.Log.LevelBadger == "" { - cfg.Log.LevelBadger = "info" + if cfg.Log.LevelStorage == "" { + cfg.Log.LevelStorage = "info" } // System @@ -204,10 +192,8 @@ func (cfg *Config) GetSanitized() interface{} { publicCfg.Log.IsProd = cfg.Log.IsProd publicCfg.Log.JSON = cfg.Log.JSON publicCfg.Log.LevelApp = cfg.Log.LevelApp - publicCfg.Log.LevelConnection = cfg.Log.LevelConnection - publicCfg.Log.LevelProxy = cfg.Log.LevelProxy - publicCfg.Log.LevelScheduler = cfg.Log.LevelScheduler - publicCfg.Log.LevelRPC = cfg.Log.LevelRPC + publicCfg.Log.LevelTCP = cfg.Log.LevelTCP + publicCfg.Log.LevelEthRPC = cfg.Log.LevelEthRPC publicCfg.Proxy.Address = cfg.Proxy.Address publicCfg.Proxy.ModelsConfigPath = cfg.Proxy.ModelsConfigPath diff --git a/proxy-router/internal/config/models-config-schema.json b/proxy-router/internal/config/models-config-schema.json index 14561ad6..37241b88 100644 --- a/proxy-router/internal/config/models-config-schema.json +++ b/proxy-router/internal/config/models-config-schema.json @@ -24,7 +24,7 @@ "title": "API Type", "description": "Defines the type of API to be used with this model", "type": "string", - "enum": ["openai", "prodia-sd", "prodia-sdxl", "prodia-v2"] + "enum": ["openai", "prodia-v2", "hyperbolic-sd", "claudeai"] }, "apiUrl": { "title": "API URL", @@ -39,6 +39,12 @@ "type": "string", "minLength": 1 }, + "parameters": { + "title": "Configuration parameters for model", + "description": "Optional parameters that can be passed to model", + "type": "object", + "additionalProperties": { "type": "string" } + }, "concurrentSlots": { "title": "Concurrent Slots", "description": "The number of concurrent slots to be used with this model", diff --git a/proxy-router/internal/config/models_config.go b/proxy-router/internal/config/models_config.go index 420c1980..89c0e73e 100644 --- a/proxy-router/internal/config/models_config.go +++ b/proxy-router/internal/config/models_config.go @@ -38,12 +38,13 @@ type ModelConfigLoader struct { } type ModelConfig struct { - ModelName string `json:"modelName" validate:"required"` - ApiType string `json:"apiType" validate:"required"` - ApiURL string `json:"apiUrl" validate:"required,url"` - ApiKey string `json:"apiKey"` - ConcurrentSlots int `json:"concurrentSlots" validate:"number"` - CapacityPolicy string `json:"capacityPolicy"` + ModelName string `json:"modelName" validate:"required"` + ApiType string `json:"apiType" validate:"required"` + ApiURL string `json:"apiUrl" validate:"required,url"` + ApiKey string `json:"apiKey"` + ConcurrentSlots int `json:"concurrentSlots" validate:"number"` + CapacityPolicy string `json:"capacityPolicy"` + Parameters map[string]string `json:"parameters"` } type ModelConfigs map[string]ModelConfig @@ -125,7 +126,7 @@ func (e *ModelConfigLoader) ModelConfigFromID(ID string) *ModelConfig { } modelConfig := e.modelConfigs[ID] - if modelConfig == (ModelConfig{}) { + if modelConfig.ModelName == "" { e.log.Warnf("model config not found for ID: %s", ID) return &ModelConfig{} } diff --git a/proxy-router/internal/handlers/httphandlers/http.go b/proxy-router/internal/handlers/httphandlers/http.go index 9c763525..128956d1 100644 --- a/proxy-router/internal/handlers/httphandlers/http.go +++ b/proxy-router/internal/handlers/httphandlers/http.go @@ -51,14 +51,13 @@ func CreateHTTPServer(log lib.ILogger, controllers ...Registrable) *gin.Engine { gin.SetMode(gin.ReleaseMode) r := gin.New() + r.Use(RequestLogger(log)) r.Use(cors.New(cors.Config{ AllowOrigins: []string{"*"}, AllowHeaders: []string{"session_id", "model_id", "chat_id"}, })) - // r.Use(RequestLogger(log)) - r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) // r.Any("/debug/pprof/*action", gin.WrapF(pprof.Index)) diff --git a/proxy-router/internal/handlers/httphandlers/logging.go b/proxy-router/internal/handlers/httphandlers/logging.go index a6eb2f6b..650a9a0b 100644 --- a/proxy-router/internal/handlers/httphandlers/logging.go +++ b/proxy-router/internal/handlers/httphandlers/logging.go @@ -1,6 +1,8 @@ package httphandlers import ( + "time" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" "github.com/gin-gonic/gin" ) @@ -12,6 +14,12 @@ func RequestLogger(logger lib.ILogger) gin.HandlerFunc { path := c.Request.URL.Path raw := c.Request.URL.RawQuery + start := time.Now() + logger.Debugf("[HTTP-REQ] %s %s", + c.Request.Method, + path, + ) + // Process request c.Next() @@ -20,11 +28,13 @@ func RequestLogger(logger lib.ILogger) gin.HandlerFunc { } // Log details - logger.Infof("[REQ] %s %s [%d] \n [ERROR]: %s", + status := c.Writer.Status() + latency := time.Since(start).Round(time.Millisecond) + logger.Debugf("[HTTP-RES] %s %s [%d] %v", c.Request.Method, path, - c.Writer.Status(), - c.Errors.ByType(gin.ErrorTypePrivate).String(), + status, + latency, ) } } diff --git a/proxy-router/internal/handlers/tcphandlers/tcp.go b/proxy-router/internal/handlers/tcphandlers/tcp.go index 37cfeec4..92982ae8 100644 --- a/proxy-router/internal/handlers/tcphandlers/tcp.go +++ b/proxy-router/internal/handlers/tcphandlers/tcp.go @@ -13,13 +13,12 @@ import ( ) func NewTCPHandler( - log, connLog lib.ILogger, - schedulerLogFactory func(contractID string) (lib.ILogger, error), + tcpLog lib.ILogger, morRpcHandler *proxyapi.MORRPCController, ) transport.Handler { return func(ctx context.Context, conn net.Conn) { addr := conn.RemoteAddr().String() - sourceLog := connLog.Named("SRC").With("SrcAddr", addr) + sourceLog := tcpLog.Named("TCP").With("SrcAddr", addr) defer func() { sourceLog.Debugf("closing connection") @@ -33,6 +32,7 @@ func NewTCPHandler( } err = morRpcHandler.Handle(ctx, *msg, sourceLog, func(resp *morrpc.RpcResponse) error { + sourceLog.Debugf("sending TCP response for method: %s", msg.Method) _, err := sendMsg(conn, resp) if err != nil { sourceLog.Errorf("Error sending message: %s", err) diff --git a/proxy-router/internal/interfaces/rpc_multi.go b/proxy-router/internal/interfaces/rpc_multi.go index ce8fea32..d6ad8dc9 100644 --- a/proxy-router/internal/interfaces/rpc_multi.go +++ b/proxy-router/internal/interfaces/rpc_multi.go @@ -3,5 +3,6 @@ package interfaces type RPCEndpoints interface { GetURLs() []string SetURLs(urls []string) error + SetURLsNoPersist(urls []string) error RemoveURLs() error } diff --git a/proxy-router/internal/proxyapi/controller_morrpc.go b/proxy-router/internal/proxyapi/controller_morrpc.go index b695fc05..c2e947e6 100644 --- a/proxy-router/internal/proxyapi/controller_morrpc.go +++ b/proxy-router/internal/proxyapi/controller_morrpc.go @@ -43,6 +43,7 @@ func NewMORRPCController(service *ProxyReceiver, validator *validator.Validate, } func (s *MORRPCController) Handle(ctx context.Context, msg m.RPCMessage, sourceLog lib.ILogger, sendResponse SendResponse) error { + sourceLog.Debugf("received TCP message with method %s", msg.Method) switch msg.Method { case "network.ping": return s.networkPing(ctx, msg, sendResponse, sourceLog) diff --git a/proxy-router/internal/proxyapi/proxy_sender.go b/proxy-router/internal/proxyapi/proxy_sender.go index b6040c7e..876c55bc 100644 --- a/proxy-router/internal/proxyapi/proxy_sender.go +++ b/proxy-router/internal/proxyapi/proxy_sender.go @@ -603,7 +603,15 @@ func (p *ProxyServiceSender) rpcRequestStreamV2( responses = append(responses, videoGenerationResult) chunk = gcs.NewChunkVideo(&videoGenerationResult) } else { - return nil, ttftMs, totalTokens, lib.WrapError(ErrInvalidResponse, err) + var imageGenerationResult gcs.ImageRawContentResult + err = json.Unmarshal(aiResponse, &imageGenerationResult) + if err == nil && imageGenerationResult.ImageRawContent != "" { + totalTokens += 1 + responses = append(responses, imageGenerationResult) + chunk = gcs.NewChunkImageRawContent(&imageGenerationResult) + } else { + return nil, ttftMs, totalTokens, lib.WrapError(ErrInvalidResponse, err) + } } } } diff --git a/proxy-router/internal/proxyctl/proxyctl.go b/proxy-router/internal/proxyctl/proxyctl.go index 93cc6743..977e7223 100644 --- a/proxy-router/internal/proxyctl/proxyctl.go +++ b/proxy-router/internal/proxyctl/proxyctl.go @@ -56,8 +56,6 @@ func (s ProxyState) String() string { return "unknown" } -type SchedulerLogFactory = func(remoteAddr string) (lib.ILogger, error) - // Proxy is a struct that represents a proxy-router part of the system type Proxy struct { eventListener *blockchainapi.EventsListener @@ -66,9 +64,8 @@ type Proxy struct { chainID *big.Int sessionStorage *storages.SessionStorage sessionRepo *sessionrepo.SessionRepositoryCached - log *lib.Logger - connLog *lib.Logger - schedulerLogFactory SchedulerLogFactory + log lib.ILogger + tcpLog *lib.Logger aiEngine *aiengine.AiEngine validator *validator.Validate modelConfigLoader *config.ModelConfigLoader @@ -81,15 +78,14 @@ type Proxy struct { } // NewProxyCtl creates a new Proxy controller instance -func NewProxyCtl(eventListerer *blockchainapi.EventsListener, wallet interfaces.PrKeyProvider, chainID *big.Int, log *lib.Logger, connLog *lib.Logger, proxyAddr string, scl SchedulerLogFactory, sessionStorage *storages.SessionStorage, modelConfigLoader *config.ModelConfigLoader, valid *validator.Validate, aiEngine *aiengine.AiEngine, blockchainService *blockchainapi.BlockchainService, sessionRepo *sessionrepo.SessionRepositoryCached, sessionExpiryHandler *blockchainapi.SessionExpiryHandler) *Proxy { +func NewProxyCtl(eventListerer *blockchainapi.EventsListener, wallet interfaces.PrKeyProvider, chainID *big.Int, log lib.ILogger, tcpLog *lib.Logger, proxyAddr string, sessionStorage *storages.SessionStorage, modelConfigLoader *config.ModelConfigLoader, valid *validator.Validate, aiEngine *aiengine.AiEngine, blockchainService *blockchainapi.BlockchainService, sessionRepo *sessionrepo.SessionRepositoryCached, sessionExpiryHandler *blockchainapi.SessionExpiryHandler) *Proxy { return &Proxy{ eventListener: eventListerer, chainID: chainID, wallet: wallet, log: log, - connLog: connLog, + tcpLog: tcpLog, proxyAddr: proxyAddr, - schedulerLogFactory: scl, sessionStorage: sessionStorage, aiEngine: aiEngine, validator: valid, @@ -149,7 +145,7 @@ func (p *Proxy) Run(ctx context.Context) error { } func (p *Proxy) run(ctx context.Context, prKey lib.HexString) error { - tcpServer := transport.NewTCPServer(p.proxyAddr, p.connLog.Named("TCP")) + tcpServer := transport.NewTCPServer(p.proxyAddr, p.tcpLog.Named("TCP")) prKey, err := p.wallet.GetPrivateKey() if err != nil { return err @@ -193,7 +189,7 @@ func (p *Proxy) run(ctx context.Context, prKey lib.HexString) error { proxyReceiver := proxyapi.NewProxyReceiver(prKey, pubKey, p.sessionStorage, p.aiEngine, p.chainID, p.modelConfigLoader, p.blockchainService, p.sessionRepo) morTcpHandler := proxyapi.NewMORRPCController(proxyReceiver, p.validator, p.sessionRepo, p.sessionStorage, prKey) tcpHandler := tcphandlers.NewTCPHandler( - p.log, p.connLog, p.schedulerLogFactory, morTcpHandler, + p.tcpLog, morTcpHandler, ) tcpServer.SetConnectionHandler(tcpHandler) diff --git a/proxy-router/internal/repositories/ethclient/rpcclient_store_env.go b/proxy-router/internal/repositories/ethclient/rpcclient_store_env.go index 894b7ff2..58c7daba 100644 --- a/proxy-router/internal/repositories/ethclient/rpcclient_store_env.go +++ b/proxy-router/internal/repositories/ethclient/rpcclient_store_env.go @@ -18,6 +18,10 @@ func (p *RPCClientStoreEnv) SetURLs(urls []string) error { return ErrEnvRPCSet } +func (p *RPCClientStoreEnv) SetURLsNoPersist(urls []string) error { + return ErrEnvRPCSet +} + func (p *RPCClientStoreEnv) RemoveURLs() error { return ErrEnvRPCSet } diff --git a/proxy-router/internal/repositories/ethclient/rpcclient_store_factory.go b/proxy-router/internal/repositories/ethclient/rpcclient_store_factory.go index 2a7b063d..2faf2ebe 100644 --- a/proxy-router/internal/repositories/ethclient/rpcclient_store_factory.go +++ b/proxy-router/internal/repositories/ethclient/rpcclient_store_factory.go @@ -11,6 +11,7 @@ import ( type RPCEndpointsPersister interface { GetURLs() []string SetURLs(urls []string) error + SetURLsNoPersist(urls []string) error RemoveURLs() error GetClient() RPCClient } diff --git a/proxy-router/internal/repositories/ethclient/rpcclient_store_keychain.go b/proxy-router/internal/repositories/ethclient/rpcclient_store_keychain.go index 3112d772..79e20823 100644 --- a/proxy-router/internal/repositories/ethclient/rpcclient_store_keychain.go +++ b/proxy-router/internal/repositories/ethclient/rpcclient_store_keychain.go @@ -38,6 +38,10 @@ func (p *RPCClientStoreKeychain) SetURLs(urls []string) error { return p.rpcClient.SetURLs(urls) } +func (p *RPCClientStoreKeychain) SetURLsNoPersist(urls []string) error { + return p.rpcClient.SetURLs(urls) +} + func (p *RPCClientStoreKeychain) RemoveURLs() error { return p.deleteURLsInStorage() } @@ -47,7 +51,6 @@ func (p *RPCClientStoreKeychain) GetClient() RPCClient { } func (p *RPCClientStoreKeychain) loadURLsFromStorage() ([]string, error) { - // return []string{"https://arb-sepolia.g.alchemy.com/v2/3-pxwBaJ7vilkz1jl-fMmCvZThGxpmo2"}, nil str, err := p.storage.Get(ETH_NODE_URL_KEY) if err != nil { return nil, err diff --git a/proxy-router/internal/storages/logger.go b/proxy-router/internal/storages/logger.go index e4b82590..902668d6 100644 --- a/proxy-router/internal/storages/logger.go +++ b/proxy-router/internal/storages/logger.go @@ -6,26 +6,26 @@ import ( "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" ) -type BadgerLogger struct { +type StorageLogger struct { log lib.ILogger } -func NewBadgerLogger(log lib.ILogger) *BadgerLogger { - return &BadgerLogger{ - log: log.Named("BADGER"), +func NewStorageLogger(log lib.ILogger) *StorageLogger { + return &StorageLogger{ + log: log.Named("Storage"), } } -func (l *BadgerLogger) Errorf(s string, p ...interface{}) { +func (l *StorageLogger) Errorf(s string, p ...interface{}) { l.log.Errorf(normalize(s), p...) } -func (l *BadgerLogger) Warningf(s string, p ...interface{}) { +func (l *StorageLogger) Warningf(s string, p ...interface{}) { l.log.Warnf(normalize(s), p...) } -func (l *BadgerLogger) Infof(s string, p ...interface{}) { +func (l *StorageLogger) Infof(s string, p ...interface{}) { l.log.Infof(normalize(s), p...) } -func (l *BadgerLogger) Debugf(s string, p ...interface{}) { +func (l *StorageLogger) Debugf(s string, p ...interface{}) { l.log.Debugf(normalize(s), p...) } diff --git a/proxy-router/internal/storages/storage.go b/proxy-router/internal/storages/storage.go index bdc5ed78..8b0e4271 100644 --- a/proxy-router/internal/storages/storage.go +++ b/proxy-router/internal/storages/storage.go @@ -12,12 +12,12 @@ type Storage struct { } func NewStorage(log lib.ILogger, path string) *Storage { - badgerLogger := NewBadgerLogger(log) + storageLogger := NewStorageLogger(log) if err := os.Mkdir(path, os.ModePerm); err != nil { - badgerLogger.Debugf("%s", err) + storageLogger.Debugf("%s", err) } opts := badger.DefaultOptions(path) - opts.Logger = badgerLogger + opts.Logger = storageLogger db, err := badger.Open(opts) if err != nil { diff --git a/proxy-router/internal/system/controller.go b/proxy-router/internal/system/controller.go index 833bda71..6a161d18 100644 --- a/proxy-router/internal/system/controller.go +++ b/proxy-router/internal/system/controller.go @@ -12,6 +12,7 @@ import ( "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/config" i "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/interfaces" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/repositories/ethclient" "github.com/gin-gonic/gin" ) @@ -47,6 +48,7 @@ func (s *SystemController) RegisterRoutes(r i.Router) { r.GET("/files", s.GetFiles) r.POST("/config/ethNode", s.SetEthNode) + r.DELETE("/config/ethNode", s.RemoveEthNode) } // HealthCheck godoc @@ -142,7 +144,7 @@ func (s *SystemController) GetFiles(ctx *gin.Context) { // @Accept json // @Produce json // @Param urls body SetEthNodeURLReq true "URLs" -// @Success 200 {object} ConfigResponse +// @Success 200 {object} StatusRes // @Router /config/ethNode [post] func (s *SystemController) SetEthNode(ctx *gin.Context) { var req SetEthNodeURLReq @@ -154,7 +156,7 @@ func (s *SystemController) SetEthNode(ctx *gin.Context) { for _, url := range req.URLs { validationErr := s.ethConnectionValidator.ValidateEthResourse(ctx, url, time.Second*2) if validationErr != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Resource %s is not available", url)}) + ctx.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Resource %s is not available. %s", url, validationErr)}) return } } @@ -165,7 +167,7 @@ func (s *SystemController) SetEthNode(ctx *gin.Context) { return } - ctx.JSON(http.StatusOK, gin.H{"status": "ok"}) + ctx.JSON(http.StatusOK, OkRes()) } // DeleteEthNode godoc @@ -174,7 +176,7 @@ func (s *SystemController) SetEthNode(ctx *gin.Context) { // @Description Delete the Eth Node URLs // @Tags system // @Produce json -// @Success 200 {object} ConfigResponse +// @Success 200 {object} StatusRes // @Router /config/ethNode [delete] func (c *SystemController) RemoveEthNode(ctx *gin.Context) { err := c.ethRPC.RemoveURLs() @@ -183,7 +185,19 @@ func (c *SystemController) RemoveEthNode(ctx *gin.Context) { return } - ctx.JSON(http.StatusOK, gin.H{"status": "ok"}) + urls, err := ethclient.GetPublicRPCURLs(int(c.chainID.Int64())) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + err = c.ethRPC.SetURLsNoPersist(urls) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + ctx.JSON(http.StatusOK, OkRes()) } func writeFiles(writer io.Writer, files []FD) error { diff --git a/proxy-router/internal/system/structs.go b/proxy-router/internal/system/structs.go index 1a495da1..c8972b7c 100644 --- a/proxy-router/internal/system/structs.go +++ b/proxy-router/internal/system/structs.go @@ -21,3 +21,11 @@ type HealthCheckResponse struct { Version string Uptime string } + +type StatusRes struct { + Status string `json:"status"` +} + +func OkRes() StatusRes { + return StatusRes{Status: "ok"} +} diff --git a/proxy-router/models-config.json.example b/proxy-router/models-config.json.example index e35e18e9..a68b24d2 100644 --- a/proxy-router/models-config.json.example +++ b/proxy-router/models-config.json.example @@ -9,10 +9,31 @@ }, { "modelId": "0x0000000000000000000000000000000000000000000000000000000000000001", - "modelName": "v1-5-pruned-emaonly.safetensors [d7049739]", - "apiType": "prodia-sd", - "apiUrl": "https://api.prodia.com/v1", + "modelName": "inference.sdxl.txt2img.v1", + "apiType": "prodia-v2", + "apiUrl": "https://inference.prodia.com/v2", "apiKey": "FILL_ME_IN" - } + }, + { + "modelId": "0x0000000000000000000000000000000000000000000000000000000000000002", + "modelName": "SDXL1.0-base", + "apiType": "hyperbolic-sd", + "apiUrl": "https://api.hyperbolic.xyz/v1", + "apiKey": "Authentication Token" + }, + { + "modelId": "0x0000000000000000000000000000000000000000000000000000000000000003", + "modelName": "claude-3-5-sonnet-20241022", + "apiType": "claudeai", + "apiUrl": "https://api.anthropic.com/v1", + "apiKey": "FILL_ME_IN" + }, + { + "modelId": "0x0000000000000000000000000000000000000000000000000000000000000004", + "modelName": "inference.sd15.txt2img.v1", + "apiType": "prodia-v2", + "apiUrl": "https://inference.prodia.com/v2", + "apiKey": "FILL_ME_IN" + }, ] } diff --git a/ui-desktop/package.json b/ui-desktop/package.json index c320316a..c2a98e01 100644 --- a/ui-desktop/package.json +++ b/ui-desktop/package.json @@ -58,7 +58,7 @@ "react-modern-drawer": "^1.2.2", "react-motion": "^0.5.2", "react-redux": "^9.1.0", - "react-router-dom": "4.3.1", + "react-router-dom": "^7.0.2", "react-select": "^5.8.0", "react-simple-image-viewer": "^1.2.2", "react-syntax-highlighter": "^15.5.0", diff --git a/ui-desktop/src/main/index.ts b/ui-desktop/src/main/index.ts index e544e360..14b350cf 100644 --- a/ui-desktop/src/main/index.ts +++ b/ui-desktop/src/main/index.ts @@ -49,10 +49,12 @@ function createWindow(): void { errorHandler({ logger: logger.error }) +const sleepBeforeStart = 3000; + // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. -app.whenReady().then(() => { +app.whenReady().then(() => new Promise(r => setTimeout(r, sleepBeforeStart))).then(() => { app.setName("morpheus"); // Set app user model id for windows electronApp.setAppUserModelId('com.electron') diff --git a/ui-desktop/src/main/src/client/apiGateway.js b/ui-desktop/src/main/src/client/apiGateway.js index dda44599..9bacb57d 100644 --- a/ui-desktop/src/main/src/client/apiGateway.js +++ b/ui-desktop/src/main/src/client/apiGateway.js @@ -245,6 +245,32 @@ const checkProviderConnectivity = async ({ address, endpoint}) => { } } +const clearEthNodeEnv = async () => { + try { + const path = `${config.chain.localProxyRouterUrl}/config/ethNode` + const response = await fetch(path, { method: "DELETE" }); + const data = await response.json(); + return data.status; + } + catch (e) { + console.log("CLEAR ETH NODE ERROR", e) + return false; + } +} + +const clearWallet = async () => { + try { + const path = `${config.chain.localProxyRouterUrl}/wallet` + const response = await fetch(path, { method: "DELETE" }); + const data = await response.json(); + return data.status; + } + catch (e) { + console.log("CLEAR WALLET ERROR", e) + return false; + } +} + export default { getAllModels, getBalances, @@ -258,5 +284,7 @@ export default { getChatHistory, updateChatHistoryTitle, deleteChatHistory, - checkProviderConnectivity + checkProviderConnectivity, + clearWallet, + clearEthNodeEnv } \ No newline at end of file diff --git a/ui-desktop/src/main/src/client/handlers/single-core.js b/ui-desktop/src/main/src/client/handlers/single-core.js index 934cae6e..5f356e20 100644 --- a/ui-desktop/src/main/src/client/handlers/single-core.js +++ b/ui-desktop/src/main/src/client/handlers/single-core.js @@ -7,6 +7,7 @@ import wallet from '../wallet' import noCore from './no-core' import WalletError from '../WalletError' import { setProxyRouterConfig, cleanupDb, getProxyRouterConfig } from '../settings' +import httpClient from '../apiGateway' export const withAuth = (fn) => @@ -317,7 +318,13 @@ export const refreshProxyRouterConnection = async (data, { api }) => export const getLocalIp = async ({ }, { api }) => api['proxy-router'].getLocalIp() export const logout = async (data) => { - return cleanupDb() + console.log("start cleaning local database and settings...") + cleanupDb() + console.log("start cleaning wallet...") + await httpClient.clearWallet(); + console.log("start cleaning eth node...") + await httpClient.clearEthNodeEnv(); + return; } export const getPoolAddress = async (data) => { diff --git a/ui-desktop/src/renderer/src/assets/logo.svg b/ui-desktop/src/renderer/src/assets/logo.svg index 71f3ca24..4180a9e0 100644 --- a/ui-desktop/src/renderer/src/assets/logo.svg +++ b/ui-desktop/src/renderer/src/assets/logo.svg @@ -1,5 +1,5 @@