From 76eea90eae6ceba7d20fb9808e58157026731742 Mon Sep 17 00:00:00 2001 From: VarunJoshi10 Date: Sun, 7 Apr 2024 19:10:11 +0530 Subject: [PATCH] added function calling with tabby --- src/api/changeNodePassword.ts | 7 +- src/api/scrapetelegram.ts | 5 +- src/api/taikoNodeCreation.ts | 3 +- src/api/telegram_auth.ts | 17 +- src/common/App.tsx | 54 ++--- src/helpers/availableChatActions.ts | 330 ++++++++++++++----------- src/helpers/determineNextAction.ts | 12 +- src/helpers/determineNextChat.ts | 317 ++++++++++++++++++++---- src/helpers/parseChatResponse.ts | 222 ++++++++--------- src/state/chat.ts | 361 ++++++++++++++++++---------- 10 files changed, 852 insertions(+), 476 deletions(-) diff --git a/src/api/changeNodePassword.ts b/src/api/changeNodePassword.ts index 254441a..886351d 100644 --- a/src/api/changeNodePassword.ts +++ b/src/api/changeNodePassword.ts @@ -8,7 +8,12 @@ export const changeNodePassword = async (params: { }): Promise => { try { const { host, username, currentPassword, newPassword } = params; - + console.log(JSON.stringify({ + host: host, + username: username, + current_password: currentPassword, + new_password: newPassword + })) // Make a POST request to change the password const response = await fetch(`${api_endpoint}/change-password`, { method: 'POST', diff --git a/src/api/scrapetelegram.ts b/src/api/scrapetelegram.ts index 424b286..8986874 100644 --- a/src/api/scrapetelegram.ts +++ b/src/api/scrapetelegram.ts @@ -1,7 +1,7 @@ // api.ts -const API_ENDPOINT = "http://43.205.198.30:80/scrape_telegram_member"; +const API_ENDPOINT = "https://autosurf.tektorch.info/scrape_telegram_member"; -export async function scrapeMembers(apiKey: string, apiHash: string, phoneNumber: string, groupName: string): Promise { +export async function scrapeMembers(apiKey: string, apiHash: string, phoneNumber: string, groupName: string): Promise { const formData = new FormData(); formData.append('group_name', groupName); formData.append('api_id', apiKey); @@ -27,6 +27,7 @@ export async function scrapeMembers(apiKey: string, apiHash: string, phoneNumber a.click(); a.remove(); console.log('CSV file downloaded successfully.'); + return 'CSV file downloaded successfully.'; } catch (error) { console.error('Error occurred while fetching data:', error); throw error; // Propagate the error to the caller diff --git a/src/api/taikoNodeCreation.ts b/src/api/taikoNodeCreation.ts index e0e3d5c..5a45fb2 100644 --- a/src/api/taikoNodeCreation.ts +++ b/src/api/taikoNodeCreation.ts @@ -32,7 +32,8 @@ export const taikoNodeEnvironmentSetup = async (params: { host: string; username export const taikoNodeAndDashboardSetup = async (params: { host: string; username: string; password: string; L1_ENDPOINT_HTTP?: string; L1_ENDPOINT_WS?: string; L1_PROPOSER_PRIVATE_KEY?: string; PROPOSE_BLOCK_TX_GAS_LIMIT?: number; BLOCK_PROPOSAL_FEE?: number; }): Promise => { try { const { host, username, password, ...otherParams } = params; - const command = `cd taiko_node && ./setup_node.sh ${otherParams.L1_ENDPOINT_HTTP} ${otherParams.L1_ENDPOINT_WS} ${otherParams.L1_PROPOSER_PRIVATE_KEY} ${otherParams.PROPOSE_BLOCK_TX_GAS_LIMIT} ${otherParams.BLOCK_PROPOSAL_FEE} %% ./setup_dashboard`; + const enb="true" + const command = `cd taiko_node && ./setup_node.sh ${otherParams.L1_ENDPOINT_HTTP} ${otherParams.L1_ENDPOINT_WS} ${enb} ${otherParams.L1_PROPOSER_PRIVATE_KEY} ${otherParams.PROPOSE_BLOCK_TX_GAS_LIMIT} ${otherParams.BLOCK_PROPOSAL_FEE} %% ./setup_dashboard`; const response = await axios.post(`${api_endpoint}/ssh-command`, { host, diff --git a/src/api/telegram_auth.ts b/src/api/telegram_auth.ts index 5528cf5..16a45e4 100644 --- a/src/api/telegram_auth.ts +++ b/src/api/telegram_auth.ts @@ -1,8 +1,8 @@ // api.ts -const API_REQ_ENDPOINT = "http://43.205.198.30:80/request_otp"; -const API_CODE_ENDPOINT = "http://43.205.198.30:80/authenticate_with_otp"; +const API_REQ_ENDPOINT = "https://autosurf.tektorch.info/request_otp"; +const API_CODE_ENDPOINT = "https://autosurf.tektorch.info/authenticate_with_otp"; -export async function requestCode(api_id: string, api_hash: string, phone: string): Promise { +export async function requestCode(api_id: string, api_hash: string, phone: string): Promise { const formData = new FormData(); formData.append('api_id', api_id); formData.append('api_hash', api_hash); @@ -19,6 +19,10 @@ export async function requestCode(api_id: string, api_hash: string, phone: strin const data = await response.json(); console.log(data); // Handle response from FastAPI console.log("otp sent successfully!"); // Call function to display success message + localStorage.setItem('telegramApiKey', api_id); + localStorage.setItem('telegramApiHash', api_hash); + + return "otp sent successfully!"; // Call function to display success message } else { throw new Error('Network response was not ok.'); } @@ -28,7 +32,7 @@ export async function requestCode(api_id: string, api_hash: string, phone: strin } } -export async function authenticateCode(phone: string,code:string): Promise { +export async function authenticateCode(phone: string,code:string): Promise { const formData = new FormData(); formData.append('phone', phone); formData.append('code', code); @@ -44,6 +48,11 @@ export async function authenticateCode(phone: string,code:string): Promise const data = await response.json(); console.log(data); // Handle response from FastAPI console.log("Authenticated successfully!"); // Call function to display success message + + localStorage.setItem('telegramPhoneNumber', phone); + localStorage.setItem('telegramlogin', 'true'); + + return "Authenticated successfully!"; // Call function to display success message } else { throw new Error('Network response was not ok.'); } diff --git a/src/common/App.tsx b/src/common/App.tsx index 9879b1b..a6275c7 100644 --- a/src/common/App.tsx +++ b/src/common/App.tsx @@ -18,14 +18,14 @@ const App: React.FC = () => { useEffect(() => { // Check local storage to see if the user has already logged in const isUserLoggedIn = localStorage.getItem('userLoggedIn'); - if (isUserLoggedIn==='true') { + if (isUserLoggedIn === 'true') { console.log(isUserLoggedIn) setLoggedIn(true); - }else{ + } else { setLoggedIn(false) } }, []); - + const handleLogout = () => { localStorage.setItem('userLoggedIn', 'false'); setLoggedIn(false); @@ -42,9 +42,9 @@ const App: React.FC = () => { } // Call the parent component callback with the selected file and message - + }; - + return ( @@ -61,40 +61,40 @@ const App: React.FC = () => { ZkSurf - {loggedIn ?( + {/* {loggedIn ?( ):NaN} + ):NaN} */} - - {loggedIn ? ( - - - Browser Automation - Chat - - - - - - - - - - - - -) : ( + + {/* {loggedIn ? ( */} + + + Browser Automation + Chat + + + + + + + + + + + + + {/* ) : ( -)} +)} */} diff --git a/src/helpers/availableChatActions.ts b/src/helpers/availableChatActions.ts index f951687..5963c50 100644 --- a/src/helpers/availableChatActions.ts +++ b/src/helpers/availableChatActions.ts @@ -1,145 +1,185 @@ -export const availableActions = [ - { - name: 'TaikoNodeEnvironmentSetup', - description: 'Sets up Taiko node environment', - args: [ - { - name: 'host', - type: 'string', - }, - { - name: 'username', - type: 'string', - }, - // { - // name: 'Password', - // type: 'string', - // }, - ], - }, - { - name: 'TaikoNodeDashboardSetup', - description: 'Sets up Taiko node and dashboard', - args: [ - { - name: 'host', - type: 'string', - }, - { - name: 'username', - type: 'string', - }, - // { - // name: 'Password', - // type: 'string', - // }, - { - name: 'http_endpoint', - type: 'string', - }, - { - name: 'ws_endpoint', - type: 'string', - }, - // { - // name: 'ENABLE_PROPOSER', - // type: 'boolean', - // }, - // { - // name: 'private_key', - // type: 'string', - // }, - { - name: 'gas_limit', - type: 'number', - }, - { - name: 'block_fee', - type: 'number', - }, - ], - }, - { - name: 'ChangeNodePassword', - description: 'Changes the password of a node', - args: [ - { - name: 'host', - type: 'string', - }, - { - name: 'username', - type: 'string', - } - ], - },{ - name: 'dmTelegramMembers', - description: 'Direct message to Telegram members', - args: [ - { - name: 'msg', - type: 'string', - }, - { - name: 'csv_file', - type: 'File', - }, - ], - }, - { - name: 'scrapeMembers', - description: 'Scrapes members of a group', - args: [ - { - name: 'groupName', - type: 'string', - }, - ], - },{ - name: 'sendEmail', - description: 'Sends an email with attachments', - args: [ - { - name: 'user_id', - type: 'string', - }, - { - name: 'subject', - type: 'string', - }, - { - name: 'msg', - type: 'string', - }, - - ], - } - ] as const; - - type AvailableAction = (typeof availableActions)[number]; - - type ArgsToObject> = { - [K in T[number]['name']]: Extract< - T[number], - { name: K } - >['type'] extends 'number' - ? number - : string; - }; - - export type ActionShape< - T extends { - name: string; - args: ReadonlyArray<{ name: string; type: string }>; - } - > = { - name: T['name']; - args: ArgsToObject; - }; - - export type ActionPayload = { - [K in AvailableAction['name']]: ActionShape< - Extract - >; - }[AvailableAction['name']]; - \ No newline at end of file +export const tools = [ + { + "type": "function", + "function": { + "name": "changeNodePassword", + "description": "Change the password for a node", + "parameters": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The host where the password change is to be applied" + }, + "username": { + "type": "string", + "description": "The username for authentication" + }, + "currentPassword": { + "type": "string", + "description": "The current password" + }, + "newPassword": { + "type": "string", + "description": "The new password" + } + }, + "required": ["host", "username", "currentPassword", "newPassword"] + } + } + }, + { + "type": "function", + "function": { + "name": "TaikoNodeEnvironmentSetup", + "description": "Sets up Taiko node environment", + "parameters": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The host where the environment setup is to be performed" + }, + "username": { + "type": "string", + "description": "The username for authentication" + }, + "password": { + "type": "string", + "description": "The password for authentication" + } + }, + "required": ["host", "username", "password"] + } + } + }, + { + "type": "function", + "function": { + "name": "TaikoNodeDashboardSetup", + "description": "Sets up Taiko node and dashboard", + "parameters": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The host where the setup is to be performed" + }, + "username": { + "type": "string", + "description": "The username for authentication" + }, + "password": { + "type": "string", + "description": "The password for authentication" + }, + "http_endpoint": { + "type": "string", + "description": "L1 HTTP endpoint" + }, + "ws_endpoint": { + "type": "string", + "description": "L1 WebSocket endpoint" + }, + "gas_limit": { + "type": "number", + "description": "Propose block transaction gas limit" + }, + "block_fee": { + "type": "number", + "description": "Block proposal fee" + } + }, + "required": ["host", "username", "password", "http_endpoint", "ws_endpoint", "gas_limit", "block_fee"] + } + } + }, + { + "type": "function", + "function": { + "name": "AuthenticateTelegram", + "description": "Authenticates the user with Telegram", + "parameters": { + "type": "object", + "properties": { + "api_id": { + "type": "string", + "description": "API ID for Telegram authentication" + }, + "api_hash": { + "type": "string", + "description": "API hash for Telegram authentication" + }, + "phone": { + "type": "string", + "description": "Phone number for Telegram authentication" + } + }, + "required": ["api_id", "api_hash", "phone"] + } + } + }, + { + "type": "function", + "function": { + "name": "dmTelegramMembers", + "description": "Direct message to Telegram members", + "parameters": { + "type": "object", + "properties": { + "msg": { + "type": "string", + "description": "Message content to be sent" + }, + "csv_file": { + "type": "File", + "description": "CSV file containing members to be messaged" + } + }, + "required": ["msg", "csv_file"] + } + } + }, + { + "type": "function", + "function": { + "name": "scrapeMembers", + "description": "Scrapes members of a group", + "parameters": { + "type": "object", + "properties": { + "groupName": { + "type": "string", + "description": "Name of the group to scrape members from" + } + }, + "required": ["groupName"] + } + } + }, + { + "type": "function", + "function": { + "name": "sendEmail", + "description": "Sends an email with attachments", + "parameters": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "description": "User ID of the recipient" + }, + "subject": { + "type": "string", + "description": "Subject of the email" + }, + "msg": { + "type": "string", + "description": "Message content of the email" + } + }, + "required": ["user_id", "subject", "msg"] + } + } + } +]; diff --git a/src/helpers/determineNextAction.ts b/src/helpers/determineNextAction.ts index dbfa283..47d0c77 100644 --- a/src/helpers/determineNextAction.ts +++ b/src/helpers/determineNextAction.ts @@ -46,24 +46,24 @@ export async function determineNextAction( for (let i = 0; i < maxAttempts; i++) { try { - const model="mistral" + const model="hermes-2-pro-mistral" const messages = [ - { role: 'user', content: prompt }, { - role: 'assistant', + role: 'system', content: truncatedSystemMessage, }, + { role: 'user', content: prompt }, ]; - const apiEndpoint = 'http://164.52.213.234:5000/v1/chat/completions'; // Replace with your own API endpoint + const apiEndpoint = 'http://164.52.213.234:8080/v1/chat/completions'; // Replace with your own API endpoint console.log(JSON.stringify({model,messages,temperature,max_tokens,stop})) const response = await fetch(apiEndpoint, { method: 'POST', headers: { - "x-api-key": "fa6c49ffa6f0e925b8b87795122109c1", + // "x-api-key": "fa6c49ffa6f0e925b8b87795122109c1", 'Content-Type': 'application/json',}, - body: JSON.stringify({ messages }), + body: JSON.stringify({ model,messages,temperature,max_tokens,stop }), } ); // const response = await fetch(apiEndpoint, requestOptions as RequestInit); diff --git a/src/helpers/determineNextChat.ts b/src/helpers/determineNextChat.ts index 7acd90a..0821d57 100644 --- a/src/helpers/determineNextChat.ts +++ b/src/helpers/determineNextChat.ts @@ -1,26 +1,251 @@ -import { availableActions } from './availableChatActions'; +import { tools } from "./availableChatActions"; -const formattedActions = availableActions - .map((action, i) => { - const args = action.args - .map((arg) => `${arg.name}: ${arg.type}`) - .join(', '); - return `${i + 1}. ${action.name}(${args}): ${action.description}`; - }) - .join('\n'); let myArray: { role: string; content: string; }[] = []; - const mysystemMessage = ` - You are an zkml AI assistant You are interaction AI. - You should generate responses of 10 words so that it should be conversational - You should only list the action names first then when the user ask question related to that action then you should ask to arguments for that action - DONT TELL ARGUMENTS TILL TASK IS DEFINED - You can use the following tool only if needed - ${formattedActions} - - The response should contain ChangeNodePassword("") - `; - myArray.push({role:"user",content:mysystemMessage},{role:"assistant",content:"Ok I understood"}) + const mysystemMessage = `You are AI assistant and you need to complete user task by using the tools that are given. + chat_message: | + <|im_start|>{{if eq .RoleName "assistant"}}assistant{{else if eq .RoleName "system"}}system{{else if eq .RoleName "function"}}{{.Role}}{{else if eq .RoleName "user"}}user{{end}} + {{ if eq .RoleName "assistant_function_call" }}{{end}} + {{ if eq .RoleName "function" }}{{end}} + {{if .Content}}{{.Content}}{{end}} + {{if .FunctionCall}}{{toJson .FunctionCall}}{{end}} + {{ if eq .RoleName "assistant_function_call" }}{{end}} + {{ if eq .RoleName "function" }}{{end}} + <|im_end|> +# https://huggingface.co/NousResearch/Hermes-2-Pro-Mistral-7B-GGUF#prompt-format-for-function-calling +function: | + <|im_start|>system + You are a function calling AI model. You are provided with function signatures within XML tags. You may call one or more functions to assist with the user query. Don't make assumptions about what values to plug into functions. Here are the available tools: + + {{range .Functions}} + {'type': 'function', 'function': {'name': '{{.Name}}', 'description': '{{.Description}}', 'parameters': {{toJson .Parameters}} }} + {{end}} + + Use the following pydantic model json schema for each tool call you will make: + {'title': 'FunctionCall', 'type': 'object', 'properties': {'arguments': {'title': 'Arguments', 'type': 'object'}, 'name': {'title': 'Name', 'type': 'string'}}, 'required': ['arguments', 'name']} + For each function call return a json object with function name and arguments within XML tags as follows: + + {'arguments': , 'name': } + <|im_end|> + {{.Input}} + <|im_start|>assistant + +chat: | + {{.Input}} + <|im_start|>assistant + "tools": [ + { + "type": "function", + "function": { + "name": "ChangeNodePassword", + "description": "Change the password for a node", + "parameters": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The host where the password change is to be applied" + }, + "username": { + "type": "string", + "description": "The username for authentication" + } + }, + "required": ["host", "username"] + } + } + }, + { + "type": "function", + "function": { + "name": "TaikoNodeEnvironmentSetup", + "description": "Setup Taiko node environment", + "parameters": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The host where the environment setup is to be performed" + }, + "username": { + "type": "string", + "description": "The username for authentication" + } + }, + "required": ["host", "username"] + } + } + }, + { + "type": "function", + "function": { + "name": "TaikoNodeDashboardSetup", + "description": "Setup Taiko node and dashboard", + "parameters": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The host where the setup is to be performed" + }, + "username": { + "type": "string", + "description": "The username for authentication" + }, + "L1_ENDPOINT_HTTP": { + "type": "string", + "description": "L1 HTTP endpoint" + }, + "L1_ENDPOINT_WS": { + "type": "string", + "description": "L1 WebSocket endpoint" + }, + "PROPOSE_BLOCK_TX_GAS_LIMIT": { + "type": "number", + "description": "Propose block transaction gas limit" + }, + "BLOCK_PROPOSAL_FEE": { + "type": "number", + "description": "Block proposal fee" + } + }, + "required": ["host", "username", "L1_ENDPOINT_HTTP", "L1_ENDPOINT_WS", "PROPOSE_BLOCK_TX_GAS_LIMIT", "BLOCK_PROPOSAL_FEE"] + } + } + }, + { + "type": "function", + "function": { + "name": "TaikoNodeDashboardSetup", + "description": "Sets up Taiko node and dashboard", + "parameters": { + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "The host where the setup is to be performed" + }, + "username": { + "type": "string", + "description": "The username for authentication" + }, + "password": { + "type": "string", + "description": "The password for authentication" + }, + "http_endpoint": { + "type": "string", + "description": "L1 HTTP endpoint" + }, + "ws_endpoint": { + "type": "string", + "description": "L1 WebSocket endpoint" + }, + "gas_limit": { + "type": "number", + "description": "Propose block transaction gas limit" + }, + "block_fee": { + "type": "number", + "description": "Block proposal fee" + } + }, + "required": ["host", "username", "password", "http_endpoint", "ws_endpoint", "gas_limit", "block_fee"] + } + } + }, + { + "type": "function", + "function": { + "name": "AuthenticateTelegram", + "description": "Authenticates the user with Telegram", + "parameters": { + "type": "object", + "properties": { + "api_id": { + "type": "string", + "description": "API ID for Telegram authentication" + }, + "api_hash": { + "type": "string", + "description": "API hash for Telegram authentication" + }, + "phone": { + "type": "string", + "description": "Phone number for Telegram authentication" + } + }, + "required": ["api_id", "api_hash", "phone"] + } + } + }, + { + "type": "function", + "function": { + "name": "dmTelegramMembers", + "description": "Direct message to Telegram members", + "parameters": { + "type": "object", + "properties": { + "msg": { + "type": "string", + "description": "Message content to be sent" + }, + "csv_file": { + "type": "File", + "description": "CSV file containing members to be messaged" + } + }, + "required": ["msg", "csv_file"] + } + } + }, + { + "type": "function", + "function": { + "name": "scrapeMembers", + "description": "Scrapes members of a group", + "parameters": { + "type": "object", + "properties": { + "groupName": { + "type": "string", + "description": "Name of the group to scrape members from" + } + }, + "required": ["groupName"] + } + } + }, + { + "type": "function", + "function": { + "name": "sendEmail", + "description": "Sends an email with attachments", + "parameters": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "description": "User ID of the recipient" + }, + "subject": { + "type": "string", + "description": "Subject of the email" + }, + "msg": { + "type": "string", + "description": "Message content of the email" + } + }, + "required": ["user_id", "subject", "msg"] + } + } + } +] + + You should ask user for parameters that are required to perform a task and keep it null it user has not entered it the default value should be null only`; + myArray.push({role:"system",content:mysystemMessage}) export async function determineNextChat( taskInstructions: string, maxAttempts = 3, @@ -28,38 +253,37 @@ export async function determineNextChat( ) { - const systemMessage = ` - You are an zkml AI assistant. - You can use the following tool - - 1. Change Node Password(host: string, username: string, currentPassword: string, newPassword: string): Change the password for a node - 2. Setup Taiko Node Environment(host: string, username: string, password: string): Setup Taiko node environment - 3. Setup Taiko Node and Dashboard(host: string, username: string, password: string, L1_ENDPOINT_HTTP: string, L1_ENDPOINT_WS: string, ENABLE_PROPOSER: boolean, L1_PROPOSER_PRIVATE_KEY: string, PROPOSE_BLOCK_TX_GAS_LIMIT: number, BLOCK_PROPOSAL_FEE: number): Setup Taiko node and dashboard - You should ask user to enter required parameters for the function they ask for - You should return response in this format taikoNodeEnvironmentSetup('192.162.2.1','root','admin123') only if the user gives all params for action - - `; - // taikoNodeEnvironmentSetup('192.162.2.1','root','admin123') - // const apiEndpoint = 'https://leo.tektorch.info/chat/completions'; // Replace with your own API endpoint const apiEndpoint = 'http://164.52.213.234:5000/v1/chat/completions'; // Replace with your own API endpoint - - const maxSystemMessageLength = 3000; // Choose a reasonable length for the system message - const truncatedSystemMessage = systemMessage.substring(0, maxSystemMessageLength); +// const apiEndpoint = 'http://164.52.213.234:8080/v1/chat/completions'; // Replace with your own API endpoint myArray.push({role:'user',content:taskInstructions}) - for (let i = 0; i < maxAttempts; i++) { + // Define the request payload + const payload = { + "model": "mistral", + "messages": myArray, + "prompt_template":"mytmp", + "temperature": 0.1, + +}; + + + +// Define the headers +const headers = { + "Content-Type": "application/json" +}; + try { - const model="mistral" - const messages = myArray - console.log(JSON.stringify({model,messages})) + + console.log(JSON.stringify({payload})) const response = await fetch(apiEndpoint, { method: 'POST', headers: { - "x-api-key": "fa6c49ffa6f0e925b8b87795122109c1", + "x-api-key": "39361746844cf9e0cbefdb80d1b081f1", 'Content-Type': 'application/json',}, - body: JSON.stringify({ messages }), + body: JSON.stringify(payload), } ); // const response = await fetch(apiEndpoint, requestOptions as RequestInit); @@ -67,13 +291,16 @@ export async function determineNextChat( const data = await response.json(); myArray.push(data.choices[0].message) console.log("Chat history is: ",myArray) + console.log("Response is:",data) console.log(data.choices[0].message?.content?.trim()); return { usage: data.usage, // Replace with your own API response format + choices:data.choices, prompt, - response: data.choices[0].message?.content?.trim(), // Replace with your own API response format + response: data.choices[0].message?.content?.trim(), + finish: data.choices[0].finish_reason, // Replace with your own API response format }; } catch (error: any) { console.log('determineNextAction error', error); @@ -88,8 +315,4 @@ export async function determineNextChat( } } } - throw new Error( - `Failed to complete query after ${maxAttempts} attempts. Please try again later.` - ); -} diff --git a/src/helpers/parseChatResponse.ts b/src/helpers/parseChatResponse.ts index e8aa3a1..d377c6a 100644 --- a/src/helpers/parseChatResponse.ts +++ b/src/helpers/parseChatResponse.ts @@ -1,111 +1,111 @@ -import { ActionPayload, availableActions } from "./availableChatActions"; - -availableActions -export type ParsedResponseSuccess = { - // thought: string; - action: string; - parsedAction: ActionPayload; -}; - -export type ParsedResponse = - | ParsedResponseSuccess - | { - error: string; - }; - -export function parseResponse(text: string): ParsedResponse { - // const thoughtMatch = text.match(/(.*?)<\/Thought>/); - const actionMatch = text.match(/(.*?)<\/Action>/); - - // if (!thoughtMatch) { - // return { - // error: 'Invalid response: Thought not found in the model response.', - // }; - // } - - if (!actionMatch) { - return { - error: 'Invalid response: Action not found in the model response.', - }; - } - - // const thought = thoughtMatch[1]; - const actionString = actionMatch[1]; - const actionPattern = /(\w+)\((.*?)\)/; - const actionParts = actionString.match(actionPattern); - - if (!actionParts) { - return { - error: - 'Invalid action format: Action should be in the format functionName(arg1, arg2, ...).', - }; - } - - const actionName = actionParts[1]; - const actionArgsString = actionParts[2]; - - const availableAction = availableActions.find( - (action) => action.name === actionName - ); - - if (!availableAction) { - return { - error: `Invalid action: "${actionName}" is not a valid action.`, - }; - } - - const argsArray = actionArgsString - .split(',') - .map((arg) => arg.trim()) - .filter((arg) => arg !== ''); - const parsedArgs: Record = {}; - - if (argsArray.length !== availableAction.args.length) { - return { - error: `Invalid number of arguments: Expected ${availableAction.args.length} for action "${actionName}", but got ${argsArray.length}.`, - }; - } - - for (let i = 0; i < argsArray.length; i++) { - const arg = argsArray[i]; - const expectedArg = availableAction.args[i]; - - if (expectedArg.type === 'number') { - const numberValue = Number(arg); - - if (isNaN(numberValue)) { - return { - error: `Invalid argument type: Expected a number for argument "${expectedArg.name}", but got "${arg}".`, - }; - } - - parsedArgs[expectedArg.name] = numberValue; - } else if (expectedArg.type === 'string') { - const stringValue = - (arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'")) || (arg.startsWith("`") && arg.endsWith("`")) ? arg.slice(1, -1) : null; - - if (stringValue === null || stringValue.includes('<')||stringValue.includes('>')) { - return { - error: `Invalid argument type: Expected a string for argument "${expectedArg.name}", but got "${arg}".`, - }; - } - - parsedArgs[expectedArg.name] = stringValue; - } else { - return { - error: `Invalid argument type: Unknown type "${expectedArg}" for argument "${expectedArg}".`, - }; - } - } - - const parsedAction = { - name: availableAction.name, - args: parsedArgs, - } as ActionPayload; - - return { - // thought, - action: actionString, - parsedAction, - }; -} \ No newline at end of file +// import { ActionPayload, availableActions } from "./availableChatActions"; + +// availableActions +// export type ParsedResponseSuccess = { +// // thought: string; +// action: string; +// parsedAction: ActionPayload; +// }; + +// export type ParsedResponse = +// | ParsedResponseSuccess +// | { +// error: string; +// }; + +// export function parseResponse(text: string): ParsedResponse { +// // const thoughtMatch = text.match(/(.*?)<\/Thought>/); +// const actionMatch = text.match(/(.*?)<\/Action>/); + +// // if (!thoughtMatch) { +// // return { +// // error: 'Invalid response: Thought not found in the model response.', +// // }; +// // } + +// if (!actionMatch) { +// return { +// error: 'Invalid response: Action not found in the model response.', +// }; +// } + +// // const thought = thoughtMatch[1]; +// const actionString = actionMatch[1]; +// const actionPattern = /(\w+)\((.*?)\)/; +// const actionParts = actionString.match(actionPattern); + +// if (!actionParts) { +// return { +// error: +// 'Invalid action format: Action should be in the format functionName(arg1, arg2, ...).', +// }; +// } + +// const actionName = actionParts[1]; +// const actionArgsString = actionParts[2]; + +// const availableAction = availableActions.find( +// (action) => action.name === actionName +// ); + +// if (!availableAction) { +// return { +// error: `Invalid action: "${actionName}" is not a valid action.`, +// }; +// } + +// const argsArray = actionArgsString +// .split(',') +// .map((arg) => arg.trim()) +// .filter((arg) => arg !== ''); +// const parsedArgs: Record = {}; + +// if (argsArray.length !== availableAction.args.length) { +// return { +// error: `Invalid number of arguments: Expected ${availableAction.args.length} for action "${actionName}", but got ${argsArray.length}.`, +// }; +// } + +// for (let i = 0; i < argsArray.length; i++) { +// const arg = argsArray[i]; +// const expectedArg = availableAction.args[i]; + +// if (expectedArg.type === 'number') { +// const numberValue = Number(arg); + +// if (isNaN(numberValue)) { +// return { +// error: `Invalid argument type: Expected a number for argument "${expectedArg.name}", but got "${arg}".`, +// }; +// } + +// parsedArgs[expectedArg.name] = numberValue; +// } else if (expectedArg.type === 'string') { +// const stringValue = +// (arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'")) || (arg.startsWith("`") && arg.endsWith("`")) ? arg.slice(1, -1) : null; + +// if (stringValue === null || stringValue.includes('<')||stringValue.includes('>')) { +// return { +// error: `Invalid argument type: Expected a string for argument "${expectedArg.name}", but got "${arg}".`, +// }; +// } + +// parsedArgs[expectedArg.name] = stringValue; +// } else { +// return { +// error: `Invalid argument type: Unknown type "${expectedArg}" for argument "${expectedArg}".`, +// }; +// } +// } + +// const parsedAction = { +// name: availableAction.name, +// args: parsedArgs, +// } as ActionPayload; + +// return { +// // thought, +// action: actionString, +// parsedAction, +// }; +// } \ No newline at end of file diff --git a/src/state/chat.ts b/src/state/chat.ts index 7f91f2c..ac6690f 100644 --- a/src/state/chat.ts +++ b/src/state/chat.ts @@ -2,9 +2,11 @@ import { create } from 'zustand'; import { determineNextChat } from '../helpers/determineNextChat'; import getLastUserMessage from '../helpers/getLastUserMessage'; import { taikoNodeEnvironmentSetup, taikoNodeAndDashboardSetup } from "../api/taikoNodeCreation"; -import { parseResponse, ParsedResponseSuccess } from '../helpers/parseChatResponse'; import { changeNodePassword } from '../api/changeNodePassword'; import { sendEmail } from '../api/sendEmail'; +import { authenticateCode, requestCode } from '../api/telegram_auth'; +import { dmTelegramMembers } from '../api/dmtelegram'; +import { scrapeMembers } from '../api/scrapetelegram'; export interface ChatMessage { id: number; @@ -21,48 +23,85 @@ export interface ChatState { message: string, file: File | null, userPass: string, - currentPassword: string, // New state variable for current password - newPassword: string, // New state variable for new password - password: string, // New state variable for password - privateKey: string // New state variable for private key + currentPassword: string, + newPassword: string, + password: string, + privateKey: string ) => Promise; showPasswordModal: boolean; setShowPasswordModal: (show: boolean) => void; - showUpdatePasswordModal: boolean; // New state variable for Update Password modal - setShowUpdatePasswordModal: (show: boolean) => void; // Function to toggle Update Password modal - showCredentialsModal: boolean; // New state variable for Credentials modal - setShowCredentialsModal: (show: boolean) => void; // Function to toggle Credentials modal + showUpdatePasswordModal: boolean; + setShowUpdatePasswordModal: (show: boolean) => void; + showCredentialsModal: boolean; + setShowCredentialsModal: (show: boolean) => void; } const useChatStore = create((set) => ({ history: [], showPasswordModal: false, setShowPasswordModal: (show) => set({ showPasswordModal: show }), - showUpdatePasswordModal: false, // Initialize Update Password modal state - setShowUpdatePasswordModal: (show) => set({ showUpdatePasswordModal: show }), // Function to toggle Update Password modal - showCredentialsModal: false, // Initialize Credentials modal state - setShowCredentialsModal: (show) => set({ showCredentialsModal: show }), // Function to toggle Credentials modal + showUpdatePasswordModal: false, + setShowUpdatePasswordModal: (show) => set({ showUpdatePasswordModal: show }), + showCredentialsModal: false, + setShowCredentialsModal: (show) => set({ showCredentialsModal: show }), addMessage: (message) => set((state) => ({ ...state, history: [...state.history, message] })), clearHistory: () => set({ history: [] }), - generateChat: async (message, file, userPass, currentPassword, newPassword, password, privateKey) => { // Modify generateChat function signature + generateChat: async ( + message, + file, + userPass, + currentPassword, + newPassword, + password, + privateKey + ) => { try { const lastUserMessage = getLastUserMessage(useChatStore.getState().history); if (!lastUserMessage) { console.error('No user message found in history.'); return; } - - const chatResponse = await determineNextChat(message); - if (chatResponse) { - const { response } = chatResponse; - - let contentWithoutActionTag = response.replace(/(.*?)<\/Action>/g, ''); + + const parseToolCallFromResponse = (response: string): { functionName: string; functionArguments: any } | null => { + try { + const matches = response.match(/(.*?)<\/tool_call>/s); + + if (matches && matches[1]) { + let toolCallJson = matches[1].trim(); + + // Attempt to make the JSON valid by replacing single quotes with double quotes + // This is simplistic and may not handle all edge cases + toolCallJson = toolCallJson.replace(/'/g, '"'); + + // Attempt to handle escaped single quotes in values + toolCallJson = toolCallJson.replace(/\\\\"/g, '\''); + + const toolCall = JSON.parse(toolCallJson); + + return { + functionName: toolCall.name, + functionArguments: toolCall.arguments, + }; + } + } catch (error) { + console.error('Error parsing tool call:', error); + } + return null; + }; + + + + + const chatResponse = await determineNextChat(message); + if (chatResponse) { + const { response } = chatResponse; + const toolCallInfo = parseToolCallFromResponse(response); + let contentWithoutActionTag = response.replace(/(.*?)<\/tool_call>/s, ''); if (contentWithoutActionTag.length === 0) { - contentWithoutActionTag = 'Please provide more details'; + contentWithoutActionTag = 'Once given all details just say call tool with given details or if tool is called then wait for some time'; } - const newMessage: ChatMessage = { id: Date.now(), sender: 'AI assistant', @@ -70,129 +109,187 @@ const useChatStore = create((set) => ({ timestamp: Date.now(), }; set((state) => ({ ...state, history: [...state.history, newMessage] })); - - const parsedResponse = parseResponse(response); - if ('error' in parsedResponse) { - console.error('Error parsing response:', parsedResponse.error); - return; - } - - const { action, parsedAction } = parsedResponse as ParsedResponseSuccess; - // const newMessage: ChatMessage = { - // id: Date.now(), - // sender: 'AI assistant', - // content: 'Please provide the required details if you Dont know what details to send Please ask me by defining the task and ask details related to it.', - // timestamp: Date.now(), - // }; - // set((state) => ({ ...state, history: [...state.history, newMessage] })); - // console.log("No function call") - const waitForDetails = async (variable: any) => { - while (!variable) { - // Do nothing and keep looping until the variable is provided - await new Promise(resolve => setTimeout(resolve, 100)); // Wait for 100 milliseconds before checking again - } - }; - - if (parsedAction.name === 'TaikoNodeEnvironmentSetup') { - if(!password || !privateKey){ - - set((state) => ({ ...state, showCredentialsModal: true })); // Show the Credentials modal - await waitForDetails(password) - } - const res=await taikoNodeEnvironmentSetup({ host: parsedAction.args.host, username: parsedAction.args.username, password: password }); // Pass password from state - set((state) => ({ ...state, showCredentialsModal: false })); // Hide the Credentials modal - const newMessage: ChatMessage = { - id: Date.now(), - sender: 'AI assistant', - content: res, - timestamp: Date.now(), - }; - set((state) => ({ ...state, history: [...state.history, newMessage] })); - } - if (parsedAction.name === "TaikoNodeDashboardSetup") { - const res=await taikoNodeAndDashboardSetup({ - host: parsedAction.args.host, - username: parsedAction.args.username, - password: password, - L1_ENDPOINT_HTTP: parsedAction.args.http_endpoint, - L1_ENDPOINT_WS: parsedAction.args.ws_endpoint, - L1_PROPOSER_PRIVATE_KEY: privateKey, - PROPOSE_BLOCK_TX_GAS_LIMIT: parsedAction.args.gas_limit, - BLOCK_PROPOSAL_FEE: parsedAction.args.block_fee - }); - const newMessage: ChatMessage = { - id: Date.now(), - sender: 'AI assistant', - content: res, - timestamp: Date.now(), - }; - set((state) => ({ ...state, history: [...state.history, newMessage] })); + const waitForDetails = async (variable: any) => { + while (!variable) { + // Do nothing and keep looping until the variable is provided + await new Promise(resolve => setTimeout(resolve, 100)); // Wait for 100 milliseconds before checking again } - if (parsedAction.name === "ChangeNodePassword") { - if(!currentPassword || !newPassword){ - - set((state) => ({ ...state, showUpdatePasswordModal: true })); // Show the Update Password modal - await waitForDetails(currentPassword); - // await waitForDetails(newPassword) - } + }; - const res = await changeNodePassword({ - host: parsedAction.args.host, - username: parsedAction.args.username, - currentPassword: currentPassword, // Pass current password from state - newPassword: newPassword, // Pass new password from state - }); - set((state) => ({ ...state, showUpdatePasswordModal: false })); // Hide the Update Password modal - const newMessage: ChatMessage = { - id: Date.now(), - sender: 'AI assistant', - content: res, - timestamp: Date.now(), - }; - set((state) => ({ ...state, history: [...state.history, newMessage] })); - } - if (parsedAction.name === "sendEmail") { - if(!password || !file){ + // const toolCallInfo = parseToolCallFromResponse(response); + if (toolCallInfo) { + const { functionName, functionArguments } = toolCallInfo; - set((state) => ({ ...state, showPasswordModal: true })); - await waitForDetails(userPass); + console.log(functionName,functionArguments.host) + // Check if all parameters are not null + const allParametersNotNull = Object.values(functionArguments).every(value => value !== 'null'); + if (allParametersNotNull) { + if (functionName === 'TaikoNodeEnvironmentSetup') { + if (!password || !privateKey) { + set((state) => ({ ...state, showCredentialsModal: true })); + await waitForDetails(password); + } + const res = await taikoNodeEnvironmentSetup({ host: functionArguments.host, username: functionArguments.username, password: password }); + set((state) => ({ ...state, showCredentialsModal: false })); + const newMessage: ChatMessage = { + id: Date.now(), + sender: 'AI assistant', + content: res, + timestamp: Date.now(), + }; + set((state) => ({ ...state, history: [...state.history, newMessage] })); } - const csv_file = file; - if (!csv_file) { + if (functionName === "TaikoNodeDashboardSetup") { + const res = await taikoNodeAndDashboardSetup({ + host: functionArguments.host, + username: functionArguments.username, + password: password, + L1_ENDPOINT_HTTP: functionArguments.http_endpoint, + L1_ENDPOINT_WS: functionArguments.ws_endpoint, + L1_PROPOSER_PRIVATE_KEY: privateKey, + PROPOSE_BLOCK_TX_GAS_LIMIT: functionArguments.gas_limit, + BLOCK_PROPOSAL_FEE: functionArguments.block_fee + }); const newMessage: ChatMessage = { id: Date.now(), sender: 'AI assistant', - content: "Please attach the csv file with Email field, then type 'Confirm Action'", + content: res, timestamp: Date.now(), }; set((state) => ({ ...state, history: [...state.history, newMessage] })); - console.error('No file attached.'); - return; } - await waitForDetails(csv_file); // Wait until csv_file is provided - - - const res=await sendEmail({ - user_id: parsedAction.args.user_id, - subject: parsedAction.args.subject, - user_pass: userPass, - msg: parsedAction.args.msg, - csv_file: csv_file - }); - set((state) => ({ ...state, showPasswordModal: false })); - const newMessage: ChatMessage = { - id: Date.now(), - sender: 'AI assistant', - content: res, - timestamp: Date.now(), - }; - set((state) => ({ ...state, history: [...state.history, newMessage] })); + if (functionName === "ChangeNodePassword") { + if (!currentPassword || !newPassword) { + set((state) => ({ ...state, showUpdatePasswordModal: true })); + await waitForDetails(currentPassword); + } + const res = await changeNodePassword({ + host: functionArguments.host, + username: functionArguments.username, + currentPassword: currentPassword, + newPassword: newPassword, + }); + set((state) => ({ ...state, showUpdatePasswordModal: false })); + const newMessage: ChatMessage = { + id: Date.now(), + sender: 'AI assistant', + content: res, + timestamp: Date.now(), + }; + set((state) => ({ ...state, history: [...state.history, newMessage] })); + } + if (functionName === "sendEmail") { + if (!password || !file) { + set((state) => ({ ...state, showPasswordModal: true })); + await waitForDetails(userPass); + } + const csv_file = file; + if (!csv_file) { + const newMessage: ChatMessage = { + id: Date.now(), + sender: 'AI assistant', + content: "Please attach the csv file with Email field, then type 'Confirm Action'", + timestamp: Date.now(), + }; + set((state) => ({ ...state, history: [...state.history, newMessage] })); + console.error('No file attached.'); + return; + } + await waitForDetails(csv_file); + const res = await sendEmail({ + user_id: functionArguments.user_id, + subject: functionArguments.subject, + user_pass: userPass, + msg: functionArguments.msg, + csv_file: csv_file + }); + set((state) => ({ ...state, showPasswordModal: false })); + const newMessage: ChatMessage = { + id: Date.now(), + sender: 'AI assistant', + content: res, + timestamp: Date.now(), + }; + set((state) => ({ ...state, history: [...state.history, newMessage] })); + } + if (functionName === 'AuthenticateTelegram') { + if (!localStorage.getItem('telegramApiKey')) { + const res = await requestCode(functionArguments.api_id, functionArguments.api_hash, functionArguments.phone) + const otp = prompt("Enter the OTP:") + if (otp === null) { + console.error('User canceled OTP input.'); + return; + } + const res1 = await authenticateCode(functionArguments.phone, otp) + const newMessage: ChatMessage = { + id: Date.now(), + sender: 'AI assistant', + content: res1, + timestamp: Date.now(), + }; + set((state) => ({ ...state, history: [...state.history, newMessage] })); + } + else { + const newMessage: ChatMessage = { + id: Date.now(), + sender: 'AI assistant', + content: "You are Already Signed-In use the Scrape members or Send DM functionality", + timestamp: Date.now(), + }; + set((state) => ({ ...state, history: [...state.history, newMessage] })); + } + } + if (functionName === "dmTelegramMembers") { + const isUserLoggedIn = localStorage.getItem('telegramlogin'); + if (isUserLoggedIn === 'true') { + const apikey = localStorage.getItem('telegramApiKey') || ''; + const apihash = localStorage.getItem('telegramApiHash') || ''; + const phone = localStorage.getItem('telegramPhoneNumber') || ''; + const csv_file = file; + if (!csv_file) { } else { + const res = await dmTelegramMembers(apikey, apihash, phone, functionArguments.msg, csv_file) + } + } else { + const newMessage: ChatMessage = { + id: Date.now(), + sender: 'AI assistant', + content: "Please use authenticate telegram to login", + timestamp: Date.now(), + }; + set((state) => ({ ...state, history: [...state.history, newMessage] })); + } + } + if (functionName === "scrapeMembers") { + const isUserLoggedIn = localStorage.getItem('telegramlogin'); + if (isUserLoggedIn === 'true') { + const apikey = localStorage.getItem('telegramApiKey') || ''; + const apihash = localStorage.getItem('telegramApiHash') || ''; + const phone = localStorage.getItem('telegramPhoneNumber') || ''; + const group = functionArguments.groupName.replace(/\\/g, ''); + const res = await scrapeMembers(apikey, apihash, phone, group); + const newMessage: ChatMessage = { + id: Date.now(), + sender: 'AI assistant', + content: res, + timestamp: Date.now(), + }; + set((state) => ({ ...state, history: [...state.history, newMessage] })); + } + else { + const newMessage: ChatMessage = { + id: Date.now(), + sender: 'AI assistant', + content: "Please use authenticate telegram to login", + timestamp: Date.now(), + }; + set((state) => ({ ...state, history: [...state.history, newMessage] })); + } + } } - - } else { - console.error('No chat response received.'); + } } - } catch (error) { + } + catch (error) { console.error('Error generating chat:', error); } },