Compare commits

...

3 Commits

Author SHA1 Message Date
jayssj11
336f30b56c auto capture of url and tab name, need to refactor code again (#3) 2024-10-29 08:04:16 +01:00
jayssj11
31a531fd07 Chat component (#2)
* request capture when server responds with Sample Requests and Responses but do not capture response yet

* break down request and response in separate entities, still need to capture response properly

* response capture works

* code refactor
2024-10-22 10:34:39 +02:00
jayssj11
4ad394419b chat window added to the extension (#1) 2024-10-02 16:47:38 +02:00
6 changed files with 506 additions and 28 deletions

161
package-lock.json generated
View File

@@ -11,13 +11,14 @@
"dependencies": {
"@extism/extism": "^1.0.2",
"@fortawesome/fontawesome-free": "^6.4.2",
"@types/axios": "^0.14.0",
"async-mutex": "^0.4.0",
"axios": "^1.7.7",
"buffer": "^6.0.3",
"charwise": "^3.0.1",
"classnames": "^2.3.2",
"comlink": "^4.4.1",
"copy-to-clipboard": "^3.3.3",
"dayjs": "^1.11.12",
"fast-deep-equal": "^3.1.3",
"fuse.js": "^6.6.2",
"level": "^8.0.0",
@@ -33,7 +34,7 @@
"redux-thunk": "^2.4.2",
"tailwindcss": "^3.3.3",
"tlsn-js": "0.1.0-alpha.6.2",
"tlsn-jsV5.3": "npm:tlsn-js@0.1.0-alpha.5.3"
"tlsn-js-v5": "npm:tlsn-js@0.1.0-alpha.5.4"
},
"devDependencies": {
"@babel/core": "^7.20.12",
@@ -3550,6 +3551,16 @@
"integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==",
"dev": true
},
"node_modules/@types/axios": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz",
"integrity": "sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==",
"deprecated": "This is a stub types definition for axios (https://github.com/mzabriskie/axios). axios provides its own type definitions, so you don't need @types/axios installed!",
"license": "MIT",
"dependencies": {
"axios": "*"
}
},
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -4836,6 +4847,12 @@
"tslib": "^2.4.0"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/autoprefixer": {
"version": "10.4.19",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
@@ -4897,6 +4914,17 @@
"node": ">=4"
}
},
"node_modules/axios": {
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axobject-query": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
@@ -5561,6 +5589,18 @@
"node": ">=0.1.90"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/comlink": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.1.tgz",
@@ -6095,11 +6135,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/dayjs": {
"version": "1.11.12",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz",
"integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg=="
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -6238,6 +6273,15 @@
"node": ">=0.10.0"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -7712,7 +7756,6 @@
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true,
"funding": [
{
"type": "individual",
@@ -7763,6 +7806,20 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -9594,7 +9651,6 @@
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -9603,7 +9659,6 @@
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"dependencies": {
"mime-db": "1.52.0"
},
@@ -11449,6 +11504,12 @@
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
@@ -13009,11 +13070,12 @@
"node": ">= 16.20.2"
}
},
"node_modules/tlsn-jsV5.3": {
"node_modules/tlsn-js-v5": {
"name": "tlsn-js",
"version": "0.1.0-alpha.5.3",
"resolved": "https://registry.npmjs.org/tlsn-js/-/tlsn-js-0.1.0-alpha.5.3.tgz",
"integrity": "sha512-eTSCQ6MaH8mN2oCfLsQDe/mdfTlIq74MbKVesV6M01C2SRE0FJcVlTWMsnT3L+wOpvOlvsiBeCWV7T9m/YMzew==",
"version": "0.1.0-alpha.5.4",
"resolved": "https://registry.npmjs.org/tlsn-js/-/tlsn-js-0.1.0-alpha.5.4.tgz",
"integrity": "sha512-qbqaDjApXarohn/XMJrxMsNHYTCzy+pw0Fc8gtPPN17PLE+1DwwLTXAAhnxYqYQyo3+Hmy+ODd4C02+KCwRWmg==",
"license": "ISC",
"dependencies": {
"comlink": "^4.4.1"
},
@@ -16331,6 +16393,14 @@
"integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==",
"dev": true
},
"@types/axios": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz",
"integrity": "sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==",
"requires": {
"axios": "*"
}
},
"@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -17380,6 +17450,11 @@
"tslib": "^2.4.0"
}
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"autoprefixer": {
"version": "10.4.19",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
@@ -17409,6 +17484,16 @@
"integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==",
"dev": true
},
"axios": {
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"requires": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"axobject-query": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
@@ -17893,6 +17978,14 @@
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
"dev": true
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"requires": {
"delayed-stream": "~1.0.0"
}
},
"comlink": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.1.tgz",
@@ -18249,11 +18342,6 @@
"is-data-view": "^1.0.1"
}
},
"dayjs": {
"version": "1.11.12",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz",
"integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg=="
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -18358,6 +18446,11 @@
}
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -19487,8 +19580,7 @@
"follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
},
"for-each": {
"version": "0.3.3",
@@ -19515,6 +19607,16 @@
}
}
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
"forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -20783,14 +20885,12 @@
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"requires": {
"mime-db": "1.52.0"
}
@@ -21881,6 +21981,11 @@
}
}
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
@@ -22990,10 +23095,10 @@
"resolved": "https://registry.npmjs.org/tlsn-js/-/tlsn-js-0.1.0-alpha.6.2.tgz",
"integrity": "sha512-6PANWQEFw48VFNfDgA017zcNRae2OutzNmE5Xcc/h6lwkZZ8MBZIFAFfz/WHZJ3fcFJumaKrG80gpomP6blZqw=="
},
"tlsn-jsV5.3": {
"version": "npm:tlsn-js@0.1.0-alpha.5.3",
"resolved": "https://registry.npmjs.org/tlsn-js/-/tlsn-js-0.1.0-alpha.5.3.tgz",
"integrity": "sha512-eTSCQ6MaH8mN2oCfLsQDe/mdfTlIq74MbKVesV6M01C2SRE0FJcVlTWMsnT3L+wOpvOlvsiBeCWV7T9m/YMzew==",
"tlsn-js-v5": {
"version": "npm:tlsn-js@0.1.0-alpha.5.4",
"resolved": "https://registry.npmjs.org/tlsn-js/-/tlsn-js-0.1.0-alpha.5.4.tgz",
"integrity": "sha512-qbqaDjApXarohn/XMJrxMsNHYTCzy+pw0Fc8gtPPN17PLE+1DwwLTXAAhnxYqYQyo3+Hmy+ODd4C02+KCwRWmg==",
"requires": {
"comlink": "^4.4.1"
}

View File

@@ -18,7 +18,9 @@
"dependencies": {
"@extism/extism": "^1.0.2",
"@fortawesome/fontawesome-free": "^6.4.2",
"@types/axios": "^0.14.0",
"async-mutex": "^0.4.0",
"axios": "^1.7.7",
"buffer": "^6.0.3",
"charwise": "^3.0.1",
"classnames": "^2.3.2",

View File

@@ -12,6 +12,7 @@ import Requests from '../../pages/Requests';
import Options from '../../pages/Options';
import Request from '../../pages/Requests/Request';
import Home from '../../pages/Home';
import Chat from '../../pages/Chat';
import logo from '../../assets/img/icon-128.png';
import RequestBuilder from '../../pages/RequestBuilder';
import Notarize from '../../pages/Notarize';
@@ -104,6 +105,7 @@ const Popup = () => {
<Route path="/custom/*" element={<RequestBuilder />} />
<Route path="/options" element={<Options />} />
<Route path="/home" element={<Home />} />
<Route path="/chat" element={<Chat />} />
<Route path="/plugininfo" element={<PluginUploadInfo />} />
<Route path="/connection-approval" element={<ConnectionApproval />} />
<Route path="/get-history-approval" element={<GetHistoryApproval />} />

63
src/pages/Chat/chat.css Normal file
View File

@@ -0,0 +1,63 @@
.chat-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.chat-window {
height: 400px;
border: 1px solid #ccc;
overflow-y: auto;
padding: 10px;
margin-bottom: 20px;
}
.message {
margin-bottom: 10px;
padding: 8px 12px;
border-radius: 20px;
max-width: 70%;
}
.user {
background-color: #007bff;
color: white;
align-self: flex-end;
margin-left: auto;
}
.bot {
background-color: #f1f0f0;
color: black;
align-self: flex-start;
}
.chat-input {
display: flex;
}
.chat-input input {
flex-grow: 1;
padding: 10px;
font-size: 16px;
}
.chat-input button {
padding: 10px 20px;
font-size: 16px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
}
.chat-buttons {
display: flex;
gap: 10px;
}
.clear-button {
background-color: #f44336;
color: white;
border: none;
}

303
src/pages/Chat/index.tsx Normal file
View File

@@ -0,0 +1,303 @@
import React, { useState, useEffect, useRef, useCallback } from 'react';
import './Chat.css';
import { useRequests } from '../../reducers/requests';
import { extractBodyFromResponse } from '../../utils/misc';
interface Message {
id: number;
text: string;
sender: 'user' | 'bot';
}
interface CapturedData {
request: string;
headers: Record<string, string>;
response: string;
}
interface RequestData {
method: string;
url: string;
headers: Record<string, string>;
body?: string;
}
interface TabInfo {
url: string;
title: string;
favicon: string;
}
const Chat: React.FC = () => {
const [messages, setMessages] = useState<Message[]>(() => {
const savedMessages = localStorage.getItem('chatMessages');
return savedMessages ? JSON.parse(savedMessages) : [];
});
const [inputMessage, setInputMessage] = useState('');
const [allRequests, setAllRequests] = useState<RequestData[]>([]);
const [isConnected, setIsConnected] = useState(false);
const socketRef = useRef<WebSocket | null>(null);
const [chatId, setChatId] = useState<string | null>(null);
const requests = useRequests();
const [capturedData, setCapturedData] = useState<CapturedData[]>([]);
const [hasSetInitialTabInfo, setHasSetInitialTabInfo] = useState(false);
useEffect(() => {
localStorage.setItem('chatMessages', JSON.stringify(messages));
}, [messages]);
const getCurrentTabInfo = async (): Promise<TabInfo> => {
return new Promise((resolve) => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const currentTab = tabs[0];
resolve({
url: currentTab.url || '',
title: currentTab.title || '',
favicon: currentTab.favIconUrl || ''
});
});
});
};
const initializeChat = async () => {
const storedChatId = localStorage.getItem('chatId');
if (storedChatId) {
setChatId(storedChatId);
await connectWebSocket(storedChatId);
} else {
await fetchNewChatId();
}
};
useEffect(() => {
initializeChat();
return () => {
if (socketRef.current) {
socketRef.current.close();
}
};
}, []);
// Effect to set initial tab info when connection is established
useEffect(() => {
const setInitialTabInfo = async () => {
if (isConnected && messages.length === 0 && !hasSetInitialTabInfo) {
const tabInfo = await getCurrentTabInfo();
setInputMessage(`Current Page: ${tabInfo.title}\n website URL: ${tabInfo.url}`);
setHasSetInitialTabInfo(true);
// Send initial info to background script
chrome.runtime.sendMessage({
type: 'TAB_INFO',
data: tabInfo
});
}
};
setInitialTabInfo();
}, [isConnected, messages.length, hasSetInitialTabInfo]);
const fetchNewChatId = async () => {
try {
const response = await fetch('http://localhost:8000/get_chat_id');
const data = await response.json();
const newChatId = data.chat_id;
localStorage.setItem('chatId', newChatId);
setChatId(newChatId);
await connectWebSocket(newChatId);
} catch (error) {
console.error('Failed to fetch chat ID:', error);
}
};
const captureRequestAndResponse = useCallback(async (req: RequestData) => {
try {
const response = await fetch(req.url, {
method: req.method,
headers: req.headers,
body: req.body,
});
const responseText = await extractBodyFromResponse(response);
const headers: Record<string, string> = {};
response.headers.forEach((value, key) => {
headers[key] = value;
});
setCapturedData(prevData => [...prevData, {
request: `${req.method} ${req.url}`,
headers,
response: responseText,
}]);
} catch (error) {
console.error('Error capturing request and response:', error);
}
}, []);
const fetchMultipleRequests = async (requests: RequestData[]) => {
try {
const fetchPromises = requests.map(async (req) => {
if (req.headers === null || req.headers === undefined) {
req.headers = {};
}
if (req.body === null || req.body === undefined) {
req.body = '';
}
const response = await fetch(req.url, {
method: req.method,
headers: req.headers,
});
const responseText = await response.text();
return {
request: `${req.method} ${req.url}`,
headers: req.headers,
response: responseText,
};
});
const responses = await Promise.all(fetchPromises);
setCapturedData(prevData => [...prevData, ...responses]);
const response_message = responses.map(data => data.response).join('\n');
setInputMessage(response_message);
} catch (error) {
console.error('Error fetching multiple requests:', error);
}
};
const connectWebSocket = async (id: string) => {
return new Promise<void>((resolve, reject) => {
socketRef.current = new WebSocket(`ws://localhost:8000/ws/${id}`);
socketRef.current.onopen = () => {
console.log('WebSocket connection established');
setIsConnected(true);
resolve();
};
socketRef.current.onmessage = (event) => {
const botResponse: Message = {
id: Date.now(),
text: event.data,
sender: 'bot',
};
setMessages((prevMessages) => [...prevMessages, botResponse]);
if (botResponse.text.includes("send_request_function")) {
const updatedRequests = requests.map(req => ({
method: req.method,
url: req.url,
headers: req.requestHeaders.reduce((acc: { [key: string]: string }, h: any) => {
if (h.name && h.value) acc[h.name] = h.value;
return acc;
}, {}),
}));
setAllRequests(updatedRequests);
const requestDetails = updatedRequests.map(req =>
`${req.method} ${req.url}\nHeaders: ${JSON.stringify(req.headers, null, 2)}`
).join('\n\n');
setInputMessage(requestDetails);
}
if (botResponse.text.includes("send_response_function")) {
const regex = /"send_response_function"\s*:\s*(\[.*?\])/s;
const match = botResponse.text.match(regex);
if (match) {
const requestArrayString = match[1];
try {
const requestArray: RequestData[] = JSON.parse(requestArrayString);
fetchMultipleRequests(requestArray);
} catch (error) {
console.error("Error parsing JSON:", error);
}
}
}
};
socketRef.current.onclose = () => {
console.log('WebSocket connection closed');
setIsConnected(false);
};
socketRef.current.onerror = (error) => {
console.error('WebSocket error:', error);
reject(error);
};
});
};
useEffect(() => {
if (capturedData.length > 0 && isConnected) {
const capturedDataMessage = JSON.stringify(capturedData);
socketRef.current?.send(capturedDataMessage);
setCapturedData([]);
}
}, [capturedData, isConnected]);
const sendMessage = () => {
if (inputMessage.trim() === '' || !isConnected) return;
const newMessage: Message = {
id: Date.now(),
text: inputMessage,
sender: 'user',
};
setMessages((prevMessages) => [...prevMessages, newMessage]);
setInputMessage('');
if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
socketRef.current.send(inputMessage);
} else {
console.error('WebSocket is not connected');
}
};
const clearChat = () => {
setMessages([]);
setAllRequests([]);
setCapturedData([]);
setHasSetInitialTabInfo(false);
};
return (
<div className="chat-container">
<div className="chat-window">
{messages.map((message) => (
<div
key={message.id}
className={`message ${message.sender === 'user' ? 'user' : 'bot'}`}
>
{message.text}
</div>
))}
</div>
<div className="chat-input">
<input
type="text"
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
placeholder="Type your message..."
className="chat-input-field"
/>
<div className="chat-buttons">
<button
onClick={sendMessage}
className="send-button"
disabled={!isConnected}
>
Send
</button>
<button
onClick={clearChat}
className="clear-button"
style={{ backgroundColor: '#f44336', color: 'white', border: 'none' }}
>
Clear Chat
</button>
</div>
</div>
{!isConnected && <div className="connection-status">Disconnected</div>}
</div>
);
};
export default Chat;

View File

@@ -44,6 +44,9 @@ export default function Home(): ReactElement {
<NavButton fa="fa-solid fa-gear" onClick={() => navigate('/options')}>
Options
</NavButton>
<NavButton fa="fa-solid fa-comment-dots" onClick={() => navigate('/chat')}>
Chat
</NavButton>
</div>
<PluginList className="mx-4" />
</div>