diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3c88fa4a..78dc813f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,6 +61,7 @@ jobs: cd launcher make cd ../proxy-router + cp ./models-config.json.example ../ui-desktop/models-config.json make build cd ../ui-desktop cp ./.env.example .env @@ -83,7 +84,8 @@ jobs: unzip -o -j $LLAMACPP build/bin/llama-server echo '{"run":["./llama-server -m ./'$MODEL'","./proxy-router","./ui-desktop-1.0.0.AppImage"]}' > mor-launch.json cp ./proxy-router/.env.example .env - zip -j $ARTIFACT ./LICENSE ./launcher/mor-launch llama-server ./proxy-router/bin/proxy-router .env $MODEL mor-launch.json ./ui-desktop/dist/ui-desktop-1.0.0.AppImage + cp ./proxy-router/models-config.json.example models-config.json + zip -j $ARTIFACT ./LICENSE ./launcher/mor-launch llama-server ./proxy-router/bin/proxy-router .env $MODEL mor-launch.json ./ui-desktop/dist/ui-desktop-1.0.0.AppImage models-config.json - name: Upload artifacts uses: actions/upload-artifact@v4 @@ -128,6 +130,7 @@ jobs: cd launcher make cd ../proxy-router + cp ./models-config.json.example ../ui-desktop/models-config.json make build cd ../ui-desktop cp ./.env.example .env @@ -150,8 +153,9 @@ jobs: unzip -o -j $LLAMACPP build/bin/llama-server echo '{"run":["./llama-server -m ./'$MODEL'","./proxy-router","./ui-desktop.app/Contents/MacOS/ui-desktop"]}' > mor-launch.json cp ./proxy-router/.env.example .env + cp ./proxy-router/models-config.json.example models-config.json unzip ./ui-desktop/dist/ui-desktop-1.0.0-mac.zip - zip -j $ARTIFACT ./LICENSE ./launcher/mor-launch ./proxy-router/bin/proxy-router .env llama-server $MODEL mor-launch.json + zip -j $ARTIFACT ./LICENSE ./launcher/mor-launch ./proxy-router/bin/proxy-router .env llama-server $MODEL mor-launch.json models-config.json zip -r $ARTIFACT ui-desktop.app - name: Upload artifacts @@ -197,6 +201,7 @@ jobs: cd launcher make cd ../proxy-router + cp ./models-config.json.example ../ui-desktop/models-config.json make build cd ../ui-desktop cp ./.env.example .env @@ -219,8 +224,9 @@ jobs: unzip -o -j $LLAMACPP build/bin/llama-server echo '{"run":["./llama-server -m ./'$MODEL'","./proxy-router","./ui-desktop.app/Contents/MacOS/ui-desktop"]}' > mor-launch.json cp ./proxy-router/.env.example .env + cp ./proxy-router/models-config.json.example models-config.json unzip ./ui-desktop/dist/ui-desktop-1.0.0-arm64-mac.zip - zip -j $ARTIFACT ./LICENSE ./launcher/mor-launch ./proxy-router/bin/proxy-router .env llama-server $MODEL mor-launch.json + zip -j $ARTIFACT ./LICENSE ./launcher/mor-launch ./proxy-router/bin/proxy-router .env llama-server $MODEL mor-launch.json models-config.json zip -r $ARTIFACT ui-desktop.app - name: Upload artifacts @@ -270,6 +276,7 @@ jobs: cd launcher make cd ../proxy-router + cp ./models-config.json.example ../ui-desktop/models-config.json make build cd ../ui-desktop cp ./.env.example .env @@ -291,11 +298,12 @@ jobs: 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","./ui-desktop-1.0.0.exe"]}' > mor-launch.json - cp ./proxy-router/.env.example .env + cp ./proxy-router/.env.example.win .env + cp ./proxy-router/models-config.json.example models-config.json mv ./proxy-router/bin/proxy-router proxy-router.exe mv ./launcher/mor-launch mor-launch.exe mv "./ui-desktop/dist/ui-desktop 1.0.0.exe" ui-desktop-1.0.0.exe - 7z a $ARTIFACT LICENSE mor-launch.exe proxy-router.exe .env llama-server.exe llama.dll ggml.dll $MODEL mor-launch.json ui-desktop-1.0.0.exe + 7z a $ARTIFACT LICENSE mor-launch.exe proxy-router.exe .env llama-server.exe llama.dll ggml.dll $MODEL mor-launch.json ui-desktop-1.0.0.exe models-config.json - name: Upload artifacts uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index ac5103da..c2d55c8c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ */**/node_modules -***/.DS_Store \ No newline at end of file +***/.DS_Store +***/.scratch +***/.env +***/.$*drawio.bkp +***/.$*drawio.dtmp \ No newline at end of file diff --git a/docs/00-overview.md b/docs/00-overview.md new file mode 100644 index 00000000..80f8dc42 --- /dev/null +++ b/docs/00-overview.md @@ -0,0 +1,62 @@ +# Overview of the Morpheus-Lumerin Environment + +![Architecture-Overview](images/overview.png) + +This document is intended to provide a high level overview of the major architectural components between model compute-providers and consumers in the Morpheus-Lumerin environment. + +The ultimate goal is to show how configuration of the compute-provider environment and the consumer nodes can enable prompts and inference from the consumer to the hosted models by the provider. The key enablers being the Arbitrum blockchain, Morpheus token for staking and bidding (transactions to pay for use) and the Lumerin proxy-router to anonymously route traffic based on smart contract governance. + +In other words, referring to the overview model...how do we get to conversation **6** where prompts and inference are happening? + +Numbers below reference the circled elements in the diagram above. + +## 0. Existing Foundation Elements +- [Readme](../README.md) - for more details +- Arbitrum Ethereum Layer 2 blockchain +- Morpheus Token (MOR) for staking and bidding +- Lumerin Smart Contract for governance and routing + +## 1. Provider AI Model +- [01-model-setup.md](01-model-setup.md) - for more details +- Existing, Hosted AI model that is available for inference +- In the real world, this is assumed to be a high-horsepower server or server farm tuned for large language models and available via standard OpenAI API interface on a privately accessed endpoint (IP address:port or DNS name:port) eg: `http://mycoolaimodel.serverfarm.io:8080` +- In the packaged software releases, llama.cpp (llama-server) example is included to run on the same machine as the other components to show how the components work together. It is not a real-world model and is not tuned for performance. + +## 2. Provider Proxy-Router +- [02-provider-setup.md](02-provider-setup.md) - for more details +- The proxy-router is the core "router" that talks to and listens to the blockchain, routes prompts and inference between the providers hosted models via bids and the consumers that purchase and use the model +- In a real-world scenario, this proxy-router would be a separate, small server or even docker container that is not part of the AI Model Server Instance (it can be, but it's nice to separate the architecture either for anonymity or performance) +- Installation on the provider side is as simple as setting up the environment variables and running the proxy-router software. + - There is a sample `.env.example` file located within the ./proxy-router folder that shoudld be copied to `.env` and edited with the appropriate values. + - Please see [proxy-router .ENV Variables](#proxy-router-env-variables) below for more information on the key values needed in the .env file +- The proxy-router needs to be on both the provider and consumer environment and have access to an Arbitrum Ethereum node via web sockets (WSS) for listening to and posting elements on the blockchain + +## 3. Provider - setup Provider, Model and Bid on the blockchain +- [03-provider-offer.md](03-provider-offer.md) - for more details +- Once the proxy-router is setup, and the provider's wallet has the proper amount of ETH and MOR, use the Swagger API Interface (http://yourlocalproxy:8082/swagger/index.html as example) to do the following: + 1. Authorize the diamond contract to spend on your wallet's behalf + 1. Register your provider (the proxy-router) on the blockchain (http://mycoolproxy.serverfarm.io:3333) + 1. Register your model on the blockchain + 1. Create a bid for your model on the blockchain +- Further details on how to do this are in the [Provider Offer Guide](provider-offer.md) + +## 4. Consumer Node Setup +- [04-consumer-setup.md](04-consumer-setup.md) - for more details +- [04a-consumer-setup-source.md](04a-consumer-setup-source.md) - for more details on setting up from gtihub source +- The consumer node is the "client" that will be purchasing bids from the blockchain, sending prompts via the proxy-router and receiving inference back from the provider's model' +- The components are very similar to the Provider side of things with the exception that the consumer node will typically not be hosting a model, but will be sending prompts to the proxy-router and receiving inference back +- In this case, the easiest way to install is to use the packaged releases for your platform on Github and follow the instructions in the README.md file +- These packages include 3 different pieces of software + - llama.cpp (llama-server) - a simple example model that can be run on the same machine as the proxy-router and ui-desktop to show how the components work together and run local (free) inference + - proxy-router - the same software as the provider side, but with different environment variables and a different role + - ui-desktop - Electron GUI that enables the user to interact with the models (via the API) to browse offered bids, purchase and send prompts +- The consumer node will need to have the proxy-router running and the UI-Desktop running to interact with the models and bids on the blockchain + +## 5. Purchase Bid +- [05-purchase-bid.md](05-purchase-bid.md) - for more details +- Once the UI-Desktop is up and running, the consumer can browse the available bids on the blockchain +- Select a bid and stake the intended MOR amount (minimum should be shown) + +## 6. Prompt & Inference +- [06-model-interaction.md](06-model-interaction.md) - for more details +- Once the bid is purchased, the consumer can send prompts to the proxy-router via the UI-Desktop \ No newline at end of file diff --git a/docs/01-model-setup.md b/docs/01-model-setup.md new file mode 100644 index 00000000..794e067d --- /dev/null +++ b/docs/01-model-setup.md @@ -0,0 +1,5 @@ +## TODO + +- The intent of this document is to outline how to run llama.cpp on your local machine and could include details on AWS / EC2 Build recommendations for compute-providers +- The end state should be presentation of a proxy-router accessible private endpoint that the proxy-router can talk to to serve its models +------------ \ No newline at end of file diff --git a/docs/02-provider-setup.md b/docs/02-provider-setup.md new file mode 100644 index 00000000..d2d1a9b5 --- /dev/null +++ b/docs/02-provider-setup.md @@ -0,0 +1,97 @@ + +# Provider Hosting (Local LLM to offer, Proxy-Router running as background/service): + +## Assumptions: +* Your AI model has been configured, started and made available to the proxy-router server via a private endpoint (IP:PORT or DNS:PORT) eg: `http://mycoolaimodel.domain.com:8080` + * Optional + * You can use the provided `llama.cpp` and `tinyllama` model to test locally + * If your local model is listening on a different port locally, you will need to modify the `OPENAI_BASE_URL` in the .env file to match the correct port +* You have an existing funded wallet with saMOR and saETH and also have the `private key` for the wallet (this will be needed for the .env file configuration) +* You have created an Alchemy or Infura free account and have a private API key for the Arbitrum Sepolia testnet (wss://arb-sepolia.g.alchemy.com/v2/) +* Your proxy-router must have a publicly accessible endpoint for the provider (ip:port or fqdn:port no protocol) eg: `mycoolmornode.domain.com:3333` - this will be used when creating the provider on the blockchain + +## Installation & Configuration Steps: +1. Download latest release for your operating system: https://github.com/Lumerin-protocol/Morpheus-Lumerin-Node/releases + +1. Extract the zip to a local folder (examples) + * Windows: `(%USERPROFILE%)/Downloads/morpheus)` + * Linux & MacOS: `~/Downloads/morpheus` + * On MacOS you may need to execute `xattr -c proxy-router` in a command window to remove the quarantine flag on MacOS + +1. Edit the `.env` file following the guide below [proxy-router .ENV Variables](#proxy-router-env-variables) + +1. **(OPTIONAL)** - External Provider or Pass through + * In some cases you will want to leverage external or existing AI Providers in the network via their own, private API + * Dependencies: + * `model-config.json` file in the proxy-router directory + * proxy-router .env file for proxy-router must also be updated to include `MODELS_CONFIG_PATH=/models-config.json` + * Once your provider is up and running, deploy a new model and model bid via the diamond contract (you will need the `model_ID` for the configuration) + * Edit the model-config.json to the following json format ... with + * The JSON ID will be the ModelID that you created above, modelName, apiTYpe, apiURL and apiKey are from the external provider and specific to their offered models + * Once the model-config.json file is updated, the morpheus node will need to be restarted to pick up the new configuration (not all models (eg: image generation can be utilized via the UI-Desktop, but API integration is possible) + * Example model-config.json file for external providers +``` +#model-config.json +{ + "0x4b5d6c2d3e4f5a6b7c8de7f89a0b19e07f4a6e1f2c3a3c28d9d5e6": { + "modelName": "v1-5-specialmodel.modelversion [externalmodel]", + "apiType": "provider_api_type", + "apiUrl": "https://api.externalmodel.com/v1/xyz/generate", + "apiKey": "api-key-from-external-provider" + }, + "0xb2c8a6b2c1d9ed7f0e9a3b4c2d6e5f14f9b8c3a7e5d6a1a0b9c7d8e4f30f4a7b": { + "modelName": "v1-7-specialmodel2.modelversion [externalmodel]", + "apiType": "provider_api_type", + "apiUrl": "https://api.externalmodel.com/v1/abc/generate", + "apiKey": "api-key-from-external-provider" + } +} +``` + +## Start the Proxy Router +1. On your server, launch the proxy-router with the modified .env file shown above + * Windows: Double click the `proxy-router.exe` (You will need to tell Windows Defender this is ok to run) + * Linux & MacOS: Open a terminal and navigate to the folder and run `./proxy-router`from the morpheus/proxy-router folder +1. This will start the proxy-router and begin monitoring the blockchain for events + +## Validating Steps: +1. Once the proxy-router is running, you can navigate to the Swagger API Interface (http://localhost:8082/swagger/index.html as example) to validate that the proxy-router is running and listening for blockchain events +1. You can also check the logs in the `./data` directory for any errors or issues that may have occurred during startup +1. Once validated, you can move on and create your provider, model and bid on the blockchain [03-provider-offer.md](03-provider-offer.md) + + +---------------- +### proxy-router .ENV Variables +Key Values in the .env file are (there are others, but these are primarly responsible for connecting to the blockchain, the provider AI model and listening for incoming traffic): +- `WALLET_PRIVATE_KEY=` + - Private Key from your wallet needed for the proxy-router to sign transactions and respond to provided prompts (this is why the proxy router must be secured and the API endpoint protected) +- `ETH_NODE_ADDRESS=wss://arb-sepolia.g.alchemy.com/v2/` + - Ethereum Node Address for the Arbitrum blockchain (via Alchemy or Infura) + - This websocket (wss) address is key for the proxy-router to listen and post to the blockchain + - We recommend using your own private ETH Node Address for better performance (free account setup via Alchemy or Infura) +- `DIAMOND_CONTRACT_ADDRESS=0x8e19288d908b2d9F8D7C539c74C899808AC3dE45` + - This is the key Lumerin Smart Contract (currently Sepolia Arbitrum testnet) + - This is the address of the smart contract that the proxy-router will interact with to post providers, models & bids + - This address will change as the smart-contract is updated and for mainnet contract interaction +- `MOR_TOKEN_ADDRESS=0xc1664f994fd3991f98ae944bc16b9aed673ef5fd` + - This is the Morpheus Token (saMOR) address for Sepolia Arbitrum testnet + - This address will be different for mainnet token +- `WEB_ADDRESS=0.0.0.0:8082` + - This is the local listenting port for your proxy-router API (Swagger) interface + - Based on your local needs, this may need to change (8082 is default) +- `WEB_PUBLIC_URL=localhost:8082` + - If you have or will be exposing your API interface to a local, PRIVATE (or VPN) network, you can change this to the DNS name or IP and port where the API will be available. The default is just on the local machine (localhost) + - The PORT must be the same as in the `WEB_ADDRESS` setting +- `OPENAI_BASE_URL=http://localhost:8080/v1` + - This is where the proxy-router should send OpenAI compatible requests to the provider model. + - By default (and included in the Morpheus-Lumerin software releases) this is set to `http://localhost:8080/v1` for the included llama.cpp model + - In a real-world scenario, this would be the IP address and port of the provider model server or server farm that is hosting the AI model separately from the proxy-router +- `PROXY_STORAGE_PATH=./data/` + - This is the path where the proxy-router will store logs and other data + - This path should be writable by the user running the proxy-router software +- `MODELS_CONFIG_PATH=` + - location of the models-config.json file that contains the models that the proxy-router will be providing. + - it has the capability to also (via private API) call external providers models (like Prodia) +- `PROXY_ADDRESS=0.0.0.0:3333` + - This is the local listening port for the proxy-router to receive prompts and inference requests from the consumer nodes + - This is the port that the consumer nodes will send prompts to and should be available publicly and via the provider definition setup on the blockchain \ No newline at end of file diff --git a/docs/03-provider-offer.md b/docs/03-provider-offer.md new file mode 100644 index 00000000..ee301d2b --- /dev/null +++ b/docs/03-provider-offer.md @@ -0,0 +1,46 @@ + +# Creating Provider, Model and Bid on the Blockchain: +**Diamond contract:** `0x8e19288d908b2d9F8D7C539c74C899808AC3dE45` + +**Needed information:** +* Provider/Owner: `0x9E26Fea97F7d644BAf62d0e20e4d4b8F836C166c` # Your ERC-20 Wallet with saMOR & saETH +* Endpoint: `server.domain.com:3333` # Internet publicly accessible server/node access point +* Model ID: `0xe1e6e3e77148d140065ef2cd4fba7f4ae59c90e1639184b6df5c84` # Random 32byte/hex that you generate +* ipfcCID: `0xc2d3a5e4f9b7c1a2c8f0b1d5e6c78492fa7bcd34e9a3b9c9e18f25d3be47a1f6` # Another 32byte/hex random for future use +* Model Name: `CapybaraHermes-v2.5-Mistral-7b` # Human Readable name for the model +* Bid Cost: `200000000000` (1*10^18 or ~7MOR) # What will the model cost per second to use + +## Steps + 1. WEB3/Arbiscan/Metamask: Authorize Diamond Contract to spend on the Provider's behalf + 1. https://sepolia.arbiscan.io/address/0xc1664f994fd3991f98ae944bc16b9aed673ef5fd#writeContract + 1. Connect to Web3 (connect Provider wallet) + 1. Click Approve + 1. Spender Address = Diamond Contract + 1. Authorized Amount = remember that this is in the form 1*10^18 so make sure there's enough MOR 1ranted to cover the contract fees + 1. The Diamond Contract is now authorized to spend MOR on provider's behalf + +1. Create Provider in the Diamond contract via swagger api: + 1. Start proxy-router + 1. http://localhost:8082/swagger/index.html#/providers/post_blockchain_providers + 1. Enter required fields: + 1. addStake = Amount of stake for provider to risk - Stake can be 0 now + 1. Endpoint = Your publicly accessible endpoint for the proxy-router provider (ip:port or fqdn:port no protocol) eg: `mycoolmornode.domain.com:3333` + +1. Create Model in the contract: + 1. Go to http://localhost:8082/swagger/index.html#/models/post_blockchain_models and enter + 1. modelId: random 32byte/hex that will uniquely identify model (uuid) + 1. ipfsCID: another random32byte/hex for future use (model library) + 1. Fee: fee for the model usage - 0 for now + 1. addStake: stake for model usage - 0 for now + 1. Owner: Provider Wallet Address + 1. name: Human Readable model like "Llama 2.0" or "Mistral 2.5" or "Collective Cognition 1.1" + 1. tags: array of tag strings for the model + 1. Capture the `modelID` from the JSON response + +1. Offer Model Bid in the contract: + 1. Navigate to http://localhost:8082/swagger/index.html#/bids/post_blockchain_bids and enter + 1. modelID: Model ID Created in last step: + 1. pricePerSecond: this is in 1*10^18 format so 100000000000 should make 5 minutes for the session 1round 37.5 saMOR + 1. Click Execute + +---------------- \ No newline at end of file diff --git a/docs/04-consumer-setup.md b/docs/04-consumer-setup.md new file mode 100644 index 00000000..ac4409db --- /dev/null +++ b/docs/04-consumer-setup.md @@ -0,0 +1,57 @@ +### Consumer from Release (Local LLM, Proxy-Router & UI-Desktop): + +This is the simplest way to get started with the Morpheus Lumerin Node as a Consumer. This will allow you to interact with the Morpheus network and the models offered on the network as a consumer. + +It will run 3 different pieces of software on your local machine: +* `llama.cpp` (llama-server) - a simple sample AI model that can be run on the same machine as the proxy-router and ui-desktop to show how the components work together and run local (free) inference +* `proxy-router` - the same software as the provider side, but with different environment variables and a different role +* `ui-desktop` - Electron GUI that enables the user to interact with the models (via the API) to browse offered bids, purchase and send prompts + +## Installation Steps: +1. Download latest release for your operating system: https://github.com/Lumerin-protocol/Morpheus-Lumerin-Node/releases + +1. Extract the zip to a local folder (examples) + * Windows: `(%USERPROFILE%)/Downloads/morpheus)` + * Linux & MacOS: `~/Downloads/morpheus` + * On MacOS you may need to execute `xattr -c mor-launch proxy-router ui-desktop.app llama-server` in a command window to remove the quarantine flag on MacOS + +1. Launch the node - this should open a command window to see local LLM model server and proxy-router start and then should launch the user interface + * Windows: Double click the `mor-launch.exe` (You will need to tell Windows Defender this is ok to run) + * Linux & MacOS: Open a terminal and navigate to the folder and run `./mor-launch` + +1. Startup User Interface: + 1. Read & accept terms & Conditions + 1. Set a strong password (this is for the UI-Desktop only) + 1. Follow the instructions for creating a new wallet (be sure to save the mnemonic in a safe place) + 1. **OPTIONAL to use existing Wallet** + - Instead of creating an new wallet and if you have the existing wallet's mnemonic, when prompted, select **`Recover your wallet Saved Mnemonic`** instead. + +## Validation Steps: +1. Local Test: Once the UI is up and running, + 1. You should see tokens for saETH and saMOR that you sent to this wallet earlier. + * If this is a new wallet, you will need to send saMOR and saETH to this wallet to be able to interact with the blockchain + * This can be done externally via metamask or usual Arbitrum testnet faucets + 1. Once you have a funded Wallet, you can interact with the local model + 1. Click on the `Chat` icon on the left side of the screen + 1. Make sure the `Local Model` is selected + 1. Begin your conversation with the model by typing in the chat window and pressing `Enter` + * You should see the model respond with the appropriate response to the prompt you entered, if not, there may be an issue with the local model service + +1. Remote Test: Once you've verified that your wallet can access the blockchain and you can see the local model working, you can switch to a remote model and test that as well + 1. In the `Chat` window, select `Change Model ` + 1. Select a different model from remote providers + 1. DropDown and select the contract address of the model you want to use + 1. Click Change + 1. Click Open Session + 1. MUST Enter at least **5** MOR to open session + 1. You can now chat with the remote model and see the responses in the chat window + +1. Cleanup/Closeout + * Manually End all Remote Sessions: + * In the Chat Window, click on the Time icon to the right of the Model line - this will expand and show current sessions, click the "X" next to each one to make sure it's closed + * Closing the UI-Desktop window should leave the CMD window open + * You’ll have to ctrl-c in the window to kill the local model and proxy-router + * To FULLY delete and force a clean startup of the UI (including forcing new password and mnemonic recovery), delete the ui-desktop folder and start the UI again + * Windows:  `%USERPROFILE%\AppData\Roaming\ui-desktop` + * Linux: `~/.config/ui-desktop` + * MacOS: `~/Library/Application Support/ui-desktop` diff --git a/consumer.md b/docs/04a-consumer-setup-source.md similarity index 99% rename from consumer.md rename to docs/04a-consumer-setup-source.md index bfa5a1a8..b4240f35 100644 --- a/consumer.md +++ b/docs/04a-consumer-setup-source.md @@ -1,4 +1,4 @@ -# Consumer Node Setup (Draft - 2024-07-23) +# Consumer Node Setup from Source (Draft - 2024-08-05) This document provides a step-by-step guide to setting up a Consumer Node for the Morepheus Network so that you can setup session and interact with the remote providers. ## Pre-requisites: diff --git a/docs/05-bid-purchase.md b/docs/05-bid-purchase.md new file mode 100644 index 00000000..c4524170 --- /dev/null +++ b/docs/05-bid-purchase.md @@ -0,0 +1 @@ +# TODO - detail steps for consumer to navigate in the ui-desktop, select & purchase remote model \ No newline at end of file diff --git a/docs/06-model-interaction.md b/docs/06-model-interaction.md new file mode 100644 index 00000000..0d6ed1e4 --- /dev/null +++ b/docs/06-model-interaction.md @@ -0,0 +1 @@ +# TODO - Detailed setps once bid has been purchased to interact with the model (might be model dependent). Also include how to close model early, check on time remaining, etc. \ No newline at end of file diff --git a/docs/images/overview.png b/docs/images/overview.png new file mode 100644 index 00000000..8978f1db Binary files /dev/null and b/docs/images/overview.png differ diff --git a/docs/images/overview.svg b/docs/images/overview.svg new file mode 100644 index 00000000..924e965d --- /dev/null +++ b/docs/images/overview.svg @@ -0,0 +1,2 @@ +
Provider Registration
Provider Registration
Model Registration 
Model Registration 
Bid Offer
Bid Offer
Model Registration 
Model Registration 
Bid Offer
Bid Offer
Bid Offer
Bid Offer
Bid Offer 0x1234...abc
Bid Offer 0x1234...abc
Provider Registration
Provider Registration
Model Registration 
Model Registration 
Bid Offer
Bid Offer
Model Registration 
Model Registration 
Bid Offer
Bid Offer
Bid Offer
Bid Offer
Bid Offer 0x1234...abc
Bid Offer 0x1234...abc
CONSUMER 
CONSUMER 
PROVIDER
PROVIDER
AI Compute Farm or Service
AI Compute Farm or Service
https://mycoolai.serverfarm.io:8080
Private Endpoint exposed only to the Lumerin Proxy-Router
https://mycoolai.serverfarm.io:8080...
models-config.json 
/proxy-router/.env variable
OPENAI_BASE_URL=
models-config.json &...
Lumerin Proxy-Router
Lumerin Proxy-R...
/proxy-router/.env define local address & port
PROXY_ADDRESS=
/proxy-router/.env define local address & port...
http://mycoolproxy.serverfarm.io:3333
Public Provider Endpoint that talks to the Blockchain
and Receives inbound prompts from purchased model bids
http://mycoolproxy.serverfarm.io:3333...
/proxy-router/.env variables
WEB_ADDRESS=0.0.0.0:8082
WEB_PUBLIC_URL=localhost:8082
/proxy-router/.env variables...
http://mycoolproxy.serverfarm.io:8082/swagger/index.html
Private Endpoint that talks to the proxy-router API interface (Access to Wallet Actions-keep secure!)
http://mycoolproxy.serverfarm.io:8082/swagger/index.html...
Arbitrum Ethereum
WebSocket Service Endpoint 
(wss://.......) 

Arbitrum...
/proxy-router/.env variable
ETH_NODE_ADDRESS=wss://
/proxy-router/.env variable...
Provider Registration
Provider Registration
Model Registration 
Model Registration 
Bid Offer
Bid Offer
Model Registration 
Model Registration 
Bid Offer
Bid Offer
Bid Offer
Bid Offer
Bid Offer 0x1234...abc
Bid Offer 0x1234...abc
Local LLama.cpp AI Model
Local...
https://localhost:8080
Internal Node Endpoint exposed only to the Lumerin Proxy-Router
https://localhost:8080...
Lumerin Proxy-Router
Lumerin Proxy-R...
http://localhost:3333
Typically, consumer endpoint would not recieve INBOUND proxy traffic
http://localhost:3333...
http://localhost:8082/swagger/index.html
Private Endpoint that talks to the proxy-router API interface 
(Access to Wallet Actions-keep secure!)
http://localhost:8082/swagger/index.html...
Morpheus-Lumerin GUIUI-Desktop (electron app) 
1
1
2
2
4
4
6
6
5
5
3
3
Blockchain

Blockchain +
Existing / Foundational Elements

Arbitrum BlockChain, Token Contract & Smart Contract
MOR_TOKEN_ADDRESS=0xc1664f994fd3991f98ae944bc16b9aed673ef5fd
DIAMOND_CONTRACT_ADDRESS=0x8e19288d908b2d9F8D7C539c74C899808AC3dE45
Existing / Foundational Elements...
0
0
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/images/simple.png b/docs/images/simple.png new file mode 100644 index 00000000..d906bfb2 Binary files /dev/null and b/docs/images/simple.png differ diff --git a/docs/images/simple.svg b/docs/images/simple.svg new file mode 100644 index 00000000..b11a0e3e --- /dev/null +++ b/docs/images/simple.svg @@ -0,0 +1,2 @@ +
CONSUMER 
CONSUMER 
PROVIDER
PROVIDER
AI Compute Farm or Service
AI Compute Farm or Service
Lumerin Proxy-Router
Lumerin Proxy-R...
 
 
Local Demo
AI Model
Local...
Lumerin Proxy-Router
Lumerin Proxy-R...
Morpheus-Lumerin GUIUI-Desktop (electron app) 
1
1
2
2
4
4
6
6
5
5
3
3
Blockchain

Blockchain +
Provider Registration
Provider Registration
Model Registration 
Model Registration 
Bid Offer 0x1234...abc
Bid Offer 0x1234...abc
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/mac-boot-strap.md b/docs/mac-boot-strap.md new file mode 100644 index 00000000..596d44e3 --- /dev/null +++ b/docs/mac-boot-strap.md @@ -0,0 +1,172 @@ +## "Simple Run From Mac" - 3 terminal windows + +### Overview +- This is a simple guide to get the Llama.cpp model, Lumerin proxy-router and ui-desktop from source running on a Mac +- This is a guide for a developer or someone familiar with the codebase and build process +- Wallet: if you’re going to start with an existing wallet from something like MetaMask (recommended)…make sure it’s a tier 1, not a derived or secondary address. Desktop-ui recover from mnemonic won’t work properly if you try to recover a secondary address with the primary mnemonic. +- Install & Configure OS-Specific Dependencies + * git (https://git-scm.com/) + * go (https://golang.org/) + * node (https://nodejs.org/) + * make (https://www.gnu.org/software/make/) + * yarn (https://yarnpkg.com/) + +- Four basic steps: + 1. Clone, build select model and run local Llama.cpp model + 2. Clone the Morpheus-Lumerin-Node repo from Github + 3. Configure, Build and run the proxy-router + 4. Configure, Build and run the ui-desktop + +## **A. LLAMA.CPP** +* Open first terminal / cli window +* You will need to know what port you want the local mode to listen on (8080 in this example) +* This work should only need to be done once as it provides a local model for validation of the local proxy-router + +**1. Clone LLamaCPP Repo & Build** +```sh +git clone https://github.com/ggerganov/llama.cpp.git +cd llama.cpp +make -j 8 +``` + +**2. Set General Variables:** +* In same CLI window set the variables that the model command line will use (or you can build out a .env file with these and the others below) + ```sh + model_host=127.0.0.1 + model_port=8080 + ``` + +**3. Set Model Specific Variables:** (pick one of the variable blocks below) +* https://huggingface.co/TheBloke + * Llama-2-7B-Chat-GGUF / llama-2-7b-chat.Q5_K_M.gguf (7.28GB RAM required) + ``` + model_url=https://huggingface.co/TheBloke + model_collection=Llama-2-7B-Chat-GGUF + model_file_name=llama-2-7b-chat.Q5_K_M.gguf + ``` + * CollectiveCognition-v1.1-Mistral-7B-GGUF / collectivecognition-v1.1-mistral-7b.Q5_K_M.gguf + ``` + model_url=https://huggingface.co/TheBloke + model_collection=CollectiveCognition-v1.1-Mistral-7B-GGUF + model_file_name=collectivecognition-v1.1-mistral-7b.Q5_K_M.gguf + ``` + * TinyLlama-1.1B-Chat-v1.0-GGUF / tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf + ``` + model_url=https://huggingface.co/TheBloke + model_collection=TinyLlama-1.1B-Chat-v1.0-GGUF + model_file_name=tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf + ``` + * CapybaraHermes-2.5-Mistral-7B-GGUF / capybarahermes-2.5-mistral-7b.Q5_K_M.gguf + ``` + model_url=https://huggingface.co/TheBloke + model_collection=CapybaraHermes-2.5-Mistral-7B-GGUF + model_file_name=capybarahermes-2.5-mistral-7b.Q5_K_M.gguf + ``` + + +**4. Download and run the model using the variables set above:** +```sh +wget -O models/${model_file_name} ${model_url}/${model_collection}/resolve/main/${model_file_name} +./llama-server -m models/${model_file_name} --host ${model_host} --port ${model_port} --n-gpu-layers 4096 +``` + +* OPTIONAL To leave this running in the background (and tail -f nohup.out to monitor) +```sh +wget -O models/${model_file_name} ${model_url}/${model_collection}/resolve/main/${model_file_name} +nohup ./llama-server -m models/${model_file_name} --host ${model_host} --port ${model_port} --n-gpu-layers 4096 & +tail -f nohup.out +``` + +**5. Validate that the local model is running:** +* Navigate to `http://127.0.0.1:8080` in a browser to see the model interface and test inferrence +* You should also see (in the terminal window) the interaction with the model and the responses + +## **B. PROXY-ROUTER** +* Open second terminal / cli window +* You will need to know + * Your Wallet private-key **DON'T EVER SHARE WITH ANYONE** for use in the .env file + * Your ETH node wss address (eg: `wss://rinkeby.infura.io/ws/v3/your_infura_project_id`) for use in the .env file (we are currently working on a default, public leverage of http providers, but wss is most reliable at this point) + * TCP port you want the proxy-router to listen on (3333 in this example) + * TCP port that you want the API to listen on (8082 in this example) + +**1. Clone repo from either the Lumerin Team fork or the Origin on Morpheus' Github** +- Lumerin Team Development Github: `git clone https://github.com/Lumerin-protocol/Morpheus-Lumerin-Node.git` +- Origin from Morpheus Github: (`git clone https://github.com/MorpheusAIs/Morpheus-Lumerin-Node.git`) +- **NOTE:** if you have already cloned and configured your .env, and want to update to the lastest revision, you can `git pull` from within the Morpheus-Lumerin-Node directory and then skip to step 4 below to build and run the updated proxy-router + +**2. Navigate to the proxy-router directory** `cd /Morpheus-Lumerin-Node/proxy-router` + +**3. Modify proxy-router environment variables** +- You will need WalletPrivateKey, WSS Eth Address and router and api listening ports +- Within the proxy-router directory, copy the `.env.example` to `.env` and edit to set the variables as needed (prompts are within the file) +```sh +cp .env.example .env +vi .env +``` +**4. Build and Run the proxy-router** +```sh +./build.sh +make run +``` +**- NOTE:** that when this launches, some anti-virus or network protection software may ask you to allow the ports to be open as well as MAC-OS firewall proteciton (watch for the pop-up windows and allow) + +**5. Validate that the proxy-router is running:** +- In the terminal window where you started the proxy-router the following messaging ensures that you're running and watching events on the blockchain +```sh +2024-08-07T11:35:49.116184 INFO proxy state: running +2024-08-07T11:35:49.116534 INFO Wallet address: +2024-08-07T11:35:49.116652 INFO started watching events, address 0x8e19288d908b2d9F8D7C539c74C899808AC3dE45 +2024-08-07T11:35:49.116924 INFO HTTP http server is listening: 0.0.0.0:8082 +2024-08-07T11:35:49.116962 INFO TCP tcp server is listening: 0.0.0.0:3333 +``` +- Navigate to `http://localhost:8082/swagger/index.html` in a browser to see the proxy-router interface and test the Swagger API + +**- NOTE** if you would like to interact directly with your proxy-router without the UI, see the instructions in [/docs/proxy-router-api-direct.md](/docs/proxy-router-api-direct.md) + +## **C. UI-DESKTOP** +* Open third terminal / cli window +* You will need to know + * TCP port that your proxy-router API interface is listening on (8082 in this example) + +**1. Navigate to ui-desktop** +`cd /Morpheus-Lumerin-Node/ui-desktop` + +**2. Check Environment Variables** +- Within the ui-desktop directory, copy the `.env.example` to `.env` and check the variables as needed +- At the current time, the defaults in the .env file shold be sufficient to operate as long as none of the LLAMA.cpp or proxy-router variables for ports have changed. +- Double check that your PROXY_WEB_URL is set to `8082` or what you used for the ASwagger API interface on the proxy-router. +- This is what enables the UI to communicate to the proxy-router environment + +```sh +cp .env.example .env +vi .env +``` + +**3. Install dependicies, compile and Run the ui-desktop** +```sh +yarn install +yarn dev +``` + +**4. Validate that the ui-desktop is running:** +- At this point, the electon app should start and if this is the first time walking through, should run through onboarding +- Check the following: + - Lower left corner - is this your correct ERC20 Wallet Address? + - On the Wallet tab, do you show saMOR and saETH balances? + - if not, you'll need to convert and transfer some saETH to saMOR to get started + - On the Chat tab, your should see your `Provider: (local)` selected by default...use the "Ask me anything..." prompt to run inference - this verifies that all three layers have been setup correctly? + - Click the Change Model, select a remote model, click Start and now you should be able to interact with the model through the UI. + +**Cleaning & Troubleshooting** +- Sometimes, due to development changes or dependency issues, you may need to clean and start fresh. +- Make sure you know what your WalletPrivate key is and have it saved in a secure location. before cleaning up and restarting the ui or proxy-router + - `rm -rf ./node_modules` from within ui-desktop + - `rm -rf '~/Library/Application Support/ui-desktop'` to clean old ui-desktop cache and start new wallet +- The proxy-router or ui-desktop may not start cleanly because of existing processes or open files from previous runs of the software + - If you need to exit either proxy-router or ui-desktop, you can use `ctrl-c` from the terminal window you started them to kill the processes… + - **Locked Processes** Doing this may leave "dangling" processes or open files to find them: + - Run `ps -ax | grep electron` or `ps -ax | grep proxy-router` which will show you the processes and particualrly the procexss id + - Run `kill -9 xxxxx` where `xxxxx` is the process ID of the un-cleaned process + - **Locked Files** In certain cases the proxy-router will not let go of the `./data/` logging files… to clean them: + - Run `lsof | grep /proxy-router/data/` to list the current open files in that directory, they should all have a common process ID (Second column on the screen) + - Run `kill -9 xxxxxx` the process id that matches to clean it all up \ No newline at end of file diff --git a/docs/proxy-router-api-direct.md b/docs/proxy-router-api-direct.md new file mode 100644 index 00000000..94c163d6 --- /dev/null +++ b/docs/proxy-router-api-direct.md @@ -0,0 +1,163 @@ +# Direct Consumer interaction with the proxy-router (no ui-desktop) +This document provides a step-by-step flow to query your local proxy router and interact with a remote model using only the API. This is useful for developers or users who want to interact with the model directly without using the UI-Desktop. + +## Pre-requisites: +* Create or use an existing ERC-20 wallet that has saMOR and saETH (Sepolia Arbitrum) tokens - you can use Metamask (new wallet..not derived) or any other ERC-20 wallet. +* You will need to have access to the wallet's private key **NEVER SHARE THIS WITH ANYONE** for steps below to authorize the contract to spend on your behalf. +* Install and launch the local llama.cpp and proxy-router from source (see [/docs/mac-boot-strap.md](/docs/mac-boot-strap.md) for instructions) + +## TL;DR +* Authorize the contract to spend on your behalf (once) +* Query the blockchain for various models / providers & get the ModelID `Id` (every session) +* Create a session with the provider using the ModelID `Id` (every session) +* Interact with the provider by sending the prompt (every session) + +## Detail: +### A. Start the proxy-router (per instructions) + +### B. Authorize the contract to spend on your behalf +Either via the swagger interface http://localhost:8082/swagger/index.html#/wallet/post_blockchain_allowance or following CLI, you can authorize the contract to spend on your behalf. **This only needs to be done once per wallet, or when funds have been depleted.** + +Approve the contract to spend 3 saMOR tokens on your behalf + +```sh +curl -X 'POST' 'http://localhost:8082/blockchain/approve?spender=0x8e19288d908b2d9F8D7C539c74C899808AC3dE45&amount=3' -H 'accept: application/json' -d '' +``` + +### C. Query the blockchain for various models / providers (Get ModelID) +* You can query the blockchain for various models and providers to get the ModelID. +* This can be done via the swagger interface http://localhost:8082/swagger/index.html#/marketplace/get_marketplace_models or following CLI: +```sh +# Returns the wallet ID (confirm that it matches your wallet): +curl -X 'GET' 'http://localhost:8082/wallet' -H 'accept: application/json' + +# Returns the list of models and providers: +curl -X 'GET' 'http://localhost:8082/blockchain/models' -H 'accept: application/json' +``` +* The first model in the list is the default model that you can use for testing purposes...see example below +* `Id` is the ID of the model that you will use to create a session with the provider. +* `Name` is the type of model offered + +``` +{ + "models": [ + { + "Id": "0x6a4813e866a48da528c533e706344ea853a1d3f21e37b4c8e7ffd5ff25779018", + "IpfsCID": "0x0000000000000000000000000000000000000000000000000000000000000000", + "Fee": 0, + "Stake": 0, + "Owner": "0x0eb467381abbc5b71f275df0c8a4e0ed8561f46f", + "Name": "llama2:7b", + "Tags": [], + "CreatedAt": 1721220139, + "IsDeleted": false + }, + { + "Id": "0x72eb5a6a575cdfb59e650994240961db2b1d915dbaa7c009b53b20fe8b9d2d7c", + "IpfsCID": "0x019ae5515ec6259cf835639fd645620811fe951f54c55ae5c85c1bb101cdcc3a", + "Fee": 42, + "Stake": 0, + "Owner": "0x65bbb982d9b0afe9aed13e999b79c56ddf9e04fc", + "Name": "CollectiveCognition-v1.1-Mistral-7b", + "Tags": [], + "CreatedAt": 1721222411, + "IsDeleted": false + }, + { + "Id": "0x84b6df5c84e1e6ae59c90e1639e3e77148d140065ef2cd4fba7f41cc7440e2c5", + "IpfsCID": "0xa3b9c9e18f25d3be47a2c8f0b1d5e6c78492fa7bcd34e9a1f6c2d3a5e4f9b7c1", + "Fee": 42, + "Stake": 0, + "Owner": "0xb8f836c167d60e20e44baf62d4d46c9e26fea97f", + "Name": "CapybaraHermes-v2.5-Mistral-7b", + "Tags": [], + "CreatedAt": 1721224046, + "IsDeleted": false + } + ] +} +``` + +### D. Create a session with the provider +```bash +curl -s -X 'POST' 'http://localhost:8082/blockchain/models//session' \ +-H 'accept:application/json' \ +-H 'Content-Type: application/json' \ +-d '{"sessionDuration": 600}' +``` +Now that the session is open, you can send inference queries to the provider and process responses in usual OpenAI format. +Your Wallet (on https://sepolia.arbiscan.io/address/) should show the transaction for the session creation. + +### E. Interact with the provider +* Send the prompt (Standard OpenAI format) with session_id in the header to interact. +* Minimally, you need to provide the `content` and `role` in the messages block and (currently) `"stream":true` for the remote prompt to work. +```bash +curl -X 'POST' \ + 'http://localhost:8082/v1/chat/completions' \ + -H 'accept: application/json' \ + -H 'session_id: ' \ + -H 'Content-Type: application/json' \ + -d '{ + "messages": [ + { + "content": "tell me a joke", + "role": "user" + } + ], + "stream": true + }' +``` + + +### Quick and Dirty Sample: +`curl -X 'POST' 'http://localhost:8082/blockchain/approve?spender=0x8e19288d908b2d9F8D7C539c74C899808AC3dE45&amount=3' -H 'accept: application/json' -d ''` + # approves the smart contract `0x8e19...dE45` to spend 3 saMOR tokens on your behalf + +`curl -s -X 'GET' 'http://localhost:8082/wallet' -H 'accept: application/json' | jq .address` + # returns the wallet ID (confirm that it matches your wallet) + +`curl -s -X 'GET' 'http://localhost:8082/blockchain/models' -H 'accept: application/json' | jq -r '.models[] | "\(.Id), \(.Name)"'` + # returns model ID and Name ... copy the ID for the next step `0x84b6df5c84e1e6ae59c90e1639e3e77148d140065ef2cd4fba7f41cc7440e2c5` + +`curl -s -X 'POST' 'http://localhost:8082/blockchain/models/0x84b6df5c84e1e6ae59c90e1639e3e77148d140065ef2cd4fba7f41cc7440e2c5/session' -H 'accept:application/json' -H 'Content-Type: application/json' -d '{"sessionDuration": 600}' | jq .sessionId` + # returns the session ID ... copy the ID for the next step `0x089111479fa2847106b4f7b17eace2e9b37e0d3c0db331b4e01a6e24de827477` + +```bash +curl -X 'POST' \ + 'http://localhost:8082/v1/chat/completions' \ + -H 'accept: application/json' \ + -H 'session_id: 0x089111479fa2847106b4f7b17eace2e9b37e0d3c0db331b4e01a6e24de827477' \ + -H 'Content-Type: application/json' \ + -d '{ + "messages": [ + { + "content": "tell me a joke", + "role": "user" + } + ], + "stream": true + }' +``` +#### Sample Output: +``` +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758293,"model":"llama2","choices":[{"index":0,"delta":{"content":"Why"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758293,"model":"llama2","choices":[{"index":0,"delta":{"content":" don"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758293,"model":"llama2","choices":[{"index":0,"delta":{"content":"'"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758293,"model":"llama2","choices":[{"index":0,"delta":{"content":"t"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758293,"model":"llama2","choices":[{"index":0,"delta":{"content":" scientists"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758293,"model":"llama2","choices":[{"index":0,"delta":{"content":" trust"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758293,"model":"llama2","choices":[{"index":0,"delta":{"content":" atoms"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758294,"model":"llama2","choices":[{"index":0,"delta":{"content":"?"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758294,"model":"llama2","choices":[{"index":0,"delta":{"content":"\n"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758294,"model":"llama2","choices":[{"index":0,"delta":{"content":"\n"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758294,"model":"llama2","choices":[{"index":0,"delta":{"content":"Because"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758294,"model":"llama2","choices":[{"index":0,"delta":{"content":" they"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758294,"model":"llama2","choices":[{"index":0,"delta":{"content":" make"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758294,"model":"llama2","choices":[{"index":0,"delta":{"content":" up"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758294,"model":"llama2","choices":[{"index":0,"delta":{"content":" everything"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758294,"model":"llama2","choices":[{"index":0,"delta":{"content":"!"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758294,"model":"llama2","choices":[{"index":0,"delta":{"content":"\u003c|im_end|\u003e"},"finish_reason":null,"content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +data: {"id":"chatcmpl-8wtgv86nUWMJneEObprWKXcWKX81PAAo","object":"chat.completion.chunk","created":1721758294,"model":"llama2","choices":[{"index":0,"delta":{},"finish_reason":"stop","content_filter_results":{"hate":{"filtered":false},"self_harm":{"filtered":false},"sexual":{"filtered":false},"violence":{"filtered":false}}}]} +``` + + diff --git a/docs/source/overview.drawio b/docs/source/overview.drawio new file mode 100644 index 00000000..e1ebcf83 --- /dev/null +++ b/docs/source/overview.drawio @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/toc.md b/docs/toc.md new file mode 100644 index 00000000..47a3be4f --- /dev/null +++ b/docs/toc.md @@ -0,0 +1,50 @@ + +Document outline: +1. Design & Expectations + * How is the proxy-router supposed to be leveraged? + * Where does it fall in the larger design for Morpheus? + * Pictures to illustrate flow of interaction and setting expecations + * How do you know it's working? (from which perspective?) + +1. Installation + 1. From Binaries, most simplistic to get up and running [install_from_binaries.md](install_from_binaries.md) + * Need ETH node solution (Http round robin) from Shev + * Create new Wallet within UI-Desktop + * Send aETH and aMOR to wallet (from separate wallet or mechanism) + * Variations for Mac ARM, Mac Intel, Linux, Windows + * Needs to be as easy and as simple as possible (minimal clicks/configuration) + * Both "private provider" and "consumer" + + 2. From Source, more complex but more control [install_from_source.md](install_from_source.md) + * Variants include + * Use existing "on-box" model + * Separate Model hosted elsewhere (with accessible endpoint) + * proxy-router node with private API access and publicly accessible router port + * directions on how to use the Swagger API / CLI / CURL + * Asssume separate eth node WSS subscription for proxy-router + * Assume using existing Wallet (with access to private key) + * Full control of proxy-router as core (tying blockchain-contracts, provider, model, bid, etc between AI Compute and Consumers) +1. Utilization + * "Done-Consumer" looks like + - download, + - unzip, + - allow binaries, + - run, + - accept, + - create, + - receive (*may need separate doc for getting tokens/eth..especailly on sa), + - chat local, + - chat remote (existing providers) + * "Done-Personal-Provider" looks like + - "Done-Consumer" plus + - authorize contract, + - create provider, + - create model, + - create bid + - validate that model is working (available on chain and accessible via proxy-router) + * "Done-Enterprise-Provider" looks like + - Assume existing Wallet (with privatekey) + - Assume existing ETH Node WSS subscription + - Assume familiarity working with API/CLI/CURL and existing blockchain stuff + - Assume existing AI Compute Resource/Model that wants to be provided + diff --git a/proxy-router/.env.example b/proxy-router/.env.example index 1181ece7..10cd5964 100644 --- a/proxy-router/.env.example +++ b/proxy-router/.env.example @@ -3,10 +3,11 @@ ETH_NODE_ADDRESS=wss://arb-sepolia.g.alchemy.com/v2/3-pxwBaJ7vilkz1jl-fMmCvZThGx DIAMOND_CONTRACT_ADDRESS=0x8e19288d908b2d9f8d7c539c74c899808ac3de45 MOR_TOKEN_ADDRESS=0xc1664f994fd3991f98ae944bc16b9aed673ef5fd -WEB_ADDRESS=0.0.0.0:8082 # Change to your desired port +WEB_ADDRESS=0.0.0.0:8082 # Change to your desired port that your Proxy-router API (Swagger should be listening on) WEB_PUBLIC_URL=localhost:8082 # Change to your desired port should match WEB_ADDRESS port number OPENAI_BASE_URL=http://localhost:8080/v1 # Change to your OpenAI API port for local model +# MODELS_CONFIG_PATH= ENVIRONMENT=development ETH_NODE_LEGACY_TX=false EXPLORER_API_URL="https://api-sepolia.arbiscan.io/api" diff --git a/proxy-router/.env.example.win b/proxy-router/.env.example.win new file mode 100644 index 00000000..f0657406 --- /dev/null +++ b/proxy-router/.env.example.win @@ -0,0 +1,29 @@ +WALLET_PRIVATE_KEY= # Private Key from your Wallet as Consumer or Provider (needed for the proxy-router to sign transactions) +ETH_NODE_ADDRESS=wss://arb-sepolia.g.alchemy.com/v2/3-pxwBaJ7vilkz1jl-fMmCvZThGxpmo2 # Recommend using your own private ETH Node Address for better performance (via Alchemy or Infura) + +DIAMOND_CONTRACT_ADDRESS=0x8e19288d908b2d9f8d7c539c74c899808ac3de45 +MOR_TOKEN_ADDRESS=0xc1664f994fd3991f98ae944bc16b9aed673ef5fd +WEB_ADDRESS=0.0.0.0:8082 # Change to your desired port that your Proxy-router API (Swagger should be listening on) +WEB_PUBLIC_URL=localhost:8082 # Change to your desired port should match WEB_ADDRESS port number +OPENAI_BASE_URL=http://localhost:8080/v1 # Change to your OpenAI API port for local model + +# MODELS_CONFIG_PATH= +ENVIRONMENT=development +ETH_NODE_LEGACY_TX=false +EXPLORER_API_URL="https://api-sepolia.arbiscan.io/api" +LOG_COLOR=true +LOG_FOLDER_PATH= +LOG_LEVEL_APP=info +LOG_LEVEL_CONNECTION=info +LOG_LEVEL_PROXY=info +LOG_LEVEL_SCHEDULER=info +OPENAI_API_KEY= #optional +PROXY_ADDRESS=0.0.0.0:3333 +PROXY_STORAGE_PATH=.\data\ +SYS_ENABLE=false +SYS_LOCAL_PORT_RANGE=1024 65535 +SYS_NET_DEV_MAX_BACKLOG=100000 +SYS_RLIMIT_HARD=524288 +SYS_RLIMIT_SOFT=524288 +SYS_SOMAXCONN=100000 +SYS_TCP_MAX_SYN_BACKLOG=100000 \ No newline at end of file diff --git a/proxy-router/.swaggo b/proxy-router/.swaggo new file mode 100644 index 00000000..61750d99 --- /dev/null +++ b/proxy-router/.swaggo @@ -0,0 +1,7 @@ +// Replacements for input fields with custom types used for swagger +replace internal/lib.BigInt string +replace internal/lib.HexString string +replace common.Hash string +replace common.Address string +replace big.Int string +replace error string diff --git a/proxy-router/Makefile b/proxy-router/Makefile index 055e4dfd..ffdc7b6f 100644 --- a/proxy-router/Makefile +++ b/proxy-router/Makefile @@ -35,4 +35,4 @@ test-integration: go test -v ./test/... swagger: - swag init -g ./internal/handlers/httphandlers/http.go \ No newline at end of file + swag fmt -g ./internal/handlers/httphandlers/http.go && swag init -g ./internal/handlers/httphandlers/http.go --parseInternal \ No newline at end of file diff --git a/proxy-router/README.md b/proxy-router/README.md deleted file mode 100644 index ea3d5c1a..00000000 --- a/proxy-router/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Proxy Refactor - - - -## Getting started - -To make it easy for you to get started with GitLab, here's a list of recommended next steps. - -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! - -## Add your files - -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: - -``` -cd existing_repo -git remote add origin https://gitlab.com/TitanInd/proxy/proxy-refactor.git -git branch -M main -git push -uf origin main -``` - -## Integrate with your tools - -- [ ] [Set up project integrations](https://gitlab.com/TitanInd/proxy/proxy-refactor/-/settings/integrations) - -## Collaborate with your team - -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) - -## Test and Deploy - -Use the built-in continuous integration in GitLab. - -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) - -*** - -# Editing this README - -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. - -## Suggestions for a good README -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. - -## Name -Choose a self-explaining name for your project. - -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. - -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. - -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. - -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. - -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. - -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. - -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. - -## Contributing -State if you are open to contributions and what your requirements are for accepting them. - -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. - -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. - -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. - -## License -For open source projects, say how it is licensed. - -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. diff --git a/proxy-router/cmd/main.go b/proxy-router/cmd/main.go index 7148127d..6bfc6c60 100644 --- a/proxy-router/cmd/main.go +++ b/proxy-router/cmd/main.go @@ -209,17 +209,18 @@ func start() error { log.Infof("Using keychain wallet") } - proxyRouterApi := proxyapi.NewProxySender(publicUrl, wallet, contractLogStorage, sessionStorage, log) - blockchainApi := blockchainapi.NewBlockchainService(ethClient, *cfg.Marketplace.DiamondContractAddress, *cfg.Marketplace.MorTokenAddress, cfg.Blockchain.ExplorerApiUrl, wallet, sessionStorage, proxyRouterApi, proxyLog, cfg.Blockchain.EthLegacyTx) - aiEngine := aiengine.NewAiEngine(cfg.AIEngine.OpenAIBaseURL, cfg.AIEngine.OpenAIKey, log) - - sessionRouter := registries.NewSessionRouter(*cfg.Marketplace.DiamondContractAddress, ethClient, log) - modelConfigLoader := config.NewModelConfigLoader(cfg.Proxy.ModelsConfigPath, log) err = modelConfigLoader.Init() if err != nil { log.Warnf("failed to load model config: %s, run with empty", err) } + + proxyRouterApi := proxyapi.NewProxySender(publicUrl, wallet, contractLogStorage, sessionStorage, log) + blockchainApi := blockchainapi.NewBlockchainService(ethClient, *cfg.Marketplace.DiamondContractAddress, *cfg.Marketplace.MorTokenAddress, cfg.Blockchain.ExplorerApiUrl, wallet, sessionStorage, proxyRouterApi, proxyLog, cfg.Blockchain.EthLegacyTx) + aiEngine := aiengine.NewAiEngine(cfg.AIEngine.OpenAIBaseURL, cfg.AIEngine.OpenAIKey, modelConfigLoader, log) + + sessionRouter := registries.NewSessionRouter(*cfg.Marketplace.DiamondContractAddress, ethClient, log) + eventListener := blockchainapi.NewEventsListener(ethClient, sessionStorage, sessionRouter, wallet, modelConfigLoader, log) blockchainController := blockchainapi.NewBlockchainController(blockchainApi, log) diff --git a/proxy-router/docs/docs.go b/proxy-router/docs/docs.go index 1f4d1f17..0e8828ea 100644 --- a/proxy-router/docs/docs.go +++ b/proxy-router/docs/docs.go @@ -23,7 +23,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Get Allowance for MOR", "parameters": [ @@ -39,7 +39,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.AllowanceRes" } } } @@ -52,7 +52,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Approve MOR allowance", "parameters": [ @@ -75,7 +75,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.TxRes" } } } @@ -88,14 +88,14 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Get ETH and MOR balance", "responses": { "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.TokenBalanceRes" } } } @@ -110,7 +110,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "bids" ], "summary": "Creates bid in blockchain", "parameters": [ @@ -128,13 +128,42 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.BidRes" + } + } + } + } + }, + "/blockchain/bids/{id}": { + "get": { + "description": "Get bid from blockchain by ID", + "produces": [ + "application/json" + ], + "tags": [ + "bids" + ], + "summary": "Get Bid by ID", + "parameters": [ + { + "type": "string", + "description": "Bid ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/structs.BidRes" } } } } }, - "/blockchain/bids/:id/session": { + "/blockchain/bids/{id}/session": { "post": { "description": "Full flow to open a session by bidId", "consumes": [ @@ -169,7 +198,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.OpenSessionRes" } } } @@ -182,14 +211,14 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Get Latest Block", "responses": { "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.BlockRes" } } } @@ -202,17 +231,14 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "models" ], "summary": "Get models list", "responses": { "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.ModelsRes" } } } @@ -225,7 +251,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "models" ], "summary": "Creates model in blockchain", "parameters": [ @@ -243,7 +269,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.ModelRes" } } } @@ -256,7 +282,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "bids" ], "summary": "Get Active Bids by Model", "parameters": [ @@ -272,10 +298,36 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.BidsRes" + } + } + } + } + }, + "/blockchain/models/{id}/bids/rated": { + "get": { + "description": "Get rated bids from blockchain by model", + "produces": [ + "application/json" + ], + "tags": [ + "bids" + ], + "summary": "Get Rated Bids", + "parameters": [ + { + "type": "string", + "description": "Model ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/structs.ScoredBidsRes" } } } @@ -316,7 +368,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.OpenSessionRes" } } } @@ -329,17 +381,14 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "providers" ], "summary": "Get providers list", "responses": { "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.ProvidersRes" } } } @@ -352,7 +401,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "providers" ], "summary": "Creates or updates provider in blockchain", "parameters": [ @@ -370,7 +419,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.ProviderRes" } } } @@ -383,7 +432,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "bids" ], "summary": "Get Bids by Provider", "parameters": [ @@ -411,10 +460,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.BidsRes" } } } @@ -427,7 +473,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "bids" ], "summary": "Get Bids by Provider", "parameters": [ @@ -443,10 +489,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.BidsRes" } } } @@ -459,7 +502,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Send Eth", "parameters": [ @@ -477,7 +520,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.TxRes" } } } @@ -490,7 +533,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Send Mor", "parameters": [ @@ -508,7 +551,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.TxRes" } } } @@ -554,10 +597,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.SessionsRes" } } } @@ -589,7 +629,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.OpenSessionRes" } } } @@ -602,14 +642,43 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "sessions" ], "summary": "Get Todays Budget", "responses": { "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.BudgetRes" + } + } + } + } + }, + "/blockchain/sessions/{id}": { + "get": { + "description": "Returns session by ID", + "produces": [ + "application/json" + ], + "tags": [ + "sessions" + ], + "summary": "Get session", + "parameters": [ + { + "type": "string", + "description": "Session ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/structs.SessionRes" } } } @@ -638,7 +707,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.TxRes" } } } @@ -651,14 +720,14 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "sessions" ], "summary": "Get Token Supply", "responses": { "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.SupplyRes" } } } @@ -671,7 +740,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Get Transactions", "parameters": [ @@ -692,10 +761,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.TransactionsRes" } } } @@ -771,14 +837,25 @@ const docTemplate = `{ "application/json" ], "tags": [ - "sessions" + "chat" ], "summary": "Initiate Session with Provider", + "parameters": [ + { + "description": "Initiate Session", + "name": "initiateSession", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/proxyapi.InitiateSessionReq" + } + } + ], "responses": { "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/morrpcmesssage.SessionRes" } } } @@ -801,7 +878,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/structs.SendRequest" + "$ref": "#/definitions/structs.AmountReq" } }, { @@ -816,7 +893,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.TxRes" } } } @@ -845,7 +922,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.BalanceRes" } } } @@ -855,13 +932,27 @@ const docTemplate = `{ "post": { "description": "Send prompt to a local or remote model based on session id in header", "produces": [ - "application/json" + "text/event-stream" ], "tags": [ - "wallet" + "chat" ], "summary": "Send Local Or Remote Prompt", "parameters": [ + { + "type": "string", + "format": "hex32", + "description": "Session ID", + "name": "session_id", + "in": "header" + }, + { + "type": "string", + "format": "hex32", + "description": "Model ID", + "name": "model_id", + "in": "header" + }, { "description": "Prompt", "name": "prompt", @@ -870,19 +961,13 @@ const docTemplate = `{ "schema": { "$ref": "#/definitions/proxyapi.OpenAiCompletitionRequest" } - }, - { - "type": "string", - "description": "Session ID", - "name": "session_id", - "in": "header" } ], "responses": { "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/proxyapi.ChatCompletionResponse" } } } @@ -894,7 +979,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "wallet" + "chat" ], "summary": "Get local models", "responses": { @@ -924,7 +1009,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/walletapi.WalletRes" } } } @@ -953,7 +1038,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/walletapi.statusRes" } } } @@ -964,6 +1049,9 @@ const docTemplate = `{ "aiengine.LocalModel": { "type": "object", "properties": { + "apiType": { + "type": "string" + }, "id": { "type": "string" }, @@ -975,8 +1063,58 @@ const docTemplate = `{ } } }, - "lib.BigInt": { - "type": "object" + "morrpcmesssage.SessionRes": { + "type": "object", + "required": [ + "approval", + "approvalSig", + "message", + "signature", + "timestamp", + "user" + ], + "properties": { + "approval": { + "type": "string" + }, + "approvalSig": { + "type": "string" + }, + "message": { + "type": "string" + }, + "signature": { + "type": "string" + }, + "timestamp": { + "type": "integer" + }, + "user": { + "type": "string" + } + } + }, + "proxyapi.ChatCompletionChoice": { + "type": "object", + "properties": { + "finish_reason": { + "description": "FinishReason\nstop: API returned complete message,\nor a message terminated by one of the stop sequences provided via the stop parameter\nlength: Incomplete model output due to max_tokens parameter or token limit\nfunction_call: The model decided to call a function\ncontent_filter: Omitted content due to a flag from our content filters\nnull: API response still in progress or incomplete", + "allOf": [ + { + "$ref": "#/definitions/proxyapi.FinishReason" + } + ] + }, + "index": { + "type": "integer" + }, + "logprobs": { + "$ref": "#/definitions/proxyapi.LogProbs" + }, + "message": { + "$ref": "#/definitions/proxyapi.ChatCompletionMessage" + } + } }, "proxyapi.ChatCompletionMessage": { "type": "object", @@ -1003,6 +1141,35 @@ const docTemplate = `{ } } }, + "proxyapi.ChatCompletionResponse": { + "type": "object", + "properties": { + "choices": { + "type": "array", + "items": { + "$ref": "#/definitions/proxyapi.ChatCompletionChoice" + } + }, + "created": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "model": { + "type": "string" + }, + "object": { + "type": "string" + }, + "system_fingerprint": { + "type": "string" + }, + "usage": { + "$ref": "#/definitions/proxyapi.Usage" + } + } + }, "proxyapi.ChatCompletionResponseFormat": { "type": "object", "properties": { @@ -1047,6 +1214,15 @@ const docTemplate = `{ "ChatMessagePartTypeImageURL" ] }, + "proxyapi.FinishReason": { + "type": "string", + "enum": [ + "stop" + ], + "x-enum-varnames": [ + "FinishReasonStop" + ] + }, "proxyapi.ImageURLDetail": { "type": "string", "enum": [ @@ -1060,6 +1236,70 @@ const docTemplate = `{ "ImageURLDetailAuto" ] }, + "proxyapi.InitiateSessionReq": { + "type": "object", + "required": [ + "bidId", + "provider", + "providerUrl", + "spend", + "user" + ], + "properties": { + "bidId": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "providerUrl": { + "type": "string" + }, + "spend": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, + "proxyapi.LogProb": { + "type": "object", + "properties": { + "bytes": { + "description": "Omitting the field if it is null", + "type": "array", + "items": { + "type": "integer" + } + }, + "logprob": { + "type": "number" + }, + "token": { + "type": "string" + }, + "top_logprobs": { + "description": "TopLogProbs is a list of the most likely tokens and their log probability, at this token position.\nIn rare cases, there may be fewer than the number of requested top_logprobs returned.", + "type": "array", + "items": { + "$ref": "#/definitions/proxyapi.TopLogProbs" + } + } + } + }, + "proxyapi.LogProbs": { + "type": "object", + "properties": { + "content": { + "description": "Content is a list of message content tokens with log probability information.", + "type": "array", + "items": { + "$ref": "#/definitions/proxyapi.LogProb" + } + } + } + }, "proxyapi.OpenAiCompletitionRequest": { "type": "object", "properties": { @@ -1131,18 +1371,140 @@ const docTemplate = `{ } } }, - "structs.CreateBidRequest": { + "proxyapi.TopLogProbs": { "type": "object", - "required": [ - "modelID", - "pricePerSecond" - ], "properties": { - "modelID": { - "type": "string" + "bytes": { + "type": "array", + "items": { + "type": "integer" + } + }, + "logprob": { + "type": "number" + }, + "token": { + "type": "string" + } + } + }, + "proxyapi.Usage": { + "type": "object", + "properties": { + "completion_tokens": { + "type": "integer" + }, + "prompt_tokens": { + "type": "integer" + }, + "total_tokens": { + "type": "integer" + } + } + }, + "structs.AllowanceRes": { + "type": "object", + "properties": { + "allowance": { + "type": "string", + "example": "100000000" + } + } + }, + "structs.AmountReq": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "string" + } + } + }, + "structs.BalanceRes": { + "type": "object", + "properties": { + "balance": { + "type": "string" + } + } + }, + "structs.Bid": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "deletedAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "modelAgentId": { + "type": "string" + }, + "nonce": { + "type": "string" }, "pricePerSecond": { - "$ref": "#/definitions/lib.BigInt" + "type": "string" + }, + "provider": { + "type": "string" + } + } + }, + "structs.BidRes": { + "type": "object", + "properties": { + "bid": { + "$ref": "#/definitions/structs.Bid" + } + } + }, + "structs.BidsRes": { + "type": "object", + "properties": { + "bids": { + "type": "array", + "items": { + "$ref": "#/definitions/structs.Bid" + } + } + } + }, + "structs.BlockRes": { + "type": "object", + "properties": { + "block": { + "type": "integer", + "example": 1234 + } + } + }, + "structs.BudgetRes": { + "type": "object", + "properties": { + "budget": { + "type": "string", + "example": "100000000" + } + } + }, + "structs.CreateBidRequest": { + "type": "object", + "required": [ + "modelID", + "pricePerSecond" + ], + "properties": { + "modelID": { + "type": "string" + }, + "pricePerSecond": { + "type": "string" } } }, @@ -1157,21 +1519,26 @@ const docTemplate = `{ ], "properties": { "fee": { - "$ref": "#/definitions/lib.BigInt" + "type": "string", + "example": "123000000000" }, "id": { - "type": "string" + "type": "string", + "example": "0x1234" }, "ipfsID": { - "type": "string" + "type": "string", + "example": "0x1234" }, "name": { "type": "string", "maxLength": 64, - "minLength": 1 + "minLength": 1, + "example": "Llama 2.0" }, "stake": { - "$ref": "#/definitions/lib.BigInt" + "type": "string", + "example": "123000000000" }, "tags": { "type": "array", @@ -1191,26 +1558,361 @@ const docTemplate = `{ ], "properties": { "endpoint": { + "type": "string", + "example": "mycoolmornode.domain.com:3989" + }, + "stake": { + "type": "string", + "example": "123000000000" + } + } + }, + "structs.Model": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "fee": { + "type": "string" + }, + "id": { + "type": "string" + }, + "ipfsCID": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "owner": { "type": "string" }, "stake": { - "$ref": "#/definitions/lib.BigInt" + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "structs.ModelRes": { + "type": "object", + "properties": { + "model": { + "$ref": "#/definitions/structs.Model" + } + } + }, + "structs.ModelsRes": { + "type": "object", + "properties": { + "models": { + "type": "array", + "items": { + "$ref": "#/definitions/structs.Model" + } } } }, "structs.OpenSessionRequest": { - "type": "object" + "type": "object", + "required": [ + "approval", + "approvalSig", + "stake" + ], + "properties": { + "approval": { + "type": "string", + "format": "hex", + "example": "0x1234" + }, + "approvalSig": { + "type": "string", + "format": "hex", + "example": "0x1234" + }, + "stake": { + "type": "string", + "example": "123000000000" + } + } + }, + "structs.OpenSessionRes": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "example": "0x1234" + } + } }, "structs.OpenSessionWithDurationRequest": { "type": "object", "properties": { "sessionDuration": { - "$ref": "#/definitions/lib.BigInt" + "type": "string" + } + } + }, + "structs.Provider": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "stake": { + "type": "string" + } + } + }, + "structs.ProviderRes": { + "type": "object", + "properties": { + "provider": { + "$ref": "#/definitions/structs.Provider" + } + } + }, + "structs.ProvidersRes": { + "type": "object", + "properties": { + "providers": { + "type": "array", + "items": { + "$ref": "#/definitions/structs.Provider" + } + } + } + }, + "structs.RawTransaction": { + "type": "object", + "properties": { + "blockHash": { + "type": "string" + }, + "blockNumber": { + "type": "string" + }, + "confirmations": { + "type": "string" + }, + "contractAddress": { + "type": "string" + }, + "cumulativeGasUsed": { + "type": "string" + }, + "from": { + "type": "string" + }, + "functionName": { + "type": "string" + }, + "gas": { + "type": "string" + }, + "gasPrice": { + "type": "string" + }, + "gasPriceBid": { + "type": "string" + }, + "gasUsed": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "input": { + "type": "string" + }, + "isError": { + "type": "string" + }, + "methodId": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "timeStamp": { + "type": "string" + }, + "to": { + "type": "string" + }, + "transactionIndex": { + "type": "string" + }, + "txreceipt_status": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "structs.ScoredBid": { + "type": "object", + "properties": { + "bid": { + "$ref": "#/definitions/structs.Bid" + }, + "id": { + "type": "string" + }, + "score": { + "type": "number" + } + } + }, + "structs.ScoredBidsRes": { + "type": "object", + "properties": { + "bids": { + "type": "array", + "items": { + "$ref": "#/definitions/structs.ScoredBid" + } } } }, "structs.SendRequest": { - "type": "object" + "type": "object", + "required": [ + "amount", + "to" + ], + "properties": { + "amount": { + "type": "string" + }, + "to": { + "type": "string" + } + } + }, + "structs.Session": { + "type": "object", + "properties": { + "bidID": { + "type": "string" + }, + "closedAt": { + "type": "string" + }, + "closeoutReceipt": { + "type": "string" + }, + "closeoutType": { + "type": "string" + }, + "endsAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "modelAgentId": { + "type": "string" + }, + "openedAt": { + "type": "string" + }, + "pricePerSecond": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "providerWithdrawnAmount": { + "type": "string" + }, + "stake": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, + "structs.SessionRes": { + "type": "object", + "properties": { + "session": { + "$ref": "#/definitions/structs.Session" + } + } + }, + "structs.SessionsRes": { + "type": "object", + "properties": { + "sessions": { + "type": "array", + "items": { + "$ref": "#/definitions/structs.Session" + } + } + } + }, + "structs.SupplyRes": { + "type": "object", + "properties": { + "supply": { + "type": "string", + "example": "100000000" + } + } + }, + "structs.TokenBalanceRes": { + "type": "object", + "properties": { + "eth": { + "type": "string", + "example": "100000000" + }, + "mor": { + "type": "string", + "example": "100000000" + } + } + }, + "structs.TransactionsRes": { + "type": "object", + "properties": { + "transactions": { + "type": "array", + "items": { + "$ref": "#/definitions/structs.RawTransaction" + } + } + } + }, + "structs.TxRes": { + "type": "object", + "properties": { + "tx": { + "type": "string", + "example": "0x1234" + } + } }, "system.ConfigResponse": { "type": "object", @@ -1257,10 +1959,25 @@ const docTemplate = `{ ], "properties": { "privateKey": { - "type": "array", - "items": { - "type": "integer" - } + "type": "string" + } + } + }, + "walletapi.WalletRes": { + "type": "object", + "properties": { + "address": { + "type": "string", + "example": "0x1234" + } + } + }, + "walletapi.statusRes": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "ok" } } } @@ -1277,8 +1994,8 @@ var SwaggerInfo = &swag.Spec{ Host: "", BasePath: "/", Schemes: []string{}, - Title: "ApiBus Example API", - Description: "This is a sample server celler server.", + Title: "Morpheus Lumerin Node API", + Description: "API for Morpheus Lumerin Node", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, LeftDelim: "{{", diff --git a/proxy-router/docs/swagger.json b/proxy-router/docs/swagger.json index 8651357e..85c3c951 100644 --- a/proxy-router/docs/swagger.json +++ b/proxy-router/docs/swagger.json @@ -1,8 +1,8 @@ { "swagger": "2.0", "info": { - "description": "This is a sample server celler server.", - "title": "ApiBus Example API", + "description": "API for Morpheus Lumerin Node", + "title": "Morpheus Lumerin Node API", "termsOfService": "http://swagger.io/terms/", "contact": {}, "version": "1.0" @@ -16,7 +16,7 @@ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Get Allowance for MOR", "parameters": [ @@ -32,7 +32,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.AllowanceRes" } } } @@ -45,7 +45,7 @@ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Approve MOR allowance", "parameters": [ @@ -68,7 +68,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.TxRes" } } } @@ -81,14 +81,14 @@ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Get ETH and MOR balance", "responses": { "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.TokenBalanceRes" } } } @@ -103,7 +103,7 @@ "application/json" ], "tags": [ - "wallet" + "bids" ], "summary": "Creates bid in blockchain", "parameters": [ @@ -121,13 +121,42 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.BidRes" + } + } + } + } + }, + "/blockchain/bids/{id}": { + "get": { + "description": "Get bid from blockchain by ID", + "produces": [ + "application/json" + ], + "tags": [ + "bids" + ], + "summary": "Get Bid by ID", + "parameters": [ + { + "type": "string", + "description": "Bid ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/structs.BidRes" } } } } }, - "/blockchain/bids/:id/session": { + "/blockchain/bids/{id}/session": { "post": { "description": "Full flow to open a session by bidId", "consumes": [ @@ -162,7 +191,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.OpenSessionRes" } } } @@ -175,14 +204,14 @@ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Get Latest Block", "responses": { "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.BlockRes" } } } @@ -195,17 +224,14 @@ "application/json" ], "tags": [ - "wallet" + "models" ], "summary": "Get models list", "responses": { "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.ModelsRes" } } } @@ -218,7 +244,7 @@ "application/json" ], "tags": [ - "wallet" + "models" ], "summary": "Creates model in blockchain", "parameters": [ @@ -236,7 +262,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.ModelRes" } } } @@ -249,7 +275,7 @@ "application/json" ], "tags": [ - "wallet" + "bids" ], "summary": "Get Active Bids by Model", "parameters": [ @@ -265,10 +291,36 @@ "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.BidsRes" + } + } + } + } + }, + "/blockchain/models/{id}/bids/rated": { + "get": { + "description": "Get rated bids from blockchain by model", + "produces": [ + "application/json" + ], + "tags": [ + "bids" + ], + "summary": "Get Rated Bids", + "parameters": [ + { + "type": "string", + "description": "Model ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/structs.ScoredBidsRes" } } } @@ -309,7 +361,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.OpenSessionRes" } } } @@ -322,17 +374,14 @@ "application/json" ], "tags": [ - "wallet" + "providers" ], "summary": "Get providers list", "responses": { "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.ProvidersRes" } } } @@ -345,7 +394,7 @@ "application/json" ], "tags": [ - "wallet" + "providers" ], "summary": "Creates or updates provider in blockchain", "parameters": [ @@ -363,7 +412,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.ProviderRes" } } } @@ -376,7 +425,7 @@ "application/json" ], "tags": [ - "wallet" + "bids" ], "summary": "Get Bids by Provider", "parameters": [ @@ -404,10 +453,7 @@ "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.BidsRes" } } } @@ -420,7 +466,7 @@ "application/json" ], "tags": [ - "wallet" + "bids" ], "summary": "Get Bids by Provider", "parameters": [ @@ -436,10 +482,7 @@ "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.BidsRes" } } } @@ -452,7 +495,7 @@ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Send Eth", "parameters": [ @@ -470,7 +513,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.TxRes" } } } @@ -483,7 +526,7 @@ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Send Mor", "parameters": [ @@ -501,7 +544,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.TxRes" } } } @@ -547,10 +590,7 @@ "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.SessionsRes" } } } @@ -582,7 +622,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.OpenSessionRes" } } } @@ -595,14 +635,43 @@ "application/json" ], "tags": [ - "wallet" + "sessions" ], "summary": "Get Todays Budget", "responses": { "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.BudgetRes" + } + } + } + } + }, + "/blockchain/sessions/{id}": { + "get": { + "description": "Returns session by ID", + "produces": [ + "application/json" + ], + "tags": [ + "sessions" + ], + "summary": "Get session", + "parameters": [ + { + "type": "string", + "description": "Session ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/structs.SessionRes" } } } @@ -631,7 +700,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.TxRes" } } } @@ -644,14 +713,14 @@ "application/json" ], "tags": [ - "wallet" + "sessions" ], "summary": "Get Token Supply", "responses": { "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.SupplyRes" } } } @@ -664,7 +733,7 @@ "application/json" ], "tags": [ - "wallet" + "transactions" ], "summary": "Get Transactions", "parameters": [ @@ -685,10 +754,7 @@ "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "type": "object" - } + "$ref": "#/definitions/structs.TransactionsRes" } } } @@ -764,14 +830,25 @@ "application/json" ], "tags": [ - "sessions" + "chat" ], "summary": "Initiate Session with Provider", + "parameters": [ + { + "description": "Initiate Session", + "name": "initiateSession", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/proxyapi.InitiateSessionReq" + } + } + ], "responses": { "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/morrpcmesssage.SessionRes" } } } @@ -794,7 +871,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/structs.SendRequest" + "$ref": "#/definitions/structs.AmountReq" } }, { @@ -809,7 +886,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.TxRes" } } } @@ -838,7 +915,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/structs.BalanceRes" } } } @@ -848,13 +925,27 @@ "post": { "description": "Send prompt to a local or remote model based on session id in header", "produces": [ - "application/json" + "text/event-stream" ], "tags": [ - "wallet" + "chat" ], "summary": "Send Local Or Remote Prompt", "parameters": [ + { + "type": "string", + "format": "hex32", + "description": "Session ID", + "name": "session_id", + "in": "header" + }, + { + "type": "string", + "format": "hex32", + "description": "Model ID", + "name": "model_id", + "in": "header" + }, { "description": "Prompt", "name": "prompt", @@ -863,19 +954,13 @@ "schema": { "$ref": "#/definitions/proxyapi.OpenAiCompletitionRequest" } - }, - { - "type": "string", - "description": "Session ID", - "name": "session_id", - "in": "header" } ], "responses": { "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/proxyapi.ChatCompletionResponse" } } } @@ -887,7 +972,7 @@ "application/json" ], "tags": [ - "wallet" + "chat" ], "summary": "Get local models", "responses": { @@ -917,7 +1002,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/walletapi.WalletRes" } } } @@ -946,7 +1031,7 @@ "200": { "description": "OK", "schema": { - "type": "object" + "$ref": "#/definitions/walletapi.statusRes" } } } @@ -957,6 +1042,9 @@ "aiengine.LocalModel": { "type": "object", "properties": { + "apiType": { + "type": "string" + }, "id": { "type": "string" }, @@ -968,8 +1056,58 @@ } } }, - "lib.BigInt": { - "type": "object" + "morrpcmesssage.SessionRes": { + "type": "object", + "required": [ + "approval", + "approvalSig", + "message", + "signature", + "timestamp", + "user" + ], + "properties": { + "approval": { + "type": "string" + }, + "approvalSig": { + "type": "string" + }, + "message": { + "type": "string" + }, + "signature": { + "type": "string" + }, + "timestamp": { + "type": "integer" + }, + "user": { + "type": "string" + } + } + }, + "proxyapi.ChatCompletionChoice": { + "type": "object", + "properties": { + "finish_reason": { + "description": "FinishReason\nstop: API returned complete message,\nor a message terminated by one of the stop sequences provided via the stop parameter\nlength: Incomplete model output due to max_tokens parameter or token limit\nfunction_call: The model decided to call a function\ncontent_filter: Omitted content due to a flag from our content filters\nnull: API response still in progress or incomplete", + "allOf": [ + { + "$ref": "#/definitions/proxyapi.FinishReason" + } + ] + }, + "index": { + "type": "integer" + }, + "logprobs": { + "$ref": "#/definitions/proxyapi.LogProbs" + }, + "message": { + "$ref": "#/definitions/proxyapi.ChatCompletionMessage" + } + } }, "proxyapi.ChatCompletionMessage": { "type": "object", @@ -996,6 +1134,35 @@ } } }, + "proxyapi.ChatCompletionResponse": { + "type": "object", + "properties": { + "choices": { + "type": "array", + "items": { + "$ref": "#/definitions/proxyapi.ChatCompletionChoice" + } + }, + "created": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "model": { + "type": "string" + }, + "object": { + "type": "string" + }, + "system_fingerprint": { + "type": "string" + }, + "usage": { + "$ref": "#/definitions/proxyapi.Usage" + } + } + }, "proxyapi.ChatCompletionResponseFormat": { "type": "object", "properties": { @@ -1040,6 +1207,15 @@ "ChatMessagePartTypeImageURL" ] }, + "proxyapi.FinishReason": { + "type": "string", + "enum": [ + "stop" + ], + "x-enum-varnames": [ + "FinishReasonStop" + ] + }, "proxyapi.ImageURLDetail": { "type": "string", "enum": [ @@ -1053,6 +1229,70 @@ "ImageURLDetailAuto" ] }, + "proxyapi.InitiateSessionReq": { + "type": "object", + "required": [ + "bidId", + "provider", + "providerUrl", + "spend", + "user" + ], + "properties": { + "bidId": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "providerUrl": { + "type": "string" + }, + "spend": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, + "proxyapi.LogProb": { + "type": "object", + "properties": { + "bytes": { + "description": "Omitting the field if it is null", + "type": "array", + "items": { + "type": "integer" + } + }, + "logprob": { + "type": "number" + }, + "token": { + "type": "string" + }, + "top_logprobs": { + "description": "TopLogProbs is a list of the most likely tokens and their log probability, at this token position.\nIn rare cases, there may be fewer than the number of requested top_logprobs returned.", + "type": "array", + "items": { + "$ref": "#/definitions/proxyapi.TopLogProbs" + } + } + } + }, + "proxyapi.LogProbs": { + "type": "object", + "properties": { + "content": { + "description": "Content is a list of message content tokens with log probability information.", + "type": "array", + "items": { + "$ref": "#/definitions/proxyapi.LogProb" + } + } + } + }, "proxyapi.OpenAiCompletitionRequest": { "type": "object", "properties": { @@ -1124,18 +1364,140 @@ } } }, - "structs.CreateBidRequest": { + "proxyapi.TopLogProbs": { "type": "object", - "required": [ - "modelID", - "pricePerSecond" - ], "properties": { - "modelID": { - "type": "string" + "bytes": { + "type": "array", + "items": { + "type": "integer" + } + }, + "logprob": { + "type": "number" + }, + "token": { + "type": "string" + } + } + }, + "proxyapi.Usage": { + "type": "object", + "properties": { + "completion_tokens": { + "type": "integer" + }, + "prompt_tokens": { + "type": "integer" + }, + "total_tokens": { + "type": "integer" + } + } + }, + "structs.AllowanceRes": { + "type": "object", + "properties": { + "allowance": { + "type": "string", + "example": "100000000" + } + } + }, + "structs.AmountReq": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "string" + } + } + }, + "structs.BalanceRes": { + "type": "object", + "properties": { + "balance": { + "type": "string" + } + } + }, + "structs.Bid": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "deletedAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "modelAgentId": { + "type": "string" + }, + "nonce": { + "type": "string" }, "pricePerSecond": { - "$ref": "#/definitions/lib.BigInt" + "type": "string" + }, + "provider": { + "type": "string" + } + } + }, + "structs.BidRes": { + "type": "object", + "properties": { + "bid": { + "$ref": "#/definitions/structs.Bid" + } + } + }, + "structs.BidsRes": { + "type": "object", + "properties": { + "bids": { + "type": "array", + "items": { + "$ref": "#/definitions/structs.Bid" + } + } + } + }, + "structs.BlockRes": { + "type": "object", + "properties": { + "block": { + "type": "integer", + "example": 1234 + } + } + }, + "structs.BudgetRes": { + "type": "object", + "properties": { + "budget": { + "type": "string", + "example": "100000000" + } + } + }, + "structs.CreateBidRequest": { + "type": "object", + "required": [ + "modelID", + "pricePerSecond" + ], + "properties": { + "modelID": { + "type": "string" + }, + "pricePerSecond": { + "type": "string" } } }, @@ -1150,21 +1512,26 @@ ], "properties": { "fee": { - "$ref": "#/definitions/lib.BigInt" + "type": "string", + "example": "123000000000" }, "id": { - "type": "string" + "type": "string", + "example": "0x1234" }, "ipfsID": { - "type": "string" + "type": "string", + "example": "0x1234" }, "name": { "type": "string", "maxLength": 64, - "minLength": 1 + "minLength": 1, + "example": "Llama 2.0" }, "stake": { - "$ref": "#/definitions/lib.BigInt" + "type": "string", + "example": "123000000000" }, "tags": { "type": "array", @@ -1184,26 +1551,361 @@ ], "properties": { "endpoint": { + "type": "string", + "example": "mycoolmornode.domain.com:3989" + }, + "stake": { + "type": "string", + "example": "123000000000" + } + } + }, + "structs.Model": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "fee": { + "type": "string" + }, + "id": { + "type": "string" + }, + "ipfsCID": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "owner": { "type": "string" }, "stake": { - "$ref": "#/definitions/lib.BigInt" + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "structs.ModelRes": { + "type": "object", + "properties": { + "model": { + "$ref": "#/definitions/structs.Model" + } + } + }, + "structs.ModelsRes": { + "type": "object", + "properties": { + "models": { + "type": "array", + "items": { + "$ref": "#/definitions/structs.Model" + } } } }, "structs.OpenSessionRequest": { - "type": "object" + "type": "object", + "required": [ + "approval", + "approvalSig", + "stake" + ], + "properties": { + "approval": { + "type": "string", + "format": "hex", + "example": "0x1234" + }, + "approvalSig": { + "type": "string", + "format": "hex", + "example": "0x1234" + }, + "stake": { + "type": "string", + "example": "123000000000" + } + } + }, + "structs.OpenSessionRes": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "example": "0x1234" + } + } }, "structs.OpenSessionWithDurationRequest": { "type": "object", "properties": { "sessionDuration": { - "$ref": "#/definitions/lib.BigInt" + "type": "string" + } + } + }, + "structs.Provider": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "isDeleted": { + "type": "boolean" + }, + "stake": { + "type": "string" + } + } + }, + "structs.ProviderRes": { + "type": "object", + "properties": { + "provider": { + "$ref": "#/definitions/structs.Provider" + } + } + }, + "structs.ProvidersRes": { + "type": "object", + "properties": { + "providers": { + "type": "array", + "items": { + "$ref": "#/definitions/structs.Provider" + } + } + } + }, + "structs.RawTransaction": { + "type": "object", + "properties": { + "blockHash": { + "type": "string" + }, + "blockNumber": { + "type": "string" + }, + "confirmations": { + "type": "string" + }, + "contractAddress": { + "type": "string" + }, + "cumulativeGasUsed": { + "type": "string" + }, + "from": { + "type": "string" + }, + "functionName": { + "type": "string" + }, + "gas": { + "type": "string" + }, + "gasPrice": { + "type": "string" + }, + "gasPriceBid": { + "type": "string" + }, + "gasUsed": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "input": { + "type": "string" + }, + "isError": { + "type": "string" + }, + "methodId": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "timeStamp": { + "type": "string" + }, + "to": { + "type": "string" + }, + "transactionIndex": { + "type": "string" + }, + "txreceipt_status": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "structs.ScoredBid": { + "type": "object", + "properties": { + "bid": { + "$ref": "#/definitions/structs.Bid" + }, + "id": { + "type": "string" + }, + "score": { + "type": "number" + } + } + }, + "structs.ScoredBidsRes": { + "type": "object", + "properties": { + "bids": { + "type": "array", + "items": { + "$ref": "#/definitions/structs.ScoredBid" + } } } }, "structs.SendRequest": { - "type": "object" + "type": "object", + "required": [ + "amount", + "to" + ], + "properties": { + "amount": { + "type": "string" + }, + "to": { + "type": "string" + } + } + }, + "structs.Session": { + "type": "object", + "properties": { + "bidID": { + "type": "string" + }, + "closedAt": { + "type": "string" + }, + "closeoutReceipt": { + "type": "string" + }, + "closeoutType": { + "type": "string" + }, + "endsAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "modelAgentId": { + "type": "string" + }, + "openedAt": { + "type": "string" + }, + "pricePerSecond": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "providerWithdrawnAmount": { + "type": "string" + }, + "stake": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, + "structs.SessionRes": { + "type": "object", + "properties": { + "session": { + "$ref": "#/definitions/structs.Session" + } + } + }, + "structs.SessionsRes": { + "type": "object", + "properties": { + "sessions": { + "type": "array", + "items": { + "$ref": "#/definitions/structs.Session" + } + } + } + }, + "structs.SupplyRes": { + "type": "object", + "properties": { + "supply": { + "type": "string", + "example": "100000000" + } + } + }, + "structs.TokenBalanceRes": { + "type": "object", + "properties": { + "eth": { + "type": "string", + "example": "100000000" + }, + "mor": { + "type": "string", + "example": "100000000" + } + } + }, + "structs.TransactionsRes": { + "type": "object", + "properties": { + "transactions": { + "type": "array", + "items": { + "$ref": "#/definitions/structs.RawTransaction" + } + } + } + }, + "structs.TxRes": { + "type": "object", + "properties": { + "tx": { + "type": "string", + "example": "0x1234" + } + } }, "system.ConfigResponse": { "type": "object", @@ -1250,10 +1952,25 @@ ], "properties": { "privateKey": { - "type": "array", - "items": { - "type": "integer" - } + "type": "string" + } + } + }, + "walletapi.WalletRes": { + "type": "object", + "properties": { + "address": { + "type": "string", + "example": "0x1234" + } + } + }, + "walletapi.statusRes": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "ok" } } } diff --git a/proxy-router/docs/swagger.yaml b/proxy-router/docs/swagger.yaml index 62f0bf75..96f08d04 100644 --- a/proxy-router/docs/swagger.yaml +++ b/proxy-router/docs/swagger.yaml @@ -2,6 +2,8 @@ basePath: / definitions: aiengine.LocalModel: properties: + apiType: + type: string id: type: string model: @@ -9,7 +11,47 @@ definitions: name: type: string type: object - lib.BigInt: + morrpcmesssage.SessionRes: + properties: + approval: + type: string + approvalSig: + type: string + message: + type: string + signature: + type: string + timestamp: + type: integer + user: + type: string + required: + - approval + - approvalSig + - message + - signature + - timestamp + - user + type: object + proxyapi.ChatCompletionChoice: + properties: + finish_reason: + allOf: + - $ref: '#/definitions/proxyapi.FinishReason' + description: |- + FinishReason + stop: API returned complete message, + or a message terminated by one of the stop sequences provided via the stop parameter + length: Incomplete model output due to max_tokens parameter or token limit + function_call: The model decided to call a function + content_filter: Omitted content due to a flag from our content filters + null: API response still in progress or incomplete + index: + type: integer + logprobs: + $ref: '#/definitions/proxyapi.LogProbs' + message: + $ref: '#/definitions/proxyapi.ChatCompletionMessage' type: object proxyapi.ChatCompletionMessage: properties: @@ -33,6 +75,25 @@ definitions: assistant's prior request to call a tool. type: string type: object + proxyapi.ChatCompletionResponse: + properties: + choices: + items: + $ref: '#/definitions/proxyapi.ChatCompletionChoice' + type: array + created: + type: integer + id: + type: string + model: + type: string + object: + type: string + system_fingerprint: + type: string + usage: + $ref: '#/definitions/proxyapi.Usage' + type: object proxyapi.ChatCompletionResponseFormat: properties: type: @@ -62,6 +123,12 @@ definitions: x-enum-varnames: - ChatMessagePartTypeText - ChatMessagePartTypeImageURL + proxyapi.FinishReason: + enum: + - stop + type: string + x-enum-varnames: + - FinishReasonStop proxyapi.ImageURLDetail: enum: - high @@ -72,6 +139,53 @@ definitions: - ImageURLDetailHigh - ImageURLDetailLow - ImageURLDetailAuto + proxyapi.InitiateSessionReq: + properties: + bidId: + type: string + provider: + type: string + providerUrl: + type: string + spend: + type: string + user: + type: string + required: + - bidId + - provider + - providerUrl + - spend + - user + type: object + proxyapi.LogProb: + properties: + bytes: + description: Omitting the field if it is null + items: + type: integer + type: array + logprob: + type: number + token: + type: string + top_logprobs: + description: |- + TopLogProbs is a list of the most likely tokens and their log probability, at this token position. + In rare cases, there may be fewer than the number of requested top_logprobs returned. + items: + $ref: '#/definitions/proxyapi.TopLogProbs' + type: array + type: object + proxyapi.LogProbs: + properties: + content: + description: Content is a list of message content tokens with log probability + information. + items: + $ref: '#/definitions/proxyapi.LogProb' + type: array + type: object proxyapi.OpenAiCompletitionRequest: properties: frequency_penalty: @@ -129,12 +243,91 @@ definitions: user: type: string type: object + proxyapi.TopLogProbs: + properties: + bytes: + items: + type: integer + type: array + logprob: + type: number + token: + type: string + type: object + proxyapi.Usage: + properties: + completion_tokens: + type: integer + prompt_tokens: + type: integer + total_tokens: + type: integer + type: object + structs.AllowanceRes: + properties: + allowance: + example: "100000000" + type: string + type: object + structs.AmountReq: + properties: + amount: + type: string + required: + - amount + type: object + structs.BalanceRes: + properties: + balance: + type: string + type: object + structs.Bid: + properties: + createdAt: + type: string + deletedAt: + type: string + id: + type: string + modelAgentId: + type: string + nonce: + type: string + pricePerSecond: + type: string + provider: + type: string + type: object + structs.BidRes: + properties: + bid: + $ref: '#/definitions/structs.Bid' + type: object + structs.BidsRes: + properties: + bids: + items: + $ref: '#/definitions/structs.Bid' + type: array + type: object + structs.BlockRes: + properties: + block: + example: 1234 + type: integer + type: object + structs.BudgetRes: + properties: + budget: + example: "100000000" + type: string + type: object structs.CreateBidRequest: properties: modelID: type: string pricePerSecond: - $ref: '#/definitions/lib.BigInt' + type: string required: - modelID - pricePerSecond @@ -142,17 +335,22 @@ definitions: structs.CreateModelRequest: properties: fee: - $ref: '#/definitions/lib.BigInt' + example: "123000000000" + type: string id: + example: "0x1234" type: string ipfsID: + example: "0x1234" type: string name: + example: Llama 2.0 maxLength: 64 minLength: 1 type: string stake: - $ref: '#/definitions/lib.BigInt' + example: "123000000000" + type: string tags: items: type: string @@ -169,21 +367,243 @@ definitions: structs.CreateProviderRequest: properties: endpoint: + example: mycoolmornode.domain.com:3989 type: string stake: - $ref: '#/definitions/lib.BigInt' + example: "123000000000" + type: string required: - endpoint - stake type: object + structs.Model: + properties: + createdAt: + type: string + fee: + type: string + id: + type: string + ipfsCID: + type: string + isDeleted: + type: boolean + name: + type: string + owner: + type: string + stake: + type: string + tags: + items: + type: string + type: array + type: object + structs.ModelRes: + properties: + model: + $ref: '#/definitions/structs.Model' + type: object + structs.ModelsRes: + properties: + models: + items: + $ref: '#/definitions/structs.Model' + type: array + type: object structs.OpenSessionRequest: + properties: + approval: + example: "0x1234" + format: hex + type: string + approvalSig: + example: "0x1234" + format: hex + type: string + stake: + example: "123000000000" + type: string + required: + - approval + - approvalSig + - stake + type: object + structs.OpenSessionRes: + properties: + sessionID: + example: "0x1234" + type: string type: object structs.OpenSessionWithDurationRequest: properties: sessionDuration: - $ref: '#/definitions/lib.BigInt' + type: string + type: object + structs.Provider: + properties: + address: + type: string + createdAt: + type: string + endpoint: + type: string + isDeleted: + type: boolean + stake: + type: string + type: object + structs.ProviderRes: + properties: + provider: + $ref: '#/definitions/structs.Provider' + type: object + structs.ProvidersRes: + properties: + providers: + items: + $ref: '#/definitions/structs.Provider' + type: array + type: object + structs.RawTransaction: + properties: + blockHash: + type: string + blockNumber: + type: string + confirmations: + type: string + contractAddress: + type: string + cumulativeGasUsed: + type: string + from: + type: string + functionName: + type: string + gas: + type: string + gasPrice: + type: string + gasPriceBid: + type: string + gasUsed: + type: string + hash: + type: string + input: + type: string + isError: + type: string + methodId: + type: string + nonce: + type: string + timeStamp: + type: string + to: + type: string + transactionIndex: + type: string + txreceipt_status: + type: string + value: + type: string + type: object + structs.ScoredBid: + properties: + bid: + $ref: '#/definitions/structs.Bid' + id: + type: string + score: + type: number + type: object + structs.ScoredBidsRes: + properties: + bids: + items: + $ref: '#/definitions/structs.ScoredBid' + type: array type: object structs.SendRequest: + properties: + amount: + type: string + to: + type: string + required: + - amount + - to + type: object + structs.Session: + properties: + bidID: + type: string + closedAt: + type: string + closeoutReceipt: + type: string + closeoutType: + type: string + endsAt: + type: string + id: + type: string + modelAgentId: + type: string + openedAt: + type: string + pricePerSecond: + type: string + provider: + type: string + providerWithdrawnAmount: + type: string + stake: + type: string + user: + type: string + type: object + structs.SessionRes: + properties: + session: + $ref: '#/definitions/structs.Session' + type: object + structs.SessionsRes: + properties: + sessions: + items: + $ref: '#/definitions/structs.Session' + type: array + type: object + structs.SupplyRes: + properties: + supply: + example: "100000000" + type: string + type: object + structs.TokenBalanceRes: + properties: + eth: + example: "100000000" + type: string + mor: + example: "100000000" + type: string + type: object + structs.TransactionsRes: + properties: + transactions: + items: + $ref: '#/definitions/structs.RawTransaction' + type: array + type: object + structs.TxRes: + properties: + tx: + example: "0x1234" + type: string type: object system.ConfigResponse: properties: @@ -213,20 +633,30 @@ definitions: walletapi.SetupWalletReqBody: properties: privateKey: - items: - type: integer - type: array + type: string required: - privateKey type: object + walletapi.WalletRes: + properties: + address: + example: "0x1234" + type: string + type: object + walletapi.statusRes: + properties: + status: + example: ok + type: string + type: object externalDocs: description: OpenAPI url: https://swagger.io/resources/open-api/ info: contact: {} - description: This is a sample server celler server. + description: API for Morpheus Lumerin Node termsOfService: http://swagger.io/terms/ - title: ApiBus Example API + title: Morpheus Lumerin Node API version: "1.0" paths: /blockchain/allowance: @@ -244,10 +674,10 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.AllowanceRes' summary: Get Allowance for MOR tags: - - wallet + - transactions /blockchain/approve: post: description: Approve MOR allowance for spender @@ -268,10 +698,10 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.TxRes' summary: Approve MOR allowance tags: - - wallet + - transactions /blockchain/balance: get: description: Get ETH and MOR balance of the user @@ -281,10 +711,10 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.TokenBalanceRes' summary: Get ETH and MOR balance tags: - - wallet + - transactions /blockchain/bids: post: consumes: @@ -302,11 +732,30 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.BidRes' summary: Creates bid in blockchain tags: - - wallet - /blockchain/bids/:id/session: + - bids + /blockchain/bids/{id}: + get: + description: Get bid from blockchain by ID + parameters: + - description: Bid ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/structs.BidRes' + summary: Get Bid by ID + tags: + - bids + /blockchain/bids/{id}/session: post: consumes: - application/json @@ -329,7 +778,7 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.OpenSessionRes' summary: Open Session by bidId in blockchain tags: - sessions @@ -342,10 +791,10 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.BlockRes' summary: Get Latest Block tags: - - wallet + - transactions /blockchain/models: get: description: Get models list from blokchain @@ -355,12 +804,10 @@ paths: "200": description: OK schema: - items: - type: object - type: array + $ref: '#/definitions/structs.ModelsRes' summary: Get models list tags: - - wallet + - models post: consumes: - application/json @@ -377,11 +824,49 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.ModelRes' summary: Creates model in blockchain tags: - - wallet - /blockchain/models/:id/session: + - models + /blockchain/models/{id}/bids: + get: + description: Get bids from blockchain by model agent + parameters: + - description: ModelAgent ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/structs.BidsRes' + summary: Get Active Bids by Model + tags: + - bids + /blockchain/models/{id}/bids/rated: + get: + description: Get rated bids from blockchain by model + parameters: + - description: Model ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/structs.ScoredBidsRes' + summary: Get Rated Bids + tags: + - bids + /blockchain/models/{id}/session: post: consumes: - application/json @@ -404,7 +889,7 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.OpenSessionRes' summary: Open Session by ModelID in blockchain tags: - sessions @@ -417,12 +902,10 @@ paths: "200": description: OK schema: - items: - type: object - type: array + $ref: '#/definitions/structs.ProvidersRes' summary: Get providers list tags: - - wallet + - providers post: consumes: - application/json @@ -439,10 +922,10 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.ProviderRes' summary: Creates or updates provider in blockchain tags: - - wallet + - providers /blockchain/providers/{id}/bids: get: description: Get bids from blockchain by provider @@ -466,12 +949,10 @@ paths: "200": description: OK schema: - items: - type: object - type: array + $ref: '#/definitions/structs.BidsRes' summary: Get Bids by Provider tags: - - wallet + - bids /blockchain/providers/{id}/bids/active: get: description: Get bids from blockchain by provider @@ -487,12 +968,10 @@ paths: "200": description: OK schema: - items: - type: object - type: array + $ref: '#/definitions/structs.BidsRes' summary: Get Bids by Provider tags: - - wallet + - bids /blockchain/send/eth: post: description: Send Eth to address @@ -509,10 +988,10 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.TxRes' summary: Send Eth tags: - - wallet + - transactions /blockchain/send/mor: post: description: Send Mor to address @@ -529,10 +1008,10 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.TxRes' summary: Send Mor tags: - - wallet + - transactions /blockchain/sessions: get: description: Get sessions from blockchain by user or provider @@ -559,9 +1038,7 @@ paths: "200": description: OK schema: - items: - type: object - type: array + $ref: '#/definitions/structs.SessionsRes' summary: Get Sessions tags: - sessions @@ -582,10 +1059,29 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.OpenSessionRes' summary: Open Session with Provider in blockchain tags: - sessions + /blockchain/sessions/{id}: + get: + description: Returns session by ID + parameters: + - description: Session ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/structs.SessionRes' + summary: Get session + tags: + - sessions /blockchain/sessions/{id}/close: post: description: Sends transaction in blockchain to close a session @@ -601,7 +1097,7 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.TxRes' summary: Close Session with Provider tags: - sessions @@ -614,10 +1110,10 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.BudgetRes' summary: Get Todays Budget tags: - - wallet + - sessions /blockchain/token/supply: get: description: Get MOR token supply from blockchain @@ -627,10 +1123,10 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.SupplyRes' summary: Get Token Supply tags: - - wallet + - sessions /blockchain/transactions: get: description: Get MOR and ETH transactions @@ -649,12 +1145,10 @@ paths: "200": description: OK schema: - items: - type: object - type: array + $ref: '#/definitions/structs.TransactionsRes' summary: Get Transactions tags: - - wallet + - transactions /config: get: description: Return the current config of proxy router @@ -705,7 +1199,7 @@ paths: name: claim required: true schema: - $ref: '#/definitions/structs.SendRequest' + $ref: '#/definitions/structs.AmountReq' - description: Session ID in: path name: id @@ -717,7 +1211,7 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.TxRes' summary: Claim Provider Balance tags: - sessions @@ -736,47 +1230,60 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/structs.BalanceRes' summary: Get Provider Claimable Balance tags: - sessions /proxy/sessions/initiate: post: description: sends a handshake to the provider + parameters: + - description: Initiate Session + in: body + name: initiateSession + required: true + schema: + $ref: '#/definitions/proxyapi.InitiateSessionReq' produces: - application/json responses: "200": description: OK schema: - type: object + $ref: '#/definitions/morrpcmesssage.SessionRes' summary: Initiate Session with Provider tags: - - sessions + - chat /v1/chat/completions: post: description: Send prompt to a local or remote model based on session id in header parameters: + - description: Session ID + format: hex32 + in: header + name: session_id + type: string + - description: Model ID + format: hex32 + in: header + name: model_id + type: string - description: Prompt in: body name: prompt required: true schema: $ref: '#/definitions/proxyapi.OpenAiCompletitionRequest' - - description: Session ID - in: header - name: session_id - type: string produces: - - application/json + - text/event-stream responses: "200": description: OK schema: - type: object + $ref: '#/definitions/proxyapi.ChatCompletionResponse' summary: Send Local Or Remote Prompt tags: - - wallet + - chat /v1/models: get: produces: @@ -790,7 +1297,7 @@ paths: type: array summary: Get local models tags: - - wallet + - chat /wallet: get: description: Get wallet address @@ -800,7 +1307,7 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/walletapi.WalletRes' summary: Get Wallet tags: - wallet @@ -819,7 +1326,7 @@ paths: "200": description: OK schema: - type: object + $ref: '#/definitions/walletapi.statusRes' summary: Set Wallet tags: - wallet diff --git a/proxy-router/internal/aiengine/ai_engine.go b/proxy-router/internal/aiengine/ai_engine.go index 68ecc560..2ae62843 100644 --- a/proxy-router/internal/aiengine/ai_engine.go +++ b/proxy-router/internal/aiengine/ai_engine.go @@ -15,6 +15,7 @@ import ( c "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal" constants "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/config" "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" "github.com/gin-gonic/gin" "github.com/sashabaranov/go-openai" @@ -22,10 +23,11 @@ import ( ) type AiEngine struct { - client *api.Client - baseURL string - apiKey string - log lib.ILogger + client *api.Client + baseURL string + apiKey string + modelsConfigLoader *config.ModelConfigLoader + log lib.ILogger } var ( @@ -36,10 +38,11 @@ var ( ErrJobFailed = errors.New("job failed") ) -func NewAiEngine(apiBaseURL, apiKey string, log lib.ILogger) *AiEngine { +func NewAiEngine(apiBaseURL, apiKey string, modelsConfigLoader *config.ModelConfigLoader, log lib.ILogger) *AiEngine { return &AiEngine{ - baseURL: apiBaseURL, - apiKey: apiKey, + modelsConfigLoader: modelsConfigLoader, + baseURL: apiBaseURL, + apiKey: apiKey, client: api.NewClientWithConfig(api.ClientConfig{ BaseURL: apiBaseURL, APIType: api.APITypeOpenAI, @@ -273,13 +276,22 @@ func (a *AiEngine) requestChatCompletionStream(ctx context.Context, request *api return nil, err } +func (a *AiEngine) GetModelsConfig() ([]string, []config.ModelConfig) { + return a.modelsConfigLoader.GetAll() +} + func (a *AiEngine) GetLocalModels() ([]LocalModel, error) { - models := []LocalModel{ - { - Id: "0x6a4813e866a48da528c533e706344ea853a1d3f21e37b4c8e7ffd5ff25779018", - Name: "LLama2:7b", - Model: "llama2", - }, + models := []LocalModel{} + + IDs, modelsFromConfig := a.modelsConfigLoader.GetAll() + for i, model := range modelsFromConfig { + models = append(models, LocalModel{ + Id: IDs[i], + Name: model.ModelName, + Model: model.ModelName, + ApiType: model.ApiType, + }) } + return models, nil } diff --git a/proxy-router/internal/aiengine/interfaces.go b/proxy-router/internal/aiengine/interfaces.go index e2aa7fa4..8b454dfe 100644 --- a/proxy-router/internal/aiengine/interfaces.go +++ b/proxy-router/internal/aiengine/interfaces.go @@ -27,7 +27,8 @@ type CompletionCallback func(completion interface{}) error type ProdiaImageGenerationCallback func(completion *ProdiaGenerationResult) error type LocalModel struct { - Id string - Name string - Model string + Id string + Name string + Model string + ApiType string } diff --git a/proxy-router/internal/blockchainapi/controller.go b/proxy-router/internal/blockchainapi/controller.go index 361d1292..2393cd19 100644 --- a/proxy-router/internal/blockchainapi/controller.go +++ b/proxy-router/internal/blockchainapi/controller.go @@ -2,6 +2,7 @@ package blockchainapi import ( "crypto/rand" + "fmt" "math/big" "net/http" @@ -27,28 +28,35 @@ func NewBlockchainController(service *BlockchainService, log lib.ILogger) *Block } func (c *BlockchainController) RegisterRoutes(r interfaces.Router) { - r.GET("/proxy/sessions/:id/providerClaimableBalance", c.getProviderClaimableBalance) - r.POST("/proxy/sessions/:id/providerClaim", c.claimProviderBalance) - + // transactions r.GET("/blockchain/balance", c.getBalance) - r.POST("/blockchain/send/eth", c.sendETH) - r.POST("/blockchain/send/mor", c.sendMOR) r.GET("/blockchain/transactions", c.getTransactions) r.GET("/blockchain/allowance", c.getAllowance) - r.POST("/blockchain/approve", c.approve) r.GET("/blockchain/latestBlock", c.getLatestBlock) + r.POST("/blockchain/approve", c.approve) + r.POST("/blockchain/send/eth", c.sendETH) + r.POST("/blockchain/send/mor", c.sendMOR) + // providers r.GET("/blockchain/providers", c.getAllProviders) r.POST("/blockchain/providers", c.createProvider) - r.GET("/blockchain/providers/:id/bids", c.getBidsByProvider) - r.GET("/blockchain/providers/:id/bids/active", c.getActiveBidsByProvider) + + // models r.GET("/blockchain/models", c.getAllModels) r.POST("/blockchain/models", c.createNewModel) + + // bids + r.POST("/blockchain/bids", c.createNewBid) + r.GET("/blockchain/bids/:id", c.getBidByID) r.GET("/blockchain/models/:id/bids", c.getBidsByModelAgent) r.GET("/blockchain/models/:id/bids/rated", c.getRatedBids) r.GET("/blockchain/models/:id/bids/active", c.getActiveBidsByModel) - r.GET("/blockchain/bids/:id", c.getBidByID) - r.POST("/blockchain/bids", c.createNewBid) + r.GET("/blockchain/providers/:id/bids", c.getBidsByProvider) + r.GET("/blockchain/providers/:id/bids/active", c.getActiveBidsByProvider) + + // sessions + r.GET("/proxy/sessions/:id/providerClaimableBalance", c.getProviderClaimableBalance) + r.POST("/proxy/sessions/:id/providerClaim", c.claimProviderBalance) r.GET("/blockchain/sessions", c.getSessions) r.GET("/blockchain/sessions/:id", c.getSession) r.POST("/blockchain/sessions", c.openSession) @@ -61,572 +69,584 @@ func (c *BlockchainController) RegisterRoutes(r interfaces.Router) { // GetProviderClaimableBalance godoc // -// @Summary Get Provider Claimable Balance -// @Description Get provider claimable balance from session -// @Tags sessions -// @Produce json -// @Param id path string true "Session ID" -// @Success 200 {object} interface{} -// @Router /proxy/sessions/{id}/providerClaimableBalance [get] +// @Summary Get Provider Claimable Balance +// @Description Get provider claimable balance from session +// @Tags sessions +// @Produce json +// @Param id path string true "Session ID" +// @Success 200 {object} structs.BalanceRes +// @Router /proxy/sessions/{id}/providerClaimableBalance [get] func (c *BlockchainController) getProviderClaimableBalance(ctx *gin.Context) { var params structs.PathHex32ID err := ctx.ShouldBindUri(¶ms) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } balance, err := c.service.GetProviderClaimableBalance(ctx, params.ID.Hash) if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"balance": balance}) + ctx.JSON(http.StatusOK, structs.BalanceRes{Balance: &lib.BigInt{Int: *balance}}) return } // ClaimProviderBalance godoc // -// @Summary Claim Provider Balance -// @Description Claim provider balance from session -// @Tags sessions -// @Produce json -// @Param claim body structs.SendRequest true "Claim" -// @Param id path string true "Session ID" -// @Success 200 {object} interface{} -// @Router /proxy/sessions/{id}/providerClaim [post] +// @Summary Claim Provider Balance +// @Description Claim provider balance from session +// @Tags sessions +// @Produce json +// @Param claim body structs.AmountReq true "Claim" +// @Param id path string true "Session ID" +// @Success 200 {object} structs.TxRes +// @Router /proxy/sessions/{id}/providerClaim [post] func (c *BlockchainController) claimProviderBalance(ctx *gin.Context) { var params structs.PathHex32ID err := ctx.ShouldBindUri(¶ms) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } _, amount, err := c.getSendParams(ctx) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } txHash, err := c.service.ClaimProviderBalance(ctx, params.ID.Hash, amount) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"txHash": txHash.String()}) + ctx.JSON(http.StatusOK, structs.TxRes{Tx: txHash}) return } // GetProviders godoc // -// @Summary Get providers list -// @Description Get providers list from blokchain -// @Tags wallet -// @Produce json -// @Success 200 {object} []interface{} -// @Router /blockchain/providers [get] +// @Summary Get providers list +// @Description Get providers list from blokchain +// @Tags providers +// @Produce json +// @Success 200 {object} structs.ProvidersRes +// @Router /blockchain/providers [get] func (c *BlockchainController) getAllProviders(ctx *gin.Context) { providers, err := c.service.GetAllProviders(ctx) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"providers": providers}) + ctx.JSON(http.StatusOK, structs.ProvidersRes{Providers: providers}) return } // SendEth godoc // -// @Summary Send Eth -// @Description Send Eth to address -// @Tags wallet -// @Produce json -// @Param sendeth body structs.SendRequest true "Send Eth" -// @Success 200 {object} interface{} -// @Router /blockchain/send/eth [post] +// @Summary Send Eth +// @Description Send Eth to address +// @Tags transactions +// @Produce json +// @Param sendeth body structs.SendRequest true "Send Eth" +// @Success 200 {object} structs.TxRes +// @Router /blockchain/send/eth [post] func (c *BlockchainController) sendETH(ctx *gin.Context) { to, amount, err := c.getSendParams(ctx) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } txHash, err := c.service.SendETH(ctx, to, amount) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"txHash": txHash.String()}) + ctx.JSON(http.StatusOK, structs.TxRes{Tx: txHash}) return } // SendMor godoc // -// @Summary Send Mor -// @Description Send Mor to address -// @Tags wallet -// @Produce json -// @Param sendmor body structs.SendRequest true "Send Mor" -// @Success 200 {object} interface{} -// @Router /blockchain/send/mor [post] +// @Summary Send Mor +// @Description Send Mor to address +// @Tags transactions +// @Produce json +// @Param sendmor body structs.SendRequest true "Send Mor" +// @Success 200 {object} structs.TxRes +// @Router /blockchain/send/mor [post] func (c *BlockchainController) sendMOR(ctx *gin.Context) { to, amount, err := c.getSendParams(ctx) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } txhash, err := c.service.SendMOR(ctx, to, amount) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"txHash": txhash.String()}) + ctx.JSON(http.StatusOK, structs.TxRes{Tx: txhash}) return } // GetBidsByProvider godoc // -// @Summary Get Bids by Provider -// @Description Get bids from blockchain by provider -// @Tags wallet -// @Produce json -// @Param offset query string false "Offset" -// @Param limit query string false "Limit" -// @Param id path string true "Provider ID" -// @Success 200 {object} []interface{} -// @Router /blockchain/providers/{id}/bids [get] +// @Summary Get Bids by Provider +// @Description Get bids from blockchain by provider +// @Tags bids +// @Produce json +// @Param offset query string false "Offset" +// @Param limit query string false "Limit" +// @Param id path string true "Provider ID" +// @Success 200 {object} structs.BidsRes +// @Router /blockchain/providers/{id}/bids [get] func (c *BlockchainController) getBidsByProvider(ctx *gin.Context) { var params structs.PathEthAddrID err := ctx.ShouldBindUri(¶ms) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } offset, limit, err := getOffsetLimit(ctx) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } bids, err := c.service.GetBidsByProvider(ctx, params.ID.Address, offset, limit) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"bids": bids}) + ctx.JSON(http.StatusOK, structs.BidsRes{Bids: bids}) return } // GetActiveBidsByProvider godoc // -// @Summary Get Bids by Provider -// @Description Get bids from blockchain by provider -// @Tags wallet -// @Produce json -// @Param id path string true "Provider ID" -// @Success 200 {object} []interface{} -// @Router /blockchain/providers/{id}/bids/active [get] +// @Summary Get Bids by Provider +// @Description Get bids from blockchain by provider +// @Tags bids +// @Produce json +// @Param id path string true "Provider ID" +// @Success 200 {object} structs.BidsRes +// @Router /blockchain/providers/{id}/bids/active [get] func (c *BlockchainController) getActiveBidsByProvider(ctx *gin.Context) { var params structs.PathEthAddrID err := ctx.ShouldBindUri(¶ms) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } bids, err := c.service.GetActiveBidsByProvider(ctx, params.ID.Address) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"bids": bids}) + ctx.JSON(http.StatusOK, structs.BidsRes{Bids: bids}) return } // GetModels godoc // -// @Summary Get models list -// @Description Get models list from blokchain -// @Tags wallet -// @Produce json -// @Success 200 {object} []interface{} -// @Router /blockchain/models [get] +// @Summary Get models list +// @Description Get models list from blokchain +// @Tags models +// @Produce json +// @Success 200 {object} structs.ModelsRes +// @Router /blockchain/models [get] func (c *BlockchainController) getAllModels(ctx *gin.Context) { - providers, err := c.service.GetAllModels(ctx) + models, err := c.service.GetAllModels(ctx) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"models": providers}) + ctx.JSON(http.StatusOK, structs.ModelsRes{Models: models}) return } // GetBidsByModelAgent godoc // -// @Summary Get Bids by Model Agent -// @Description Get bids from blockchain by model agent -// @Tags wallet -// @Produce json -// @Param offset query string false "Offset" -// @Param limit query string false "Limit" -// @Param id path string true "ModelAgent ID" -// @Success 200 {object} []interface{} -// @Router /blockchain/models/{id}/bids [get] +// @Summary Get Bids by Model Agent +// @Description Get bids from blockchain by model agent +// @Tags bids +// @Produce json +// @Param offset query string false "Offset" +// @Param limit query string false "Limit" +// @Param id path string true "ModelAgent ID" +// @Success 200 {object} structs.BidsRes +// @Router /blockchain/models/{id}/bids [get] func (c *BlockchainController) getBidsByModelAgent(ctx *gin.Context) { var params structs.PathHex32ID err := ctx.ShouldBindUri(¶ms) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } offset, limit, err := getOffsetLimit(ctx) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } bids, err := c.service.GetBidsByModelAgent(ctx, params.ID.Hash, offset, limit) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"bids": bids}) + ctx.JSON(http.StatusOK, structs.BidsRes{Bids: bids}) return } // GetActiveBidsByModel godoc // -// @Summary Get Active Bids by Model -// @Description Get bids from blockchain by model agent -// @Tags wallet -// @Produce json -// @Param id path string true "ModelAgent ID" -// @Success 200 {object} []interface{} -// @Router /blockchain/models/{id}/bids [get] +// @Summary Get Active Bids by Model +// @Description Get bids from blockchain by model agent +// @Tags bids +// @Produce json +// @Param id path string true "ModelAgent ID" +// @Success 200 {object} structs.BidsRes +// @Router /blockchain/models/{id}/bids [get] func (c *BlockchainController) getActiveBidsByModel(ctx *gin.Context) { var params structs.PathHex32ID err := ctx.ShouldBindUri(¶ms) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } bids, err := c.service.GetActiveBidsByModel(ctx, params.ID.Hash) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"bids": bids}) + ctx.JSON(http.StatusOK, structs.BidsRes{Bids: bids}) return } // GetBalance godoc // -// @Summary Get ETH and MOR balance -// @Description Get ETH and MOR balance of the user -// @Tags wallet -// @Produce json -// @Success 200 {object} interface{} -// @Router /blockchain/balance [get] +// @Summary Get ETH and MOR balance +// @Description Get ETH and MOR balance of the user +// @Tags transactions +// @Produce json +// @Success 200 {object} structs.TokenBalanceRes +// @Router /blockchain/balance [get] func (s *BlockchainController) getBalance(ctx *gin.Context) { ethBalance, morBalance, err := s.service.GetBalance(ctx) if err != nil { s.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"eth": ethBalance.String(), "mor": morBalance.String()}) + ctx.JSON(http.StatusOK, structs.TokenBalanceRes{ + ETH: &lib.BigInt{Int: *ethBalance}, + MOR: &lib.BigInt{Int: *morBalance}, + }) return } // GetTransactions godoc // -// @Summary Get Transactions -// @Description Get MOR and ETH transactions -// @Tags wallet -// @Produce json -// @Param page query string false "Page" -// @Param limit query string false "Limit" -// @Success 200 {object} []interface{} -// @Router /blockchain/transactions [get] +// @Summary Get Transactions +// @Description Get MOR and ETH transactions +// @Tags transactions +// @Produce json +// @Param page query string false "Page" +// @Param limit query string false "Limit" +// @Success 200 {object} structs.TransactionsRes +// @Router /blockchain/transactions [get] func (c *BlockchainController) getTransactions(ctx *gin.Context) { page, limit, err := getPageLimit(ctx) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } txs, err := c.service.GetTransactions(ctx, page, limit) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"transactions": txs}) + ctx.JSON(http.StatusOK, structs.TransactionsRes{Transactions: txs}) } // GetAllowance godoc // -// @Summary Get Allowance for MOR -// @Description Get MOR allowance for spender -// @Tags wallet -// @Produce json -// @Param spender query string true "Spender address" -// @Success 200 {object} interface{} -// @Router /blockchain/allowance [get] +// @Summary Get Allowance for MOR +// @Description Get MOR allowance for spender +// @Tags transactions +// @Produce json +// @Param spender query string true "Spender address" +// @Success 200 {object} structs.AllowanceRes +// @Router /blockchain/allowance [get] func (c *BlockchainController) getAllowance(ctx *gin.Context) { var query structs.QuerySpender err := ctx.ShouldBindQuery(&query) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } allowance, err := c.service.GetAllowance(ctx, query.Spender.Address) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"allowance": allowance.String()}) + ctx.JSON(http.StatusOK, structs.AllowanceRes{Allowance: &lib.BigInt{Int: *allowance}}) return } // Approve godoc // -// @Summary Approve MOR allowance -// @Description Approve MOR allowance for spender -// @Tags wallet -// @Produce json -// @Param spender query string true "Spender address" -// @Param amount query string true "Amount" -// @Success 200 {object} interface{} -// @Router /blockchain/approve [post] +// @Summary Approve MOR allowance +// @Description Approve MOR allowance for spender +// @Tags transactions +// @Produce json +// @Param spender query string true "Spender address" +// @Param amount query string true "Amount" +// @Success 200 {object} structs.TxRes +// @Router /blockchain/approve [post] func (c *BlockchainController) approve(ctx *gin.Context) { var query structs.QueryApprove err := ctx.ShouldBindQuery(&query) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } tx, err := c.service.Approve(ctx, query.Spender.Address, query.Amount.Unpack()) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"tx": tx.Hex()}) + ctx.JSON(http.StatusOK, structs.TxRes{Tx: tx}) return } // OpenSession godoc // -// @Summary Open Session with Provider in blockchain -// @Description Sends transaction in blockchain to open a session -// @Tags sessions -// @Produce json -// @Accept json -// @Param opensession body structs.OpenSessionRequest true "Open session" -// @Success 200 {object} interface{} -// @Router /blockchain/sessions [post] +// @Summary Open Session with Provider in blockchain +// @Description Sends transaction in blockchain to open a session +// @Tags sessions +// @Produce json +// @Accept json +// @Param opensession body structs.OpenSessionRequest true "Open session" +// @Success 200 {object} structs.OpenSessionRes +// @Router /blockchain/sessions [post] func (c *BlockchainController) openSession(ctx *gin.Context) { var reqPayload structs.OpenSessionRequest if err := ctx.ShouldBindJSON(&reqPayload); err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } sessionId, err := c.service.OpenSession(ctx, reqPayload.Approval, reqPayload.ApprovalSig, reqPayload.Stake.Unpack()) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"sessionId": sessionId.Hex()}) + ctx.JSON(http.StatusOK, structs.OpenSessionRes{SessionID: sessionId}) return } // OpenSessionByBidId godoc // -// @Summary Open Session by bidId in blockchain -// @Description Full flow to open a session by bidId -// @Tags sessions -// @Produce json -// @Accept json -// @Param opensession body structs.OpenSessionWithDurationRequest true "Open session" -// @Param id path string true "Bid ID" -// @Success 200 {object} interface{} -// @Router /blockchain/bids/{id}/session [post] +// @Summary Open Session by bidId in blockchain +// @Description Full flow to open a session by bidId +// @Tags sessions +// @Produce json +// @Accept json +// @Param opensession body structs.OpenSessionWithDurationRequest true "Open session" +// @Param id path string true "Bid ID" +// @Success 200 {object} structs.OpenSessionRes +// @Router /blockchain/bids/{id}/session [post] func (s *BlockchainController) openSessionByBid(ctx *gin.Context) { var reqPayload structs.OpenSessionWithDurationRequest if err := ctx.ShouldBindJSON(&reqPayload); err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } var params structs.PathHex32ID err := ctx.ShouldBindUri(¶ms) if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } sessionId, err := s.service.openSessionByBid(ctx, params.ID.Hash, reqPayload.SessionDuration.Unpack()) if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"sessionId": sessionId.Hex()}) + ctx.JSON(http.StatusOK, structs.OpenSessionRes{SessionID: sessionId}) return } // OpenSessionByModelId godoc // -// @Summary Open Session by ModelID in blockchain -// @Description Full flow to open a session by modelId -// @Tags sessions -// @Produce json -// @Accept json -// @Param opensession body structs.OpenSessionWithDurationRequest true "Open session" -// @Param id path string true "Model ID" -// @Success 200 {object} interface{} -// @Router /blockchain/models/{id}/session [post] +// @Summary Open Session by ModelID in blockchain +// @Description Full flow to open a session by modelId +// @Tags sessions +// @Produce json +// @Accept json +// @Param opensession body structs.OpenSessionWithDurationRequest true "Open session" +// @Param id path string true "Model ID" +// @Success 200 {object} structs.OpenSessionRes +// @Router /blockchain/models/{id}/session [post] func (s *BlockchainController) openSessionByModelId(ctx *gin.Context) { var reqPayload structs.OpenSessionWithDurationRequest if err := ctx.ShouldBindJSON(&reqPayload); err != nil { s.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } var params structs.PathHex32ID err := ctx.ShouldBindUri(¶ms) if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } sessionId, err := s.service.OpenSessionByModelId(ctx, params.ID.Hash, reqPayload.SessionDuration.Unpack()) if err != nil { s.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"sessionId": sessionId.Hex()}) + ctx.JSON(http.StatusOK, structs.OpenSessionRes{SessionID: sessionId}) return } // CloseSession godoc // -// @Summary Close Session with Provider -// @Description Sends transaction in blockchain to close a session -// @Tags sessions -// @Produce json -// @Param id path string true "Session ID" -// @Success 200 {object} interface{} -// @Router /blockchain/sessions/{id}/close [post] +// @Summary Close Session with Provider +// @Description Sends transaction in blockchain to close a session +// @Tags sessions +// @Produce json +// @Param id path string true "Session ID" +// @Success 200 {object} structs.TxRes +// @Router /blockchain/sessions/{id}/close [post] func (c *BlockchainController) closeSession(ctx *gin.Context) { var params structs.PathHex32ID err := ctx.ShouldBindUri(¶ms) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } txHash, err := c.service.CloseSession(ctx, params.ID.Hash) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"tx": txHash.Hex()}) + ctx.JSON(http.StatusOK, structs.TxRes{Tx: txHash}) return } +// GetSession godoc +// +// @Summary Get session +// @Description Returns session by ID +// @Tags sessions +// @Produce json +// @Param id path string true "Session ID" +// @Success 200 {object} structs.SessionRes +// @Router /blockchain/sessions/{id} [get] func (c *BlockchainController) getSession(ctx *gin.Context) { var params structs.PathHex32ID err := ctx.ShouldBindUri(¶ms) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } session, err := c.service.GetSession(ctx, params.ID.Hash) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"session": session}) + ctx.JSON(http.StatusOK, structs.SessionRes{Session: session}) return } // GetSessions godoc // -// @Summary Get Sessions -// @Description Get sessions from blockchain by user or provider -// @Tags sessions -// @Produce json -// @Param offset query string false "Offset" -// @Param limit query string false "Limit" -// @Param provider query string false "Provider address" -// @Param user query string false "User address" -// @Success 200 {object} []interface{} -// @Router /blockchain/sessions [get] +// @Summary Get Sessions +// @Description Get sessions from blockchain by user or provider +// @Tags sessions +// @Produce json +// @Param offset query string false "Offset" +// @Param limit query string false "Limit" +// @Param provider query string false "Provider address" +// @Param user query string false "User address" +// @Success 200 {object} structs.SessionsRes +// @Router /blockchain/sessions [get] func (c *BlockchainController) getSessions(ctx *gin.Context) { offset, limit, err := getOffsetLimit(ctx) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } @@ -634,7 +654,7 @@ func (c *BlockchainController) getSessions(ctx *gin.Context) { err = ctx.ShouldBindQuery(&req) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } @@ -643,167 +663,185 @@ func (c *BlockchainController) getSessions(ctx *gin.Context) { if !hasUser && !hasProvider { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": "user or provider is required"}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: fmt.Errorf("user or provider is required")}) return } if hasUser && hasProvider { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": "only one of user or provider is allowed"}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: fmt.Errorf("only one of user or provider is allowed")}) return } sessions, err := c.service.GetSessions(ctx, req.User.Address, req.Provider.Address, offset, limit) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"sessions": sessions}) + ctx.JSON(http.StatusOK, structs.SessionsRes{Sessions: sessions}) return } // GetTodaysBudget godoc // -// @Summary Get Todays Budget -// @Description Get todays budget from blockchain -// @Tags wallet -// @Produce json -// @Success 200 {object} interface{} -// @Router /blockchain/sessions/budget [get] +// @Summary Get Todays Budget +// @Description Get todays budget from blockchain +// @Tags sessions +// @Produce json +// @Success 200 {object} structs.BudgetRes +// @Router /blockchain/sessions/budget [get] func (s *BlockchainController) getBudget(ctx *gin.Context) { budget, err := s.service.GetTodaysBudget(ctx) if err != nil { s.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"budget": budget.String()}) + ctx.JSON(http.StatusOK, structs.BudgetRes{Budget: &lib.BigInt{Int: *budget}}) return } // GetTokenSupply godoc // -// @Summary Get Token Supply -// @Description Get MOR token supply from blockchain -// @Tags wallet -// @Produce json -// @Success 200 {object} interface{} -// @Router /blockchain/token/supply [get] +// @Summary Get Token Supply +// @Description Get MOR token supply from blockchain +// @Tags sessions +// @Produce json +// @Success 200 {object} structs.SupplyRes +// @Router /blockchain/token/supply [get] func (s *BlockchainController) getSupply(ctx *gin.Context) { supply, err := s.service.GetTokenSupply(ctx) if err != nil { s.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"supply": supply.String()}) + ctx.JSON(http.StatusOK, structs.SupplyRes{Supply: &lib.BigInt{Int: *supply}}) return } // GetLatestBlock godoc // -// @Summary Get Latest Block -// @Description Get latest block number from blockchain -// @Tags wallet -// @Produce json -// @Success 200 {object} interface{} -// @Router /blockchain/latestBlock [get] +// @Summary Get Latest Block +// @Description Get latest block number from blockchain +// @Tags transactions +// @Produce json +// @Success 200 {object} structs.BlockRes +// @Router /blockchain/latestBlock [get] func (c *BlockchainController) getLatestBlock(ctx *gin.Context) { block, err := c.service.GetLatestBlock(ctx) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"block": block}) + ctx.JSON(http.StatusOK, structs.BlockRes{Block: block}) return } +// GetBidByID godoc +// +// @Summary Get Bid by ID +// @Description Get bid from blockchain by ID +// @Tags bids +// @Produce json +// @Param id path string true "Bid ID" +// @Success 200 {object} structs.BidRes +// @Router /blockchain/bids/{id} [get] func (c *BlockchainController) getBidByID(ctx *gin.Context) { var params structs.PathHex32ID err := ctx.ShouldBindUri(¶ms) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } bid, err := c.service.GetBidByID(ctx, params.ID.Hash) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"bid": bid}) + ctx.JSON(http.StatusOK, structs.BidRes{Bid: bid}) return } +// GetRatedBids godoc +// +// @Summary Get Rated Bids +// @Description Get rated bids from blockchain by model +// @Tags bids +// @Produce json +// @Param id path string true "Model ID" +// @Success 200 {object} structs.ScoredBidsRes +// @Router /blockchain/models/{id}/bids/rated [get] func (c *BlockchainController) getRatedBids(ctx *gin.Context) { var params structs.PathHex32ID err := ctx.ShouldBindUri(¶ms) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } bids, err := c.service.GetRatedBids(ctx, params.ID.Hash) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"bids": bids}) + ctx.JSON(http.StatusOK, structs.ScoredBidsRes{Bids: bids}) return } // СreateNewProvider godoc // -// @Summary Creates or updates provider in blockchain -// @Tags wallet -// @Produce json -// @Accept json -// @Param provider body structs.CreateProviderRequest true "Provider" -// @Success 200 {object} interface{} -// @Router /blockchain/providers [post] +// @Summary Creates or updates provider in blockchain +// @Tags providers +// @Produce json +// @Accept json +// @Param provider body structs.CreateProviderRequest true "Provider" +// @Success 200 {object} structs.ProviderRes +// @Router /blockchain/providers [post] func (c *BlockchainController) createProvider(ctx *gin.Context) { var provider structs.CreateProviderRequest if err := ctx.ShouldBindJSON(&provider); err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } result, err := c.service.CreateNewProvider(ctx, provider.Stake, provider.Endpoint) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"provider": result}) + ctx.JSON(http.StatusOK, structs.ProviderRes{Provider: result}) return } // CreateNewModel godoc // -// @Summary Creates model in blockchain -// @Tags wallet -// @Produce json -// @Accept json -// @Param model body structs.CreateModelRequest true "Model" -// @Success 200 {object} interface{} -// @Router /blockchain/models [post] +// @Summary Creates model in blockchain +// @Tags models +// @Produce json +// @Accept json +// @Param model body structs.CreateModelRequest true "Model" +// @Success 200 {object} structs.ModelRes +// @Router /blockchain/models [post] func (c *BlockchainController) createNewModel(ctx *gin.Context) { var model structs.CreateModelRequest if err := ctx.ShouldBindJSON(&model); err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } @@ -813,7 +851,7 @@ func (c *BlockchainController) createNewModel(ctx *gin.Context) { _, err := rand.Read(hash[:]) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } modelId = hash @@ -825,29 +863,29 @@ func (c *BlockchainController) createNewModel(ctx *gin.Context) { result, err := c.service.CreateNewModel(ctx, modelId, ipsfHash, model.Fee, model.Stake, model.Name, model.Tags) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"model": result}) + ctx.JSON(http.StatusOK, structs.ModelRes{Model: result}) return } // CreateBidRequest godoc // -// @Summary Creates bid in blockchain -// @Tags wallet -// @Produce json -// @Accept json -// @Param bid body structs.CreateBidRequest true "Bid" -// @Success 200 {object} interface{} -// @Router /blockchain/bids [post] +// @Summary Creates bid in blockchain +// @Tags bids +// @Produce json +// @Accept json +// @Param bid body structs.CreateBidRequest true "Bid" +// @Success 200 {object} structs.BidRes +// @Router /blockchain/bids [post] func (c *BlockchainController) createNewBid(ctx *gin.Context) { var bid structs.CreateBidRequest if err := ctx.ShouldBindJSON(&bid); err != nil { c.log.Error(err) - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusBadRequest, structs.ErrRes{Error: err}) return } @@ -855,11 +893,11 @@ func (c *BlockchainController) createNewBid(ctx *gin.Context) { result, err := c.service.CreateNewBid(ctx, modelId, bid.PricePerSecond) if err != nil { c.log.Error(err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + ctx.JSON(http.StatusInternalServerError, structs.ErrRes{Error: err}) return } - ctx.JSON(http.StatusOK, gin.H{"bid": result}) + ctx.JSON(http.StatusOK, structs.BidRes{Bid: result}) return } diff --git a/proxy-router/internal/blockchainapi/mappers.go b/proxy-router/internal/blockchainapi/mappers.go index 87ae2508..139fbe69 100644 --- a/proxy-router/internal/blockchainapi/mappers.go +++ b/proxy-router/internal/blockchainapi/mappers.go @@ -23,10 +23,10 @@ func mapBid(bidID common.Hash, bid marketplace.Bid) *structs.Bid { Id: bidID, ModelAgentId: bid.ModelAgentId, Provider: bid.Provider, - Nonce: bid.Nonce, - CreatedAt: bid.CreatedAt, - DeletedAt: bid.DeletedAt, - PricePerSecond: bid.PricePerSecond, + Nonce: &lib.BigInt{Int: *bid.Nonce}, + CreatedAt: &lib.BigInt{Int: *bid.CreatedAt}, + DeletedAt: &lib.BigInt{Int: *bid.DeletedAt}, + PricePerSecond: &lib.BigInt{Int: *bid.PricePerSecond}, } } diff --git a/proxy-router/internal/blockchainapi/rating.go b/proxy-router/internal/blockchainapi/rating.go index 0e936eca..46c63562 100644 --- a/proxy-router/internal/blockchainapi/rating.go +++ b/proxy-router/internal/blockchainapi/rating.go @@ -9,13 +9,8 @@ import ( "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" ) -type ScoredBid struct { - Bid structs.Bid - Score float64 -} - -func rateBids(bidIds [][32]byte, bids []m.Bid, pmStats []m.ProviderModelStats, mStats m.ModelStats, log lib.ILogger) []ScoredBid { - scoredBids := make([]ScoredBid, len(bids)) +func rateBids(bidIds [][32]byte, bids []m.Bid, pmStats []m.ProviderModelStats, mStats m.ModelStats, log lib.ILogger) []structs.ScoredBid { + scoredBids := make([]structs.ScoredBid, len(bids)) for i := range bids { score := getScore(bids[i], pmStats[i], mStats) @@ -23,15 +18,15 @@ func rateBids(bidIds [][32]byte, bids []m.Bid, pmStats []m.ProviderModelStats, m log.Errorf("provider score is not valid %d for bid %v, pmStats %v, mStats %v", score, bidIds[i], pmStats[i], mStats) score = 0 } - scoredBid := ScoredBid{ + scoredBid := structs.ScoredBid{ Bid: structs.Bid{ Id: bidIds[i], Provider: bids[i].Provider, - PricePerSecond: bids[i].PricePerSecond, ModelAgentId: bids[i].ModelAgentId, - Nonce: bids[i].Nonce, - CreatedAt: bids[i].CreatedAt, - DeletedAt: bids[i].DeletedAt, + PricePerSecond: &lib.BigInt{Int: *(bids[i].PricePerSecond)}, + Nonce: &lib.BigInt{Int: *(bids[i].Nonce)}, + CreatedAt: &lib.BigInt{Int: *(bids[i].CreatedAt)}, + DeletedAt: &lib.BigInt{Int: *(bids[i].DeletedAt)}, }, Score: score, } diff --git a/proxy-router/internal/blockchainapi/service.go b/proxy-router/internal/blockchainapi/service.go index 547b5900..ad840cff 100644 --- a/proxy-router/internal/blockchainapi/service.go +++ b/proxy-router/internal/blockchainapi/service.go @@ -116,9 +116,9 @@ func (s *BlockchainService) GetAllProviders(ctx context.Context) ([]*structs.Pro result[i] = &structs.Provider{ Address: addrs[i], Endpoint: value.Endpoint, - Stake: value.Stake, + Stake: &lib.BigInt{Int: *value.Stake}, IsDeleted: value.IsDeleted, - CreatedAt: value.CreatedAt, + CreatedAt: &lib.BigInt{Int: *value.CreatedAt}, } } @@ -194,7 +194,7 @@ func (s *BlockchainService) GetBidByID(ctx context.Context, ID common.Hash) (*st return mapBid(ID, *bid), nil } -func (s *BlockchainService) GetRatedBids(ctx context.Context, modelID common.Hash) ([]ScoredBid, error) { +func (s *BlockchainService) GetRatedBids(ctx context.Context, modelID common.Hash) ([]structs.ScoredBid, error) { modelStats, err := s.marketplace.GetModelStats(ctx, modelID) if err != nil { return nil, err @@ -253,9 +253,9 @@ func (s *BlockchainService) CreateNewProvider(ctx context.Context, stake *lib.Bi return &structs.Provider{ Address: transactOpt.From, Endpoint: provider.Endpoint, - Stake: provider.Stake, + Stake: &lib.BigInt{Int: *provider.Stake}, IsDeleted: provider.IsDeleted, - CreatedAt: provider.CreatedAt, + CreatedAt: &lib.BigInt{Int: *provider.CreatedAt}, }, nil } @@ -345,8 +345,12 @@ func (s *BlockchainService) CloseSession(ctx context.Context, sessionID common.H return tx, nil } -func (s *BlockchainService) GetSession(ctx *gin.Context, sessionID common.Hash) (*sessionrouter.Session, error) { - return s.sessionRouter.GetSession(ctx, sessionID) +func (s *BlockchainService) GetSession(ctx *gin.Context, sessionID common.Hash) (*structs.Session, error) { + ses, err := s.sessionRouter.GetSession(ctx, sessionID) + if err != nil { + return nil, err + } + return mapSession(*ses), nil } func (s *BlockchainService) GetProviderClaimableBalance(ctx *gin.Context, sessionID common.Hash) (*big.Int, error) { @@ -649,13 +653,13 @@ func (s *BlockchainService) OpenSessionByModelId(ctx context.Context, modelID co return common.Hash{}, fmt.Errorf("no provider accepting session") } -func (s *BlockchainService) tryOpenSession(ctx context.Context, bid ScoredBid, duration *big.Int, supply *big.Int, budget *big.Int, userAddr common.Address) (common.Hash, error) { +func (s *BlockchainService) tryOpenSession(ctx context.Context, bid structs.ScoredBid, duration *big.Int, supply *big.Int, budget *big.Int, userAddr common.Address) (common.Hash, error) { provider, err := s.providerRegistry.GetProviderById(ctx, bid.Bid.Provider) if err != nil { return common.Hash{}, lib.WrapError(ErrProvider, err) } - totalCost := duration.Mul(bid.Bid.PricePerSecond, duration) + totalCost := duration.Mul(&bid.Bid.PricePerSecond.Int, duration) stake := totalCost.Div(totalCost.Mul(supply, totalCost), budget) initRes, err := s.proxyService.InitiateSession(ctx, userAddr, bid.Bid.Provider, stake, bid.Bid.Id, provider.Endpoint) diff --git a/proxy-router/internal/blockchainapi/structs/bid.go b/proxy-router/internal/blockchainapi/structs/bid.go index 9431f70d..764f2753 100644 --- a/proxy-router/internal/blockchainapi/structs/bid.go +++ b/proxy-router/internal/blockchainapi/structs/bid.go @@ -1,8 +1,7 @@ package structs import ( - "math/big" - + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" "github.com/ethereum/go-ethereum/common" ) @@ -10,10 +9,10 @@ type Bid struct { Id common.Hash Provider common.Address ModelAgentId common.Hash - PricePerSecond *big.Int - Nonce *big.Int - CreatedAt *big.Int - DeletedAt *big.Int + PricePerSecond *lib.BigInt + Nonce *lib.BigInt + CreatedAt *lib.BigInt + DeletedAt *lib.BigInt } type ScoredBid struct { diff --git a/proxy-router/internal/blockchainapi/structs/provider.go b/proxy-router/internal/blockchainapi/structs/provider.go index 7ce5a30d..5b881ab0 100644 --- a/proxy-router/internal/blockchainapi/structs/provider.go +++ b/proxy-router/internal/blockchainapi/structs/provider.go @@ -1,15 +1,14 @@ package structs import ( - "math/big" - + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" "github.com/ethereum/go-ethereum/common" ) type Provider struct { Address common.Address Endpoint string - Stake *big.Int - CreatedAt *big.Int + Stake *lib.BigInt + CreatedAt *lib.BigInt IsDeleted bool } diff --git a/proxy-router/internal/blockchainapi/structs/req.go b/proxy-router/internal/blockchainapi/structs/req.go index 1b8389b8..dad1284d 100644 --- a/proxy-router/internal/blockchainapi/structs/req.go +++ b/proxy-router/internal/blockchainapi/structs/req.go @@ -6,9 +6,13 @@ import ( ) type OpenSessionRequest struct { - Approval lib.HexString `json:"approval" binding:"required" validate:"hexadecimal"` - ApprovalSig lib.HexString `json:"approvalSig" binding:"required" validate:"hexadecimal"` - Stake *lib.BigInt `json:"stake" binding:"required" validate:"number,gt=0"` + Approval lib.HexString `json:"approval" binding:"required" validate:"hexadecimal" format:"hex" example:"0x1234"` + ApprovalSig lib.HexString `json:"approvalSig" binding:"required" validate:"hexadecimal" format:"hex" example:"0x1234"` + Stake *lib.BigInt `json:"stake" binding:"required" validate:"number,gt=0" example:"123000000000"` +} + +type AmountReq struct { + Amount *lib.BigInt `json:"amount" binding:"required" validate:"number,gt=0"` } type SendRequest struct { @@ -17,7 +21,7 @@ type SendRequest struct { } type PathHex32ID struct { - ID lib.Hash `uri:"id" binding:"required" validate:"hex32"` + ID lib.Hash `uri:"id" binding:"required" validate:"hex32" format:"hex" example:"0x1234"` } type PathEthAddrID struct { @@ -58,15 +62,15 @@ type CreateBidRequest struct { } type CreateProviderRequest struct { - Stake *lib.BigInt `json:"stake" binding:"required" validate:"number"` - Endpoint string `json:"endpoint" binding:"required" validate:"string"` + Stake *lib.BigInt `json:"stake" binding:"required" validate:"number" example:"123000000000"` + Endpoint string `json:"endpoint" binding:"required" validate:"string" example:"mycoolmornode.domain.com:3989"` } type CreateModelRequest struct { - ID string `json:"id" binding:"omitempty" validate:"hex32"` - IpfsID string `json:"ipfsID" binding:"required" validate:"hex32"` - Fee *lib.BigInt `json:"fee" binding:"required" validate:"number"` - Stake *lib.BigInt `json:"stake" binding:"required" validate:"number"` - Name string `json:"name" binding:"required" validate:"min=1,max=64"` + ID string `json:"id" binding:"omitempty" validate:"hex32" example:"0x1234"` + IpfsID string `json:"ipfsID" binding:"required" validate:"hex32" example:"0x1234"` + Fee *lib.BigInt `json:"fee" binding:"required" validate:"number" example:"123000000000"` + Stake *lib.BigInt `json:"stake" binding:"required" validate:"number" example:"123000000000"` + Name string `json:"name" binding:"required" validate:"min=1,max=64" example:"Llama 2.0"` Tags []string `json:"tags" binding:"required" validate:"min=1,max=64,dive,min=1,max=64"` } diff --git a/proxy-router/internal/blockchainapi/structs/res.go b/proxy-router/internal/blockchainapi/structs/res.go new file mode 100644 index 00000000..0d976c01 --- /dev/null +++ b/proxy-router/internal/blockchainapi/structs/res.go @@ -0,0 +1,83 @@ +package structs + +import ( + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" + "github.com/ethereum/go-ethereum/common" +) + +type AllowanceRes struct { + Allowance *lib.BigInt `json:"allowance" example:"100000000"` +} + +type TxRes struct { + Tx common.Hash `json:"tx" example:"0x1234"` +} + +type ErrRes struct { + Error error `json:"error" example:"error message"` +} + +type OpenSessionRes struct { + SessionID common.Hash `json:"sessionID" example:"0x1234"` +} + +type BalanceRes struct { + Balance *lib.BigInt `json:"balance"` +} + +type ProviderRes struct { + Provider *Provider `json:"provider"` +} + +type ProvidersRes struct { + Providers []*Provider `json:"providers"` +} + +type BidRes struct { + Bid *Bid `json:"bid"` +} + +type BidsRes struct { + Bids []*Bid `json:"bids"` +} + +type ScoredBidsRes struct { + Bids []ScoredBid `json:"bids"` +} + +type ModelRes struct { + Model *Model `json:"model"` +} + +type ModelsRes struct { + Models []*Model `json:"models"` +} + +type TokenBalanceRes struct { + MOR *lib.BigInt `json:"mor" example:"100000000"` + ETH *lib.BigInt `json:"eth" example:"100000000"` +} + +type TransactionsRes struct { + Transactions []RawTransaction `json:"transactions"` +} + +type SessionRes struct { + Session *Session `json:"session"` +} + +type SessionsRes struct { + Sessions []*Session `json:"sessions"` +} + +type BudgetRes struct { + Budget *lib.BigInt `json:"budget" example:"100000000"` +} + +type SupplyRes struct { + Supply *lib.BigInt `json:"supply" example:"100000000"` +} + +type BlockRes struct { + Block uint64 `json:"block" example:"1234"` +} diff --git a/proxy-router/internal/config/models_config.go b/proxy-router/internal/config/models_config.go index 12044b7a..942e95c4 100644 --- a/proxy-router/internal/config/models_config.go +++ b/proxy-router/internal/config/models_config.go @@ -69,3 +69,14 @@ func (e *ModelConfigLoader) ModelConfigFromID(ID string) *ModelConfig { return &modelConfig } + +func (e *ModelConfigLoader) GetAll() ([]string, []ModelConfig) { + var modelConfigs []ModelConfig + var modelIDs []string + for ID, v := range e.modelConfigs { + modelConfigs = append(modelConfigs, v) + modelIDs = append(modelIDs, ID) + } + + return modelIDs, modelConfigs +} diff --git a/proxy-router/internal/handlers/httphandlers/http.go b/proxy-router/internal/handlers/httphandlers/http.go index 2cde183f..6f96504d 100644 --- a/proxy-router/internal/handlers/httphandlers/http.go +++ b/proxy-router/internal/handlers/httphandlers/http.go @@ -1,8 +1,6 @@ package httphandlers import ( - "net/http/pprof" - "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" @@ -23,15 +21,15 @@ type Registrable interface { RegisterRoutes(r interfaces.Router) } -// @title ApiBus Example API -// @version 1.0 -// @description This is a sample server celler server. -// @termsOfService http://swagger.io/terms/ +// @title Morpheus Lumerin Node API +// @version 1.0 +// @description API for Morpheus Lumerin Node +// @termsOfService http://swagger.io/terms/ -// @BasePath / +// @BasePath / -// @externalDocs.description OpenAPI -// @externalDocs.url https://swagger.io/resources/open-api/ +// @externalDocs.description OpenAPI +// @externalDocs.url https://swagger.io/resources/open-api/ func CreateHTTPServer(log lib.ILogger, controllers ...Registrable) *gin.Engine { ginValidatorInstance := binding.Validator.Engine().(*validator.Validate) err := config.RegisterHex32(ginValidatorInstance) @@ -57,13 +55,13 @@ func CreateHTTPServer(log lib.ILogger, controllers ...Registrable) *gin.Engine { r.Use(cors.New(cors.Config{ AllowOrigins: []string{"*"}, - AllowHeaders: []string{"session_id"}, + AllowHeaders: []string{"session_id", "model_id"}, })) // r.Use(RequestLogger(log)) r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) - r.Any("/debug/pprof/*action", gin.WrapF(pprof.Index)) + // r.Any("/debug/pprof/*action", gin.WrapF(pprof.Index)) for _, c := range controllers { c.RegisterRoutes(r) diff --git a/proxy-router/internal/proxyapi/chat_responses.go b/proxy-router/internal/proxyapi/chat_responses.go new file mode 100644 index 00000000..f6003539 --- /dev/null +++ b/proxy-router/internal/proxyapi/chat_responses.go @@ -0,0 +1,132 @@ +package proxyapi + +import ( + "net/http" + "strconv" + "time" +) + +type ChatCompletionChoice struct { + Index int `json:"index"` + Message ChatCompletionMessage `json:"message"` + // FinishReason + // stop: API returned complete message, + // or a message terminated by one of the stop sequences provided via the stop parameter + // length: Incomplete model output due to max_tokens parameter or token limit + // function_call: The model decided to call a function + // content_filter: Omitted content due to a flag from our content filters + // null: API response still in progress or incomplete + FinishReason FinishReason `json:"finish_reason"` + LogProbs *LogProbs `json:"logprobs,omitempty"` +} + +// ChatCompletionResponse represents a response structure for chat completion API. +type ChatCompletionResponse struct { + ID string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + Model string `json:"model"` + Choices []ChatCompletionChoice `json:"choices"` + Usage Usage `json:"usage"` + SystemFingerprint string `json:"system_fingerprint"` + + httpHeader `swaggerignore:"true"` +} + +type Usage struct { + PromptTokens int `json:"prompt_tokens"` + CompletionTokens int `json:"completion_tokens"` + TotalTokens int `json:"total_tokens"` +} + +type ToolCall struct { + // Index is not nil only in chat completion chunk object + Index *int `json:"index,omitempty"` + ID string `json:"id"` + Type ToolType `json:"type"` + Function FunctionCall `json:"function"` +} + +type FunctionCall struct { + Name string `json:"name,omitempty"` + // call function with arguments in JSON format + Arguments string `json:"arguments,omitempty"` +} + +type ToolType string + +type FinishReason string + +// LogProb represents the probability information for a token. +type LogProb struct { + Token string `json:"token"` + LogProb float64 `json:"logprob"` + Bytes []byte `json:"bytes,omitempty"` // Omitting the field if it is null + // TopLogProbs is a list of the most likely tokens and their log probability, at this token position. + // In rare cases, there may be fewer than the number of requested top_logprobs returned. + TopLogProbs []TopLogProbs `json:"top_logprobs"` +} + +// LogProbs is the top-level structure containing the log probability information. +type LogProbs struct { + // Content is a list of message content tokens with log probability information. + Content []LogProb `json:"content"` +} + +type TopLogProbs struct { + Token string `json:"token"` + LogProb float64 `json:"logprob"` + Bytes []byte `json:"bytes,omitempty"` +} + +type httpHeader http.Header + +func (h *httpHeader) SetHeader(header http.Header) { + *h = httpHeader(header) +} + +func (h *httpHeader) Header() http.Header { + return http.Header(*h) +} + +func (h *httpHeader) GetRateLimitHeaders() RateLimitHeaders { + return newRateLimitHeaders(h.Header()) +} + +// RateLimitHeaders struct represents Openai rate limits headers. +type RateLimitHeaders struct { + LimitRequests int `json:"x-ratelimit-limit-requests"` + LimitTokens int `json:"x-ratelimit-limit-tokens"` + RemainingRequests int `json:"x-ratelimit-remaining-requests"` + RemainingTokens int `json:"x-ratelimit-remaining-tokens"` + ResetRequests ResetTime `json:"x-ratelimit-reset-requests"` + ResetTokens ResetTime `json:"x-ratelimit-reset-tokens"` +} + +type ResetTime string + +func (r ResetTime) String() string { + return string(r) +} + +func (r ResetTime) Time() time.Time { + d, _ := time.ParseDuration(string(r)) + return time.Now().Add(d) +} + +func newRateLimitHeaders(h http.Header) RateLimitHeaders { + limitReq, _ := strconv.Atoi(h.Get("x-ratelimit-limit-requests")) + limitTokens, _ := strconv.Atoi(h.Get("x-ratelimit-limit-tokens")) + remainingReq, _ := strconv.Atoi(h.Get("x-ratelimit-remaining-requests")) + remainingTokens, _ := strconv.Atoi(h.Get("x-ratelimit-remaining-tokens")) + return RateLimitHeaders{ + LimitRequests: limitReq, + LimitTokens: limitTokens, + RemainingRequests: remainingReq, + RemainingTokens: remainingTokens, + ResetRequests: ResetTime(h.Get("x-ratelimit-reset-requests")), + ResetTokens: ResetTime(h.Get("x-ratelimit-reset-tokens")), + } +} + +const FinishReasonStop FinishReason = "stop" diff --git a/proxy-router/internal/proxyapi/controller_http.go b/proxy-router/internal/proxyapi/controller_http.go index eb67c24e..75bd275d 100644 --- a/proxy-router/internal/proxyapi/controller_http.go +++ b/proxy-router/internal/proxyapi/controller_http.go @@ -1,6 +1,8 @@ package proxyapi import ( + "encoding/json" + "fmt" "net/http" constants "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal" @@ -33,12 +35,13 @@ func (s *ProxyController) RegisterRoutes(r interfaces.Router) { // InitiateSession godoc // -// @Summary Initiate Session with Provider -// @Description sends a handshake to the provider -// @Tags sessions -// @Produce json -// @Success 200 {object} interface{} -// @Router /proxy/sessions/initiate [post] +// @Summary Initiate Session with Provider +// @Description sends a handshake to the provider +// @Tags chat +// @Produce json +// @Param initiateSession body proxyapi.InitiateSessionReq true "Initiate Session" +// @Success 200 {object} morrpcmesssage.SessionRes +// @Router /proxy/sessions/initiate [post] func (s *ProxyController) InitiateSession(ctx *gin.Context) { var req *InitiateSessionReq @@ -58,14 +61,15 @@ func (s *ProxyController) InitiateSession(ctx *gin.Context) { // SendPrompt godoc // -// @Summary Send Local Or Remote Prompt -// @Description Send prompt to a local or remote model based on session id in header -// @Tags wallet -// @Produce json -// @Param prompt body proxyapi.OpenAiCompletitionRequest true "Prompt" -// @Param session_id header string false "Session ID" -// @Success 200 {object} interface{} -// @Router /v1/chat/completions [post] +// @Summary Send Local Or Remote Prompt +// @Description Send prompt to a local or remote model based on session id in header +// @Tags chat +// @Produce text/event-stream +// @Param session_id header string false "Session ID" format(hex32) +// @Param model_id header string false "Model ID" format(hex32) +// @Param prompt body proxyapi.OpenAiCompletitionRequest true "Prompt" +// @Success 200 {object} proxyapi.ChatCompletionResponse +// @Router /v1/chat/completions [post] func (c *ProxyController) Prompt(ctx *gin.Context) { var ( body openai.ChatCompletionRequest @@ -83,14 +87,32 @@ func (c *ProxyController) Prompt(ctx *gin.Context) { if (head.SessionID == lib.Hash{}) { body.Stream = ctx.GetHeader(constants.HEADER_ACCEPT) == constants.CONTENT_TYPE_JSON - // TODO: Implement logic to get model based on prompt for local session, for now return default model - body.Model = c.GetModelForPrompt() - c.aiEngine.PromptCb(ctx, &body) + modelId := head.ModelID.Hex() + + prompt, t := c.GetBodyForLocalPrompt(modelId, &body) + if t == "openai" { + c.aiEngine.PromptCb(ctx, &body) + } + if t == "prodia" { + c.aiEngine.PromptProdiaImage(ctx, prompt.(*aiengine.ProdiaGenerationRequest), func(completion interface{}) error { + ctx.Writer.Header().Set(constants.HEADER_CONTENT_TYPE, constants.CONTENT_TYPE_EVENT_STREAM) + marshalledResponse, err := json.Marshal(completion) + if err != nil { + return err + } + _, err = ctx.Writer.Write([]byte(fmt.Sprintf("data: %s\n\n", marshalledResponse))) + if err != nil { + return err + } + + ctx.Writer.Flush() + return nil + }) + } + return } - // TODO: Implement logic to get model based on session id for remote session, for now return default model - body.Model = c.GetModelForPrompt() err := c.service.SendPrompt(ctx, ctx.Writer, &body, head.SessionID.Hash) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) @@ -102,11 +124,11 @@ func (c *ProxyController) Prompt(ctx *gin.Context) { // GetLocalModels godoc // -// @Summary Get local models -// @Tags wallet -// @Produce json -// @Success 200 {object} []aiengine.LocalModel -// @Router /v1/models [get] +// @Summary Get local models +// @Tags chat +// @Produce json +// @Success 200 {object} []aiengine.LocalModel +// @Router /v1/models [get] func (c *ProxyController) Models(ctx *gin.Context) { models, err := c.aiEngine.GetLocalModels() if err != nil { @@ -116,6 +138,35 @@ func (c *ProxyController) Models(ctx *gin.Context) { ctx.JSON(http.StatusOK, models) } -func (c *ProxyController) GetModelForPrompt() string { - return "llama2" +func (c *ProxyController) GetBodyForLocalPrompt(modelId string, req *openai.ChatCompletionRequest) (interface{}, string) { + if modelId == "" { + req.Model = "llama2" + return req, "openai" + } + + ids, models := c.aiEngine.GetModelsConfig() + + for i, model := range models { + if ids[i] == modelId { + if model.ApiType == "openai" { + req.Model = model.ModelName + return req, model.ApiType + } + + if model.ApiType == "prodia" { + prompt := &aiengine.ProdiaGenerationRequest{ + Model: model.ModelName, + Prompt: req.Messages[0].Content, + ApiUrl: model.ApiURL, + ApiKey: model.ApiKey, + } + return prompt, model.ApiType + } + + return req, "openai" + } + } + + req.Model = "llama2" + return req, "openai" } diff --git a/proxy-router/internal/proxyapi/morrpcmessage/abi.go b/proxy-router/internal/proxyapi/morrpcmessage/abi.go index 34e6c19b..4a1e09b0 100644 --- a/proxy-router/internal/proxyapi/morrpcmessage/abi.go +++ b/proxy-router/internal/proxyapi/morrpcmessage/abi.go @@ -11,7 +11,8 @@ var approvalAbi = []lib.AbiParameter{ var sessionReportAbi = []lib.AbiParameter{ {Type: "bytes32"}, // sessionID - {Type: "uint128"}, // chainID + {Type: "uint256"}, // chainID + {Type: "uint128"}, // timestamp {Type: "uint32"}, // tpsScaled1000 {Type: "uint32"}, // ttftMs } diff --git a/proxy-router/internal/proxyapi/proxy_receiver.go b/proxy-router/internal/proxyapi/proxy_receiver.go index 39914c96..11182487 100644 --- a/proxy-router/internal/proxyapi/proxy_receiver.go +++ b/proxy-router/internal/proxyapi/proxy_receiver.go @@ -112,6 +112,10 @@ func (s *ProxyReceiver) SessionPrompt(ctx context.Context, requestID string, use err = s.aiEngine.PromptProdiaImage(ctx, prodiaReq, responseCb) } else { + req.Model = session.ModelName + if req.Model == "" { + req.Model = "llama2" + } _, err = s.aiEngine.PromptStream(ctx, req, responseCb) } diff --git a/proxy-router/internal/proxyapi/proxy_sender.go b/proxy-router/internal/proxyapi/proxy_sender.go index db6e8cc9..28dce176 100644 --- a/proxy-router/internal/proxyapi/proxy_sender.go +++ b/proxy-router/internal/proxyapi/proxy_sender.go @@ -312,14 +312,14 @@ func (p *ProxyServiceSender) rpcRequestStream(ctx context.Context, resWriter Res return lib.WrapError(ErrDecrFailed, err) } - var payload openai.ChatCompletionResponse + var payload ChatCompletionResponse err = json.Unmarshal(aiResponse, &payload) var stop = true if err == nil { stop = false choices := payload.Choices for _, choice := range choices { - if choice.FinishReason == openai.FinishReasonStop { + if choice.FinishReason == FinishReasonStop { stop = true } } diff --git a/proxy-router/internal/proxyapi/requests.go b/proxy-router/internal/proxyapi/requests.go index c3d12409..7664b796 100644 --- a/proxy-router/internal/proxyapi/requests.go +++ b/proxy-router/internal/proxyapi/requests.go @@ -23,6 +23,7 @@ type PromptReq struct { type PromptHead struct { SessionID lib.Hash `header:"session_id" validate:"hex32"` + ModelID lib.Hash `header:"model_id" validate:"hex32"` } type InferenceRes struct { diff --git a/proxy-router/internal/storages/storage.go b/proxy-router/internal/storages/storage.go index 61bf0af6..a0a03649 100644 --- a/proxy-router/internal/storages/storage.go +++ b/proxy-router/internal/storages/storage.go @@ -1,6 +1,8 @@ package storages import ( + "os" + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" badger "github.com/dgraph-io/badger/v4" ) @@ -10,6 +12,9 @@ type Storage struct { } func NewStorage(log lib.ILogger, path string) *Storage { + if err := os.Mkdir(path, os.ModePerm); err != nil { + log.Warn(err) + } opts := badger.DefaultOptions(path) opts.Logger = NewBadgerLogger(log) diff --git a/proxy-router/internal/system/controller.go b/proxy-router/internal/system/controller.go index ef105383..700d157c 100644 --- a/proxy-router/internal/system/controller.go +++ b/proxy-router/internal/system/controller.go @@ -58,12 +58,12 @@ func (s *SystemController) RegisterRoutes(r interfaces.Router) { // HealthCheck godoc // -// @Summary Healthcheck example -// @Description do ping -// @Tags healthcheck -// @Produce json -// @Success 200 {object} HealthCheckResponse -// @Router /healthcheck [get] +// @Summary Healthcheck example +// @Description do ping +// @Tags healthcheck +// @Produce json +// @Success 200 {object} HealthCheckResponse +// @Router /healthcheck [get] func (s *SystemController) HealthCheck(ctx *gin.Context) { ctx.JSON(http.StatusOK, HealthCheckResponse{ Status: "healthy", @@ -74,12 +74,12 @@ func (s *SystemController) HealthCheck(ctx *gin.Context) { // GetConfig godoc // -// @Summary Get Config -// @Description Return the current config of proxy router -// @Tags healthcheck -// @Produce json -// @Success 200 {object} ConfigResponse -// @Router /config [get] +// @Summary Get Config +// @Description Return the current config of proxy router +// @Tags healthcheck +// @Produce json +// @Success 200 {object} ConfigResponse +// @Router /config [get] func (s *SystemController) GetConfig(ctx *gin.Context) { prkey, err := s.wallet.GetPrivateKey() if err != nil { @@ -104,12 +104,12 @@ func (s *SystemController) GetConfig(ctx *gin.Context) { // GetFiles godoc // -// @Summary Get files -// @Description Returns opened files -// @Tags healthcheck -// @Produce json -// @Success 200 {object} []FD -// @Router /files [get] +// @Summary Get files +// @Description Returns opened files +// @Tags healthcheck +// @Produce json +// @Success 200 {object} []FD +// @Router /files [get] func (s *SystemController) GetFiles(ctx *gin.Context) { files, err := s.sysConfig.GetFileDescriptors(ctx, os.Getpid()) if err != nil { diff --git a/proxy-router/internal/walletapi/controller.go b/proxy-router/internal/walletapi/controller.go index 906f93fe..796d032b 100644 --- a/proxy-router/internal/walletapi/controller.go +++ b/proxy-router/internal/walletapi/controller.go @@ -27,12 +27,12 @@ func (s *WalletController) RegisterRoutes(r interfaces.Router) { // GetWallet godoc // -// @Summary Get Wallet -// @Description Get wallet address -// @Tags wallet -// @Produce json -// @Success 200 {object} interface{} -// @Router /wallet [get] +// @Summary Get Wallet +// @Description Get wallet address +// @Tags wallet +// @Produce json +// @Success 200 {object} WalletRes +// @Router /wallet [get] func (s *WalletController) GetWallet(ctx *gin.Context) { prKey, err := s.service.GetPrivateKey() if err != nil { @@ -42,18 +42,18 @@ func (s *WalletController) GetWallet(ctx *gin.Context) { if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } - ctx.JSON(http.StatusOK, gin.H{"address": addr}) + ctx.JSON(http.StatusOK, WalletRes{Address: addr}) } // SetupWallet godoc // -// @Summary Set Wallet -// @Description Set wallet private key -// @Tags wallet -// @Produce json -// @Param privatekey body walletapi.SetupWalletReqBody true "Private key" -// @Success 200 {object} interface{} -// @Router /wallet [post] +// @Summary Set Wallet +// @Description Set wallet private key +// @Tags wallet +// @Produce json +// @Param privatekey body walletapi.SetupWalletReqBody true "Private key" +// @Success 200 {object} statusRes +// @Router /wallet [post] func (s *WalletController) SetupWallet(ctx *gin.Context) { var req SetupWalletReqBody err := ctx.ShouldBindJSON(&req) @@ -66,5 +66,5 @@ func (s *WalletController) SetupWallet(ctx *gin.Context) { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - ctx.JSON(http.StatusOK, gin.H{"status": "ok"}) + ctx.JSON(http.StatusOK, OkRes()) } diff --git a/proxy-router/internal/walletapi/structs.go b/proxy-router/internal/walletapi/structs.go index 1243f2ea..e63c585e 100644 --- a/proxy-router/internal/walletapi/structs.go +++ b/proxy-router/internal/walletapi/structs.go @@ -1,7 +1,24 @@ package walletapi -import "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" +import ( + "github.com/MorpheusAIs/Morpheus-Lumerin-Node/proxy-router/internal/lib" + "github.com/ethereum/go-ethereum/common" +) type SetupWalletReqBody struct { PrivateKey lib.HexString `json:"privateKey" binding:"required" validate:"required,eth_addr"` } + +type WalletRes struct { + Address common.Address `json:"address" example:"0x1234"` +} + +type statusRes struct { + Status string `json:"status" example:"ok"` +} + +func OkRes() statusRes { + return statusRes{ + Status: "ok", + } +} diff --git a/proxy-router/models-config.json.example b/proxy-router/models-config.json.example index f28da51d..d327a10d 100644 --- a/proxy-router/models-config.json.example +++ b/proxy-router/models-config.json.example @@ -4,5 +4,9 @@ "apiType": "prodia", "apiUrl": "https://api.prodia.com/v1/sd/generate", "apiKey": "API_KEY_HERE" + }, + "0x6a4813e866a48da528c533e706344ea853a1d3f21e37b4c8e7ffd5ff25779018": { + "modelName": "llama2", + "apiType": "openai" } } \ No newline at end of file diff --git a/proxy-router/test/ai_engine_test.go b/proxy-router/test/ai_engine_test.go index 4b79abcb..b8b7fadb 100644 --- a/proxy-router/test/ai_engine_test.go +++ b/proxy-router/test/ai_engine_test.go @@ -13,7 +13,7 @@ import ( ) func AiEngine_Prompt(t *testing.T) { - aiEngine := aiengine.NewAiEngine("http://localhost:11434/v1", "", lib.NewTestLogger()) + aiEngine := aiengine.NewAiEngine("http://localhost:11434/v1", "", nil, lib.NewTestLogger()) req := &api.ChatCompletionRequest{ Model: "llama2", MaxTokens: 100, @@ -31,7 +31,7 @@ func AiEngine_Prompt(t *testing.T) { } func TestAiEngine_PromptStream(t *testing.T) { - aiEngine := aiengine.NewAiEngine("http://localhost:11434/v1", "", lib.NewTestLogger()) + aiEngine := aiengine.NewAiEngine("http://localhost:11434/v1", "", nil, lib.NewTestLogger()) req := &api.ChatCompletionRequest{ Model: "llama2", MaxTokens: 100, diff --git a/readme.md b/readme.md index 20dad8bc..278e71e1 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,5 @@ # Morpheus Lumerin Node +![Simple-Overview](docs/images/simple.png) The purpose of this software is to enable interaction with distributed, decentralized LLMs on the Morpheus network through a desktop chat experience. The steps listed below are for both the Consumer and Provider to get started with the Morpheus Lumerin Node. As the software is developed, both onboarding & configuration of the provider and consumer roles will be simplified, automated and more transparent to the end user. @@ -6,122 +7,26 @@ The steps listed below are for both the Consumer and Provider to get started wit # **NOTE: ARBITRUM SEPOLIA TESTNET ONLY at this time - DEVELOPER PREVIEW ONLY** **Components that are included in this repository are:** -* Lumerin Proxy-Router is a background process that monitors sepcific blockchain contract events, manages secure sessions between consumers and providers and routes prompts and responses between them -* Lumerin UI-Desktop is the front end UI to interact with LLMs and the Morpheus network via the proxy-router -* Local Llama.cpp and tinyllama model to run locally +* Local `Llama.cpp` and tinyllama model to run locally for demonstration purposes only +* Lumerin `proxy-router` is a background process that monitors sepcific blockchain contract events, +manages secure sessions between consumers and providers and routes prompts and responses between them +* Lumerin `ui-desktop` is the front end UI to interact with LLMs and the Morpheus network via the proxy-router as a consumer ## Tokens and Contract Information * Morpheus saMOR Token: `0xc1664f994fd3991f98ae944bc16b9aed673ef5fd` * Lumerin Morpheus Smart Contract : `0x8e19288d908b2d9F8D7C539c74C899808AC3dE45` * Interact with the Morpheus Contract: https://louper.dev/diamond/0x8e19288d908b2d9F8D7C539c74C899808AC3dE45?network=arbitrumSepolia#write * Blockchain Explorer: `https://sepolia.arbiscan.io/` +* Swagger API: `http://localhost:8082/swagger/index.html` -## Prerequisites -* **WALLET:** For testing, you will need both `saMOR` and `saETH` tokens in your wallet. You should be able to get either of these from the usual Sepolia Arbitrum testnet faucets. +## Funds +* **WALLET:** For testing as a provider or consumer, you will need both `saMOR` and `saETH` tokens in your wallet. You should be able to get either of these from the usual Sepolia Arbitrum testnet faucets. * `saMOR` is the token used to pay for the model provider staking and consumer usage * `saETH` is the token used to pay for the gas fees on the network - * At this time, we recommend setting up a new wallet in either MetaMask or other existing crypto wallet - * You will need both the wallet's `private key` and `mnemonic` from your wallet to startup and interact with both the proxy-router and the UI-Desktop - * AS ALWAYS: **NEVER** share either of these two items with anyone else, as they are the keys to your wallet and can be used to access your funds - * In the future, as the UI-Desktop functionality is developed, all of this will be handled in the UI and you will not need to manually enter these items -* **ETH NODE:** You will need access to either a public or private Sepolia Arbitrum ETH node that the proxy-router and ui-desktop will use to monitor events on the blockchain - * We have provided an existing public node example `https://arbitrum-sepolia.blockpi.network/v1/rpc/public` in the .env-example file for you to use, however, - * We stronly recommend either a wss: or https: private endpoint from a trusted service like Alchemy or Infura (both offer free accounts for testing/private usage) +## Installation & Operation +* [00-Overview](docs/00-overview.md) - This provides a comprehensive picture of the Provider, Blockchain and Consumer environments and how they interact. This will also link to other documents for more advanced setup and configuration. +* [04-Consumer-Setup](docs/04-consumer-setup.md) - This is the simplest way to get started with the Morpheus Lumerin Node as a Consumer. This will allow you to interact with the Morpheus network and the models offered on the network as a consumer running from packaged releases. -## Getting Started (using the provided releases - run from source instructions coming soon) -### Common Steps for both Consumer and Provider: -1. PreRequisites: - 1. Have Mnemonic and Private key for a Consumer Wallet (Suggest setting one up in MetaMask so you can pull the mnemonic and private key) - 2. Obtain or Transfer saMOR and saETH to this wallet for testing (suggest 1000 saMOR and at least 0.1 saETH) - Sepolia Arbitrum Chain -2. Download latest release for your operating system: https://github.com/Lumerin-protocol/Morpheus-Lumerin-Node/releases -3. Extract the zip to a local folder (examples) - * Windows: `(%USERPROFILE%)/Downloads/morpheus)` - * Linux & MacOS: `~/Downloads/morpheus` - * On MacOS you may need to execute `xattr -c mor-launch proxy-router ui-desktop.app` in a command window to remove the quarantine flag on MacOS -4. Edit the .env file (this is a hidden file, please use your OS specific method to show hidden files) - * Change `ETH_NODE_ADDRESS=` (you can setup a free one in Alchemy or Infura) - * Change `WALLET_PRIVATE_KEY=` This will be the private key of the Wallet you setup previously - -### Consumer (Local LLM, Proxy-Router & UI-Desktop): -1. Assuming that you have already setup the prerequisites and downloaded the latest release, you can follow the steps below to get started -2. Launch the node - this should open a command window to see local LLM model server and proxy-router start and then should launch the user interface - * Windows: Double click the `mor-launch.exe` (You will need to tell Windows Defender this is ok to run) - * Linux & MacOS: Open a terminal and navigate to the folder and run `./mor-launch` -3. Startup User Interface: - 1. Read & accept terms & Conditions - 2. Set a strong password (this is for the UI-Desktop only) - 3. When prompted to create the wallet from a new Mnemonic, select **Recover your wallet** from `Saved Mnemonic` instead. - * This is important so that the private key for the proxy-router (in the .env file) and the wallet running in the UI are the same - * If you create a new wallet, the proxy-router will be listening to a different wallet address than the UI is managing and this will not work. -4. Local Test: Once the UI is up and running, - 1. You should see tokens for saETH and saMOR that you sent to this wallet earlier. If not, either the ETH_NODE_ADDRESS is incorrect or the wallet address is not the same as the one you sent the tokens to - 2. Click on the `Chat` icon on the left side of the screen - 3. Make sure the `Local Model` is selected - 4. Begin your conversation with the model by typing in the chat window and pressing `Enter` - * You should see the model respond with the appropriate response to the prompt you entered, if not, then likely the configuration in the .env file is incorrect or the model is not running correctly -5. Remote Test: Once you've verified that your wallet can access the blockchain and you can see the local model working, you can switch to a remote model and test that as well - 1. In the `Chat` window, select `Change Model ` - 1. Select a different model from remote providers - 2. DropDown and select the contract address of the model you want to use - 3. Click Change - 4. Click Open Session - 5. MUST Enter at least **5** MOR to open session - 3. You can now chat with the remote model and see the responses in the chat window -6. Cleanup/Closeout - * Manually End all Remote Sessions: - * In the Chat Window, click on the Time icon to the right of the Model line - this will expand and show current sessions, click the "X" next to each one to make sure it's closed - * Closing the UI-Desktop window should leave the CMD window open - * You’ll have to ctrl-c in the window to kill the local model and proxy-router - * To FULLY delete and force a clean startup of the UI (including forcing new password and mnemonic recovery), delete the ui-desktop folder and start the UI again - * Windows:  `%USERPROFILE%\AppData\Roaming\ui-desktop` - * Linux: `~/.config/ui-desktop` - * MacOS: `~/Library/Application Support/ui-desktop` - -### Provider (Local LLM to offer, Proxy-Router running as background/service): -This section is used for offering your hosted LLM model to the network for others to use. - -**At this time, we are not onboarding any new providers, but you can follow the steps below to see how it would work and will be automated and simplified in the future.** -1. SETUP PROVIDER / MODEL / BID: - 1. WEB3/Arbiscan/Metamask: Authorize Diamond Contract to spend on the Provider's behalf - 1. https://sepolia.arbiscan.io/address/0xc1664f994fd3991f98ae944bc16b9aed673ef5fd#writeContract - 2. Connect to Web3 (connect Provider wallet) - 3. Click Approve - 4. Spender Address = Diamond Contract - 5. Authorized Amount = remember that this is in the form 1*10^18 so make sure there's enough MOR granted to cover the contract fees - 6. The Diamond Contract is now authorized to spend MOR on provider's behalf - 2. Create Provider in the Diamond contract via Louper: - 1.https://louper.dev/diamond/0x8e19288d908b2d9F8D7C539c74C899808AC3dE45?network=arbitrumSepolia#write - 2. Connect Wallet (approve via MM) - 3. Select ProviderRegistry/providerRegister function - 1. addr = Provider address - 2. addStake = Amount of stake for provider to risk - Stake can be 0 now - 3. Endpoint = Publicly accessible endpoint for provider (ip:port or fqdn:port no protocol) eg: `mycoolmornode.domain.com:3989` - 3. Create Model in the contract: - 1. Select ModelRegistry/modelRegister function - 1. modelId: random 32byte/hex that will uniquely identify model (uuid) - 2. ipfsCID: another random32byte/hex for future use (model library) - 3. Fee: fee for the model usage - 0 for now - 4. addStake: stake for model usage - 0 for now - 5. Owner: Provider Wallet Address - 6. name: Human Readable model like "Llama 2.0" or "Mistral 2.5" or "Collective Cognition 1.1" - 7. tags: comma delimited tags for the model - 8. Capture the `modelID` from the JSON response - 4. Offer Model Bid in the contract: - 1. Select Marketplace/postModelBid function - 1. providerAddr: Provider Wallet Address - 2. modelID: Model ID Created in last step: - 3. pricePerSecond: this is in 1*10^18 format so 100000000000 should make 5 minutes for the session around 37.5 saMOR) - 4. Click WRITE and confirm via MM -2. LAUNCH LOCAL MODEL: - * On your server (that is accessible at the `provider endpoint` you provided in the contract), launch the local model server - * You can use the provided `llama.cpp` and `tinyllama` model to test locally - * If your local model is listening on a different port locally, you will need to modify the `OPENAI_BASE_URL` in the .env file to match the correct port -3. LAUNCH PROXY-ROUTER: - * On your server, launch the proxy-router with the modified .env file shown in the common pre-requisites section - * Windows: Double click the `proxy-router.exe` (You will need to tell Windows Defender this is ok to run) - * Linux & MacOS: Open a terminal and navigate to the folder and run `./proxy-router`from the morpheus/proxy-router folder - * This will start the proxy-router in the background and begin monitoring the blockchain for events -4. VERIFY PROVIDER SETUP - * On a separate machine and with a separate wallet, you can follow the consumer steps above to verify that your model is available and working correctly \ No newline at end of file +* [02-Provider-Setup](docs/02-provider-setup.md) - This is the simplest way to get started with the Morpheus Lumerin Node as a Provider. This will allow you to connect your existing AI-Model to the Morpheus network and offer it for use by consumers. \ No newline at end of file diff --git a/ui-desktop/package.json b/ui-desktop/package.json index 8eb5dc54..3e16cd26 100644 --- a/ui-desktop/package.json +++ b/ui-desktop/package.json @@ -59,6 +59,7 @@ "react-redux": "^9.1.0", "react-router-dom": "4.3.1", "react-select": "^5.8.0", + "react-simple-image-viewer": "^1.2.2", "react-textarea-autosize": "^8.5.3", "react-virtualized": "9.20.1", "redux": "4.2.0", diff --git a/ui-desktop/src/main/index.ts b/ui-desktop/src/main/index.ts index 8521f56c..2f41069f 100644 --- a/ui-desktop/src/main/index.ts +++ b/ui-desktop/src/main/index.ts @@ -18,8 +18,8 @@ const installExtension = (install as any).default as typeof install function createWindow(): void { // Create the browser window. const mainWindow = new BrowserWindow({ - width: 900, - height: 670, + width: 1200, + height: 800, show: false, autoHideMenuBar: true, // ...(process.platform === 'linux' ? { icon } : {}), diff --git a/ui-desktop/src/renderer/src/components/Login.tsx b/ui-desktop/src/renderer/src/components/Login.tsx index fcff5154..a9ccb9e9 100644 --- a/ui-desktop/src/renderer/src/components/Login.tsx +++ b/ui-desktop/src/renderer/src/components/Login.tsx @@ -1,4 +1,3 @@ -import React from 'react' import styled from 'styled-components' import withLoginState from '../store/hocs/withLoginState' diff --git a/ui-desktop/src/renderer/src/components/bids/Bids.tsx b/ui-desktop/src/renderer/src/components/bids/Bids.tsx index c5b59b8c..d920b815 100644 --- a/ui-desktop/src/renderer/src/components/bids/Bids.tsx +++ b/ui-desktop/src/renderer/src/components/bids/Bids.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { withRouter } from 'react-router-dom'; import withBidsState from '../../store/hocs/withBidsState'; diff --git a/ui-desktop/src/renderer/src/components/chat/Chat.css b/ui-desktop/src/renderer/src/components/chat/Chat.css index 69b15528..29db8f4f 100644 --- a/ui-desktop/src/renderer/src/components/chat/Chat.css +++ b/ui-desktop/src/renderer/src/components/chat/Chat.css @@ -1,6 +1,6 @@ .history-drawer { - width: 35%!important; - min-width: 250px!important; + width: 40%!important; + min-width: 400px!important; border-left: 1px solid rgba(255, 255, 255, 0.16)!important; padding: 2.4rem!important; background: #0d1f16!important; diff --git a/ui-desktop/src/renderer/src/components/chat/Chat.styles.tsx b/ui-desktop/src/renderer/src/components/chat/Chat.styles.tsx index b82b47a2..53ad7501 100644 --- a/ui-desktop/src/renderer/src/components/chat/Chat.styles.tsx +++ b/ui-desktop/src/renderer/src/components/chat/Chat.styles.tsx @@ -191,4 +191,22 @@ export const LoadingCover = styled.div` background: rgba(0,0,0,0.4); z-index: 5; +` + +export const ImageContainer = styled.img` + cursor: pointer; + padding: 0.25rem; + background-color: var(--bs-highlight-color); + border: var(--bs-border-width) solid var(--bs-highlight-color); + border-radius: var(--bs-border-radius); + max-width: 100%; + height: 256px; + + @media (min-height: 700px) { + height: 320px; + } +` + +export const SubPriceLabel = styled.span` + color: ${p => p.theme.colors.morMain}; ` \ No newline at end of file diff --git a/ui-desktop/src/renderer/src/components/chat/Chat.tsx b/ui-desktop/src/renderer/src/components/chat/Chat.tsx index 49b068ee..e87d4657 100644 --- a/ui-desktop/src/renderer/src/components/chat/Chat.tsx +++ b/ui-desktop/src/renderer/src/components/chat/Chat.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react' // import component 👇 import Drawer from 'react-modern-drawer' -import { IconHistory, IconArrowUp, IconServer, IconInfinity } from '@tabler/icons-react'; +import { IconHistory, IconArrowUp } from '@tabler/icons-react'; import { View, ContainerTitle, @@ -17,7 +17,9 @@ import { CustomTextArrea, Control, SendBtn, - LoadingCover + LoadingCover, + ImageContainer, + SubPriceLabel } from './Chat.styles'; import { BtnAccent } from '../dashboard/BalanceBlock.styles'; import { withRouter } from 'react-router-dom'; @@ -28,9 +30,11 @@ import 'react-modern-drawer/dist/index.css' import './Chat.css' import { ChatHistory } from './ChatHistory'; import Spinner from 'react-bootstrap/Spinner'; +import { formatSmallNumber } from './utils'; import ModelSelectionModal from './modals/ModelSelectionModal'; import { parseDataChunk, makeId, getColor, isClosed } from './utils'; -import {Cooldown} from './Cooldown'; +import { Cooldown } from './Cooldown'; +import ImageViewer from "react-simple-image-viewer"; let abort = false; let cancelScroll = false; @@ -41,12 +45,14 @@ const Chat = (props) => { const [value, setValue] = useState(""); const [isLoading, setIsLoading] = useState(true); - + const [messages, setMessages] = useState([]); + const [isOpen, setIsOpen] = useState(false); const [sessions, setSessions] = useState(); const [isSpinning, setIsSpinning] = useState(false); const [meta, setMeta] = useState({ budget: 0, supply: 0 }); + const [imagePreview, setImagePreview] = useState(); const [activeSession, setActiveSession] = useState(undefined); const [chainData, setChainData] = useState(null); @@ -58,12 +64,12 @@ const Chat = (props) => { const [selectedBid, setSelectedBid] = useState(null); const [selectedModel, setSelectedModel] = useState(undefined); const [requiredStake, setRequiredStake] = useState<{ min: Number, max: number }>({ min: 0, max: 0 }) - const [balances, setBalances] = useState<{ eth: Number, mor: number }>({ eth: 0, mor: 0}); + const [balances, setBalances] = useState<{ eth: Number, mor: number }>({ eth: 0, mor: 0 }); const modelName = selectedModel?.Name || "Model"; const isLocal = selectedModel?.useLocal; - const providerAddress = isLocal ? "(local)" : selectedBid?.Provider ? abbreviateAddress(selectedBid?.Provider, 4) : null; + const providerAddress = isLocal ? "(local)" : selectedBid?.Provider ? abbreviateAddress(selectedBid?.Provider, 5) : null; const isDisabled = (!activeSession && !isLocal) || isReadonly; const isEnoughFunds = Number(balances.mor) > Number(requiredStake.min); @@ -110,10 +116,6 @@ const Chat = (props) => { }) }, []) - const [messages, setMessages] = useState([]); - - const [isOpen, setIsOpen] = useState(false); - const toggleDrawer = () => { setIsOpen((prevState) => !prevState) } @@ -134,13 +136,13 @@ const Chat = (props) => { const calculateAcceptableDuration = (pricePerSecond: number, balance: number, stakingInfo) => { const delta = 60; // 1 minute - if(balance > requiredStake.max) { + if (balance > requiredStake.max) { return 24 * 60 * 60; // 1 day in seconds } const targetDuration = Math.round((balance * Number(stakingInfo.budget)) / (Number(stakingInfo.supply) * pricePerSecond)) - - if(targetDuration - delta < 5 * 60) { + + if (targetDuration - delta < 5 * 60) { return 5 * 60; } @@ -161,9 +163,12 @@ const Chat = (props) => { if (!openedSession) { return; } - setActiveSession({ sessionId: openedSession }); - await refreshSessions(); + const allSessions = await refreshSessions(); + const targetSessionData = allSessions.find(x => x.Id == openedSession); + const targetModel = chainData.models.find(x => x.Id == targetSessionData.ModelAgentId) + const targetBid = targetModel.bids.find(x => x.Id == targetSessionData.BidID); + setSelectedBid(targetBid); } finally { setIsLoading(false); @@ -175,19 +180,19 @@ const Chat = (props) => { if (session) { try { const history = await props.client.getChatHistory(session.sessionId); - if (history.length) { - setMessages(history[0].messages || []); - } + setMessages(history.length ? (history[0].messages || []) : []); } catch (e) { props.toasts.toast('error', 'Failed to load chat history'); } } + scrollToBottom(); } const refreshSessions = async () => { const sessions = await props.getSessionsByUser(props.address); setSessions(sessions); + return sessions; } const closeSession = async (sessionId: string) => { @@ -212,13 +217,12 @@ const Chat = (props) => { const openSessions = sessions.filter(s => !isClosed(s)); const openSession = openSessions.find(s => s.Id == sessionId); - if (!openSession) { setIsReadonly(true) const closedSession = sessions.find(s => s.Id == sessionId); if (closedSession) { - await onSetActiveSession({ sessionId: closedSession.Id }) + await onSetActiveSession({ sessionId: closedSession.Id }) const selectedBid = findBid(closedSession.BidID); setSelectedBid(selectedBid); const selectedModel = chainData.models.find((m: any) => m.Id == closedSession.ModelAgentId); @@ -228,12 +232,13 @@ const Chat = (props) => { } else { setIsReadonly(false) - await onSetActiveSession({ sessionId: openSession.Id, endDate: openSession.EndsAt }) + await onSetActiveSession({ sessionId: openSession.Id, endDate: openSession.EndsAt }) const selectedBid = findBid(openSession.BidID); setSelectedBid(selectedBid); const selectedModel = chainData.models.find((m: any) => m.Id == openSession.ModelAgentId); setSelectedModel(selectedModel); } + setTimeout(scrollToBottom, 400); } const registerScrollEvent = (register) => { @@ -264,7 +269,7 @@ const Chat = (props) => { const call = async (message) => { scrollToBottom(); - const chatHistory = messages.map(m => ({ role: m.role, content: m.text })) + const chatHistory = messages.map(m => ({ role: m.role, content: m.text, isImageContent: m.isImageContent })) let memoState = [...messages, { id: makeId(6), text: value, ...userMessage }]; setMessages(memoState); @@ -272,23 +277,24 @@ const Chat = (props) => { const headers = { "Accept": "application/json" }; - if (!isLocal) { + if (isLocal) { + headers["model_id"] = selectedModel.Id; + } else { headers["session_id"] = activeSession.sessionId; } + const hasImageHistory = chatHistory.some(x => x.isImageContent); + const incommingMessage = { role: "user", content: message }; + const payload = { + stream: true, + messages: hasImageHistory ? [incommingMessage] : [...chatHistory, incommingMessage] + }; + + // If image take only last message const response = await fetch(`${props.config.chain.localProxyRouterUrl}/v1/chat/completions`, { method: 'POST', headers, - body: JSON.stringify({ - stream: true, - messages: [ - ...chatHistory, - { - role: "user", - content: message - } - ] - }) + body: JSON.stringify(payload) }).catch((e) => { console.log("Failed to send request", e) return null; @@ -314,6 +320,7 @@ const Chat = (props) => { const reader = response.body.getReader() registerScrollEvent(true); + const iconProps = { icon: modelName.toUpperCase()[0], color: getColor(modelName.toUpperCase()[0]) }; try { while (true) { if (abort) { @@ -330,13 +337,26 @@ const Chat = (props) => { const decodedString = textDecoder.decode(value, { stream: true }); const parts = parseDataChunk(decodedString); parts.forEach(part => { - if (!part?.id) { + if (part.error) { + console.warn(part.error); + return; + } + const imageContent = part.imageUrl; + + if (!part?.id && !imageContent) { return; } + + let result: any[] = []; const message = memoState.find(m => m.id == part.id); const otherMessages = memoState.filter(m => m.id != part.id); - const text = `${message?.text || ''}${part?.choices[0]?.delta?.content || ''}`.replace("<|im_start|>", "").replace("<|im_end|>", ""); - const result = [...otherMessages, { id: part.id, user: modelName, role: "assistant", text: text, icon: modelName.toUpperCase()[0], color: getColor(modelName.toUpperCase()[0]) }]; + if (imageContent) { + result = [...otherMessages, { id: part.job, user: modelName, role: "assistant", text: imageContent, isImageContent: true, ...iconProps }]; + } + else { + const text = `${message?.text || ''}${part?.choices[0]?.delta?.content || ''}`.replace("<|im_start|>", "").replace("<|im_end|>", ""); + result = [...otherMessages, { id: part.id, user: modelName, role: "assistant", text: text, ...iconProps }]; + } memoState = result; setMessages(result); scrollToBottom(); @@ -393,14 +413,16 @@ const Chat = (props) => { // TODO: Add support for custom Bid. setMessages([]); setActiveSession(undefined); + setSelectedBid(undefined); setIsReadonly(false); abort = true; if (isLocal) { - debugger; - const localModel = (chainData?.models?.find((m: any) => m.hasLocal)); + const localModel = (chainData?.models?.find((m: { Id: string }) => m.Id == modelId)); if (localModel) { setSelectedModel({ ...localModel, useLocal: true }); + } else { + props.toasts.toast('error', 'Failed to select local model'); } return; } @@ -463,9 +485,9 @@ const Chat = (props) => { (local) ) - : ( + : ( <> - {providerAddress} + {selectedBid ? formatSmallNumber(selectedBid?.PricePerSecond / (10 ** 18)) : 0} MOR/s ) } @@ -491,7 +513,11 @@ const Chat = (props) => {
{modelName}
-
Provider: {isLocal ? "(local)" : providerAddress}
+ { + (selectedBid || isLocal) &&
+ Provider: {isLocal ? "(local)" : providerAddress} +
+ }
@@ -499,34 +525,47 @@ const Chat = (props) => {
+ {imagePreview && ( + setImagePreview("")} + disableScroll={false} + backgroundStyle={{ + backgroundColor: "rgba(0,0,0,0.9)", + zIndex: 1000 + }} + closeOnClickOutside={true} + /> + )} + { messages?.length ? messages.map(x => ( - + )) - : (!isLocal && !activeSession && -
- { - isEnoughFunds ? - <> -
Staked MOR funds will be reserved to start session
-
Session may last from 5 mins to 24 hours depending on staked funds (min: {(Number(requiredStake.min) / 10 ** 18).toFixed(2)}, max: {(Number(requiredStake.max) / 10 ** 18).toFixed(2)} MOR)
- : -
To start session required balance should be at least {(Number(requiredStake.min) / 10 ** 18).toFixed(2)} MOR
- } -
- - Start -
-
) + : (!isLocal && !activeSession && +
+ { + isEnoughFunds ? + <> +
Staked MOR funds will be reserved to start session
+
Session may last from 5 mins to 24 hours depending on staked funds (min: {(Number(requiredStake.min) / 10 ** 18).toFixed(2)}, max: {(Number(requiredStake.max) / 10 ** 18).toFixed(2)} MOR)
+ : +
To start session required balance should be at least {(Number(requiredStake.min) / 10 ** 18).toFixed(2)} MOR
+ } +
+ + Start +
+
) }
@@ -560,7 +599,7 @@ const Chat = (props) => { ) } -const Message = ({ message }) => { +const Message = ({ message, onOpenImage }) => { return (
@@ -568,7 +607,11 @@ const Message = ({ message }) => {
{message.user} - {message.text} + { + message.isImageContent + ? ({ onOpenImage(message.text)} />}) + : ({message.text}) + }
) } diff --git a/ui-desktop/src/renderer/src/components/chat/ChatHistory.tsx b/ui-desktop/src/renderer/src/components/chat/ChatHistory.tsx index 1ad53392..b1150401 100644 --- a/ui-desktop/src/renderer/src/components/chat/ChatHistory.tsx +++ b/ui-desktop/src/renderer/src/components/chat/ChatHistory.tsx @@ -43,6 +43,18 @@ const HistoryEntryTitle = styled.div` white-space: nowrap; ` +const ModelName = styled.div` + text-overflow: ellipsis; + width: 250px; + height: 24px; + overflow: hidden; + text-wrap: nowrap; +` + +const Duration = styled.div` + color: white; +` + interface ChatHistoryProps { onCloseSession: (string) => void; onSelectSession: (string) => void; @@ -68,11 +80,11 @@ export const ChatHistory = (props: ChatHistoryProps) => { const title = titleObj?.title || ""; const model = props.models.find(x => x.Id == a.ModelAgentId); return ( - props.onSelectSession(a.Id)}> + props.onSelectSession(a.Id)}> {title ? {title} : null} - -
{model.Name}
-
{(a.EndsAt - a.OpenedAt) / 60} min
+ + {model?.Name} + {((a.EndsAt - a.OpenedAt) / 60).toFixed(0)} min { !isClosed(a) ? ( props.onCloseSession(a.Id)}>) :
CLOSED
} diff --git a/ui-desktop/src/renderer/src/components/chat/modals/ModelRow.tsx b/ui-desktop/src/renderer/src/components/chat/modals/ModelRow.tsx index f38c24ae..f974355e 100644 --- a/ui-desktop/src/renderer/src/components/chat/modals/ModelRow.tsx +++ b/ui-desktop/src/renderer/src/components/chat/modals/ModelRow.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import styled from 'styled-components'; import Select from "react-select"; import { @@ -6,15 +6,22 @@ import { } from '../../contracts/modals/CreateContractModal.styles'; import { abbreviateAddress } from '../../../utils'; import { formatSmallNumber } from '../utils'; -import { IconEdit, IconX } from '@tabler/icons-react'; +import { IconX } from '@tabler/icons-react'; const RowContainer = styled.div` - padding: 1.2rem 0; + padding: 1.2rem; display: grid; - grid-template-columns: 2fr 4fr 160px; + grid-template-columns: 3fr 1fr 160px; text-align: center; - box-shadow: 0 -1px 0 0 ${p => p.theme.colors.morMain} inset; + border: ${p => p.theme.colors.morMain} solid 0.5px; color: ${p => p.theme.colors.morMain}; + background: rgba(0,0,0, 0.1); + border-radius: 5px; + margin-bottom: 5px; + + &:last-child { + margin-bottom: 0 + } `; const FlexCenter = styled.div` @@ -36,19 +43,18 @@ const PriceContainer = styled.div` display: flex; justify-content: ${p => p.hasLocal ? "space-evenly" : 'center'}; align-items: center; + white-space: nowrap; ` -const UseLocalBlock = styled(FlexCenter)` - text-decoration: underline; - cursor: pointer; - - &:hover { - opacity: 0.8; - } +const ModelNameContainer = styled(FlexCenter)` + justify-content: start; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; ` const selectorStyles = { - control: (base, state) => ({ ...base, borderColor: '#20dc8e', width: '100%', background: 'transparent' }), + control: (base) => ({ ...base, borderColor: '#20dc8e', width: '100%', background: 'transparent' }), option: (base, state) => ({ ...base, backgroundColor: state.isSelected ? '#0e4353' : "#03160e", @@ -123,9 +129,9 @@ function ModelRow(props) { return ( - + {props?.model?.Name} - + { useSelect @@ -154,7 +160,7 @@ function ModelRow(props) { style={{ marginRight: '10px' }}> {formatPrice()} - + {/* setUseSelect(!useSelect)}> */} : - diff --git a/ui-desktop/src/renderer/src/components/chat/modals/ModelSelectionModal.tsx b/ui-desktop/src/renderer/src/components/chat/modals/ModelSelectionModal.tsx index ab487661..ca663202 100644 --- a/ui-desktop/src/renderer/src/components/chat/modals/ModelSelectionModal.tsx +++ b/ui-desktop/src/renderer/src/components/chat/modals/ModelSelectionModal.tsx @@ -9,12 +9,14 @@ import { import ModelRow from './ModelRow'; -const rowRenderer = (models, onChangeModel) => ({ key, index, style }) => ( - +const rowRenderer = (models, onChangeModel) => ({ index, style }) => ( +
+ +
); const bodyProps = { @@ -45,11 +47,11 @@ const ModelSelectionModal = ({ isActive, handleClose, models, onChangeModel }) = Change Model - + {({ width, height }) => ( ([{ + const [models] = useState([{ "Id": "0x0557d796a4490cb847efa225c610e56921e1aee2cefcd6e3577c5d470b5bbf80", "IpfsCID": "0x0000000000000000000000000000697066733a2f2f6970667361646472657373", "Fee": 100, diff --git a/ui-desktop/src/renderer/src/components/onboarding/VerifyMnemonicStep.tsx b/ui-desktop/src/renderer/src/components/onboarding/VerifyMnemonicStep.tsx index 43d05c59..fd2f300b 100644 --- a/ui-desktop/src/renderer/src/components/onboarding/VerifyMnemonicStep.tsx +++ b/ui-desktop/src/renderer/src/components/onboarding/VerifyMnemonicStep.tsx @@ -1,6 +1,5 @@ import * as utils from '../../store/utils'; import PropTypes from 'prop-types'; -import React from 'react'; import { TextInput, AltLayout, Btn, Sp } from '../common'; import SecondaryBtn from './SecondaryBtn'; diff --git a/ui-desktop/src/renderer/src/components/providers/ProvidersList.tsx b/ui-desktop/src/renderer/src/components/providers/ProvidersList.tsx index 1e2dfad4..5b1584a5 100644 --- a/ui-desktop/src/renderer/src/components/providers/ProvidersList.tsx +++ b/ui-desktop/src/renderer/src/components/providers/ProvidersList.tsx @@ -1,45 +1,13 @@ -import { useState, useEffect } from 'react'; import { withRouter } from 'react-router-dom'; import withProvidersState from "../../store/hocs/withProvidersState"; import styled from 'styled-components'; import Accordion from 'react-bootstrap/Accordion'; -import Card from 'react-bootstrap/Card'; import { abbreviateAddress } from '../../utils'; -import { Btn } from '../../components/common' import Table from 'react-bootstrap/Table'; import Button from 'react-bootstrap/Button'; import './Providers.css' -const ClaimBtn = styled(Btn)` - background-color: ${p => p.theme.colors.morMain}; - color: black; - font-weight: 600; - border-radius: 5px; -`; - -const CustomCard = styled(Card)` - background: #244a47!important; - color: #21dc8f!important; - border: 0.5px solid!important; - cursor: pointer!important; - - p { - color: white!important; - } - - .gap-20 { - gap: 20px!important; - } -` - -const Container = styled.div` - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: 24px; -` - const BidTable = styled(Table)` text-align: center!important; border: 0.5px solid#21dc8f!important; diff --git a/ui-desktop/src/renderer/src/main.tsx b/ui-desktop/src/renderer/src/main.tsx index 4d177899..a143cea2 100644 --- a/ui-desktop/src/renderer/src/main.tsx +++ b/ui-desktop/src/renderer/src/main.tsx @@ -1,5 +1,4 @@ // import './assets/main.css' -import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' diff --git a/ui-desktop/src/renderer/src/store/hocs/withChatState.jsx b/ui-desktop/src/renderer/src/store/hocs/withChatState.jsx index 1f7eecf0..8aca8bc3 100644 --- a/ui-desktop/src/renderer/src/store/hocs/withChatState.jsx +++ b/ui-desktop/src/renderer/src/store/hocs/withChatState.jsx @@ -15,11 +15,15 @@ const withChatState = WrappedComponent => { static displayName = `withChatState(${WrappedComponent.displayName || WrappedComponent.name})`; - getBitsByModels = async (modelId) => { + getBidsByModels = async (modelId) => { try { const path = `${this.props.config.chain.localProxyRouterUrl}/blockchain/models/${modelId}/bids` const response = await fetch(path); const data = await response.json(); + if (data.error) { + console.error(data.error); + return []; + } return data.bids; } catch (e) { @@ -46,16 +50,24 @@ const withChatState = WrappedComponent => { } closeSession = async (sessionId) => { + this.context.toast('info', 'Closing...'); try { const path = `${this.props.config.chain.localProxyRouterUrl}/blockchain/sessions/${sessionId}/close`; const response = await fetch(path, { method: "POST" }); const data = await response.json(); - return data.success; + if (data.error) { + this.context.toast('error', 'Session not closed'); + throw new Error(data.error); + } + if(data.tx) { + this.context.toast('success', 'Session successfully closed'); + } } catch (e) { console.log("Error", e) + this.context.toast('error', 'Failed to close session'); return []; } } @@ -78,8 +90,20 @@ const withChatState = WrappedComponent => { } getAllModels = async () => { - const result = await this.props.client.getAllModels(); - return result; + try { + const path = `${this.props.config.chain.localProxyRouterUrl}/blockchain/models`; + const response = await fetch(path); + const data = await response.json(); + if (data.error) { + console.error(data.error); + return []; + } + return data.models; + } + catch (e) { + console.log("Error", e) + return []; + } } getLocalModels = async () => { @@ -98,19 +122,31 @@ const withChatState = WrappedComponent => { } getModelsData = async () => { - const localModels = await this.getLocalModels(); - const models = (await this.getAllModels()).filter(m => !m.IsDeleted); - const providers = (await this.getProviders()).filter(m => !m.IsDeleted); + const [localModels, modelsResp, providersResp] = await Promise.all([ + this.getLocalModels(), + this.getAllModels(), + this.getProviders()]); + + const models = modelsResp.filter(m => !m.IsDeleted); + const providers = providersResp.filter(m => !m.IsDeleted); const providersMap = providers.reduce((a, b) => ({ ...a, [b.Address.toLowerCase()]: b }), {}); + + const responses = (await Promise.all( + models.map(async m => { + const id = m.Id; + const bids = (await this.getBidsByModels(id)) + .filter(b => +b.DeletedAt === 0) + .map(b => ({ ...b, ProviderData: providersMap[b.Provider.toLowerCase()], Model: m })); + return { id, bids } + }) + )).reduce((a,b) => ({...a, [b.id]: b.bids}), {}); + const result = []; for (const model of models) { const id = model.Id; - - const bids = (await this.getBitsByModels(id)) - .filter(b => !b.DeletedAt) - .map(b => ({ ...b, ProviderData: providersMap[b.Provider.toLowerCase()], Model: model })); - + const bids = responses[id]; + const localModel = localModels.find(lm => lm.Id == id); result.push({ ...model, bids, hasLocal: Boolean(localModel) }) @@ -120,8 +156,9 @@ const withChatState = WrappedComponent => { } getMetaInfo = async () => { - var budget = await this.props.client.getTodaysBudget(); - var supply = await this.props.client.getTokenSupply(); + const [budget, supply] = await Promise.all([ + this.props.client.getTodaysBudget(), + this.props.client.getTokenSupply()]); return { budget, supply }; } @@ -143,7 +180,6 @@ const withChatState = WrappedComponent => { onOpenSession = async ({ modelId, duration }) => { this.context.toast('info', 'Processing...'); - try { const path = `${this.props.config.chain.localProxyRouterUrl}/blockchain/models/${modelId}/session`; const body = { @@ -160,7 +196,7 @@ const withChatState = WrappedComponent => { return; } this.context.toast('success', 'Session successfully created'); - return dataResponse.sessionId; + return dataResponse.sessionID; } catch (e) { console.error(e); @@ -178,7 +214,7 @@ const withChatState = WrappedComponent => { return ( { } getBalances = async () => { - var balances = await this.props.client.getBalances(); - var rate = await this.props.client.getRates(); + const balances = await this.props.client.getBalances(); + const rate = await this.props.client.getRates(); return { balances, rate }; } diff --git a/ui-desktop/yarn.lock b/ui-desktop/yarn.lock index ae43fe74..28a0d739 100644 --- a/ui-desktop/yarn.lock +++ b/ui-desktop/yarn.lock @@ -7368,6 +7368,11 @@ react-select@^5.8.0: react-transition-group "^4.3.0" use-isomorphic-layout-effect "^1.1.2" +react-simple-image-viewer@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/react-simple-image-viewer/-/react-simple-image-viewer-1.2.2.tgz#2a93413512b76ce8d336dd5fa19660a970d05be4" + integrity sha512-Vk9p6Glm7uE4cSEBGkqZPGC3qoZcAwd48nq5/JN13NKd9rUrUIWZWFEmRzO+FVwl6c0UdjSDkthGoaoiYeWVjg== + react-textarea-autosize@^8.5.3: version "8.5.3" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz#d1e9fe760178413891484847d3378706052dd409"