From 1109e26b1603dc034682173baa79fca843f3593e Mon Sep 17 00:00:00 2001 From: Marchusness Date: Sun, 24 Mar 2024 22:55:37 +1030 Subject: [PATCH 1/3] AI Pay as alternative to OpenAI API key --- .github/workflows/main.yml | 52 ++++++++++++++++++++++++ index.html | 1 + package-lock.json | 81 +++++++++++++++++++++++++++++++++++++ package.json | 3 ++ src/App.jsx | 6 ++- src/components/ChatView.jsx | 11 ++--- src/components/Setting.jsx | 35 +++++++++++++++- src/components/SideBar.jsx | 4 +- src/utils/dalle.js | 21 +++++++++- src/utils/davinci.js | 13 +++++- vite.config.js | 5 ++- 11 files changed, 218 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..c3746c2 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,52 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ['main'] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow one concurrent deployment +concurrency: + group: 'pages' + cancel-in-progress: true + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Node + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + - name: Install dependencies + run: | + npm install + - name: Build + run: npm run build:githubPages + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + # Upload dist repository + path: './dist' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/index.html b/index.html index 150f0d6..c34b129 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,7 @@ + AI chat diff --git a/package-lock.json b/package-lock.json index 3dc96a3..0c740d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "chatgpt", "version": "2.0.0", "dependencies": { + "ai-pay": "^0.3.6", + "ai-pay-react-hooks": "^0.0.2", "langchain": "^0.0.155", "moment": "^2.29.4", "no-profanity": "^1.5.1", @@ -1142,6 +1144,85 @@ "node": ">= 8.0.0" } }, + "node_modules/ai-pay": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/ai-pay/-/ai-pay-0.3.6.tgz", + "integrity": "sha512-95gymlcD3dRal2HwRjtwAXsi9EXF4xk+WCorHbdFfuNYRVRdCVkCaIFJ9P/SCR0YibhsM8Du3oYrgcM6kZxp9g==", + "dependencies": { + "openai": "^4.24.7" + } + }, + "node_modules/ai-pay-react-hooks": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/ai-pay-react-hooks/-/ai-pay-react-hooks-0.0.2.tgz", + "integrity": "sha512-573vR8Sd1Yrn6op3iqqs29t4pr+Nh6VZW85DUKo9k2dWHe5T1IC9f0a9zXBkIba2WD+ahoU4KOJE2JTI1uFvNw==", + "dependencies": { + "ai-pay": "^0.2.4", + "react": "^18.2.0" + } + }, + "node_modules/ai-pay-react-hooks/node_modules/ai-pay": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/ai-pay/-/ai-pay-0.2.7.tgz", + "integrity": "sha512-+OzhfQaVPtNUu5bbyjX34RRHXnuIur+5FBh1nWngWtrOe4iRPKPWptAmolalYMMcmu4XLXfWiGMKdF1oS3hjCA==", + "dependencies": { + "openai": "^4.24.7" + } + }, + "node_modules/ai-pay-react-hooks/node_modules/openai": { + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.29.2.tgz", + "integrity": "sha512-cPkT6zjEcE4qU5OW/SoDDuXEsdOLrXlAORhzmaguj5xZSPlgKvLhi27sFWhLKj07Y6WKNWxcwIbzm512FzTBNQ==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "digest-fetch": "^1.3.0", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + }, + "bin": { + "openai": "bin/cli" + } + }, + "node_modules/ai-pay-react-hooks/node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ai-pay/node_modules/openai": { + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.29.2.tgz", + "integrity": "sha512-cPkT6zjEcE4qU5OW/SoDDuXEsdOLrXlAORhzmaguj5xZSPlgKvLhi27sFWhLKj07Y6WKNWxcwIbzm512FzTBNQ==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "digest-fetch": "^1.3.0", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + }, + "bin": { + "openai": "bin/cli" + } + }, + "node_modules/ai-pay/node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", diff --git a/package.json b/package.json index 0dcc830..3afffde 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,13 @@ "scripts": { "dev": "vite", "build": "vite build", + "build:githubPages": "vite build --base /chatgpt-pro/", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, "dependencies": { + "ai-pay": "^0.3.6", + "ai-pay-react-hooks": "^0.0.2", "langchain": "^0.0.155", "moment": "^2.29.4", "no-profanity": "^1.5.1", diff --git a/src/App.jsx b/src/App.jsx index 8ce3569..f659dbf 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -4,19 +4,21 @@ import ChatView from './components/ChatView'; import { useEffect, useState } from 'react'; import Modal from './components/Modal'; import Setting from './components/Setting'; +import { AiPayClient } from "ai-pay"; const App = () => { const [modalOpen, setModalOpen] = useState(false); useEffect(() => { const apiKey = window.localStorage.getItem('api-key'); - if (!apiKey) { + const aiPaySessionId = AiPayClient.getInstance().getClientSessionId(); + if (!apiKey && !aiPaySessionId) { setModalOpen(true); } }, []); return ( - +
diff --git a/src/components/ChatView.jsx b/src/components/ChatView.jsx index a8e94eb..41cc763 100644 --- a/src/components/ChatView.jsx +++ b/src/components/ChatView.jsx @@ -8,6 +8,7 @@ import { davinci } from '../utils/davinci'; import { dalle } from '../utils/dalle'; import Modal from './Modal'; import Setting from './Setting'; +import { AiPayClient } from 'ai-pay'; const options = ['ChatGPT', 'DALLĀ·E']; const gptModel = ['gpt-3.5-turbo', 'gpt-4']; @@ -78,7 +79,8 @@ const ChatView = () => { e.preventDefault(); const key = window.localStorage.getItem('api-key'); - if (!key) { + const sessionId = AiPayClient.getInstance().getClientSessionId(); + if (!key && !sessionId) { setModalOpen(true); return; } @@ -101,9 +103,8 @@ const ChatView = () => { //const data = response.data.choices[0].message.content; LLMresponse && updateMessage(LLMresponse, true, aiModel); } else { - const response = await dalle(cleanPrompt, key); - const data = response.data.data[0].url; - data && updateMessage(data, true, aiModel); + const responseUrl = await dalle(cleanPrompt, key); + responseUrl && updateMessage(responseUrl, true, aiModel); } } catch (err) { window.alert(`Error: ${err} please try again later`); @@ -198,7 +199,7 @@ const ChatView = () => {
- + diff --git a/src/components/Setting.jsx b/src/components/Setting.jsx index c18e340..44ed528 100644 --- a/src/components/Setting.jsx +++ b/src/components/Setting.jsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import { checkApiKey } from '../utils/checkKeys'; +import { useSessionData } from 'ai-pay-react-hooks'; import PropTypes from 'prop-types'; @@ -9,6 +10,17 @@ const Setting = ({ modalOpen, setModalOpen }) => { const [errorMsg, setErrorMsg] = useState(''); const [input, setInput] = useState(''); + const { + browserExtensionInstalled, + sessionState, + } = useSessionData(); + + useEffect(() => { + if (sessionState === "ACTIVE") { + setModalOpen(false); + } + }, [sessionState, setModalOpen]) + const saveKey = async (e) => { e.preventDefault(); setLoading(true); @@ -44,7 +56,7 @@ const Setting = ({ modalOpen, setModalOpen }) => {
-

Use your own API-key.

+

Use your OpenAI API key.

keys are saved in your own browser

Get OpenAI API key{' '} @@ -59,6 +71,7 @@ const Setting = ({ modalOpen, setModalOpen }) => {

setInput(e.target.value)} type='password' className='w-full max-w-xs input input-bordered' @@ -82,6 +95,26 @@ const Setting = ({ modalOpen, setModalOpen }) => { )}

{errorMsg}

+ +
+
+

OR

+
+
+ +

Use AI Pay

+ {sessionState === "ACTIVE" ? ( +

AI Pay session is active. You can now use ChatGPT-Pro.

+ ) : ( + + {browserExtensionInstalled ? 'Learn how to start a session' : 'Download AI Pay'} + + )}
); }; diff --git a/src/components/SideBar.jsx b/src/components/SideBar.jsx index 27b2e32..372a122 100644 --- a/src/components/SideBar.jsx +++ b/src/components/SideBar.jsx @@ -90,11 +90,11 @@ const SideBar = () => {
  • setModalOpen(true)}> -

    OpenAI Key

    +

    AI Provider

  • - + diff --git a/src/utils/dalle.js b/src/utils/dalle.js index aad6edd..53b18db 100644 --- a/src/utils/dalle.js +++ b/src/utils/dalle.js @@ -1,6 +1,25 @@ import { Configuration, OpenAIApi } from 'openai'; +import { AiPayClient, imageGeneration } from 'ai-pay'; export const dalle = async (prompt, key) => { + const AiPaySessionId = AiPayClient.getInstance().getClientSessionId(); + + if (AiPaySessionId) { + const { + error, + data, + } = await imageGeneration({ + prompt: `${prompt}`, + imageModel: "dall-e-2", + size: '512x512', + }); + + if (!data) { + throw new Error(error); + } + return data.imageUrls[0] + } + const configuration = new Configuration({ apiKey: key, }); @@ -12,5 +31,5 @@ export const dalle = async (prompt, key) => { size: '512x512', }); - return response; + return response.data.data[0].url; }; diff --git a/src/utils/davinci.js b/src/utils/davinci.js index c305075..c4c3047 100644 --- a/src/utils/davinci.js +++ b/src/utils/davinci.js @@ -7,6 +7,7 @@ import { MessagesPlaceholder, } from 'langchain/prompts'; import { BufferMemory } from 'langchain/memory'; +import { AiPayClient } from 'ai-pay'; const memory = new BufferMemory({ returnMessages: true, @@ -21,7 +22,17 @@ export const davinci = async (prompt, key, gptVersion) => { new MessagesPlaceholder('history'), HumanMessagePromptTemplate.fromTemplate('{input}'), ]); - const model = new ChatOpenAI({ + + const aiPaySessionId = AiPayClient.getInstance().getClientSessionId(); + + const model = new ChatOpenAI(aiPaySessionId ? { + openAIApiKey: aiPaySessionId, + model: gptVersion, + temperature: 0.3, + configuration: { + baseURL: 'https://api.joinaipay.com/api/openai-compatible' + } + } : { openAIApiKey: key, model: gptVersion, temperature: 0.3, diff --git a/vite.config.js b/vite.config.js index b3f5337..79514d5 100644 --- a/vite.config.js +++ b/vite.config.js @@ -2,9 +2,10 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; // https://vitejs.dev/config/ -export default defineConfig({ +export default defineConfig(({base}) => ({ plugins: [react()], + base, build: { chunkSizeWarningLimit: 2000, // set the limit to 2000 kBs }, -}); +})); From cfd787b91ae58b696a68eda4456a80f0aa68f443 Mon Sep 17 00:00:00 2001 From: Marchusness Date: Wed, 3 Apr 2024 22:06:58 +1030 Subject: [PATCH 2/3] add support for streaming --- src/components/ChatView.jsx | 17 ++++++++++++++--- src/components/Message.jsx | 2 +- src/utils/davinci.js | 38 ++++++++++++++++++++++++------------- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/components/ChatView.jsx b/src/components/ChatView.jsx index 41cc763..b29a38d 100644 --- a/src/components/ChatView.jsx +++ b/src/components/ChatView.jsx @@ -43,6 +43,7 @@ const ChatView = () => { const [gpt, setGpt] = useState(gptModel[0]); const [messages, addMessage] = useContext(ChatContext); const [modalOpen, setModalOpen] = useState(false); + const [streamedResponse, setStreamedResponse] = useState(undefined); /** * Scrolls the chat area to the bottom. @@ -99,8 +100,11 @@ const ChatView = () => { console.log(selected); try { if (aiModel === options[0]) { - const LLMresponse = await davinci(cleanPrompt, key, gptVersion); - //const data = response.data.choices[0].message.content; + const LLMresponse = await davinci(cleanPrompt, key, gptVersion, (streamedResponse) => { + setStreamedResponse(streamedResponse); + }); + setStreamedResponse(undefined); + LLMresponse && updateMessage(LLMresponse, true, aiModel); } else { const responseUrl = await dalle(cleanPrompt, key); @@ -172,7 +176,14 @@ const ChatView = () => { )} - {thinking && } + {thinking && !streamedResponse && } + + {streamedResponse && } diff --git a/src/components/Message.jsx b/src/components/Message.jsx index b5c2e17..ca0b935 100644 --- a/src/components/Message.jsx +++ b/src/components/Message.jsx @@ -28,7 +28,7 @@ const Message = (props) => {
    - {moment(createdAt).calendar()} + {createdAt && moment(createdAt).calendar()}
    diff --git a/src/utils/davinci.js b/src/utils/davinci.js index c4c3047..fa89a06 100644 --- a/src/utils/davinci.js +++ b/src/utils/davinci.js @@ -14,7 +14,7 @@ const memory = new BufferMemory({ memoryKey: 'history', }); -export const davinci = async (prompt, key, gptVersion) => { +export const davinci = async (prompt, key, gptVersion, streamCallback) => { const chatPrompt = ChatPromptTemplate.fromMessages([ SystemMessagePromptTemplate.fromTemplate( 'The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context and always responds in markdown format. If the AI does not know the answer to a question, it truthfully says it does not know.' @@ -25,18 +25,20 @@ export const davinci = async (prompt, key, gptVersion) => { const aiPaySessionId = AiPayClient.getInstance().getClientSessionId(); - const model = new ChatOpenAI(aiPaySessionId ? { - openAIApiKey: aiPaySessionId, + const model = new ChatOpenAI({ model: gptVersion, temperature: 0.3, - configuration: { - baseURL: 'https://api.joinaipay.com/api/openai-compatible' - } - } : { - openAIApiKey: key, - model: gptVersion, - temperature: 0.3, - }); + streaming: true, + + ...(aiPaySessionId ? { + openAIApiKey: aiPaySessionId, + configuration: { + baseURL: 'https://api.joinaipay.com/api/openai-compatible' + }, + } : { + openAIApiKey: key, + }) +}); const chain = new ConversationChain({ memory: memory, @@ -44,8 +46,18 @@ export const davinci = async (prompt, key, gptVersion) => { llm: model, }); - const response = await chain.call({ input: prompt }); - console.log(response); + let streamedResponse = "" + const response = await chain.call({ + input: prompt , + callbacks: [ + { + handleLLMNewToken(token) { + streamedResponse += token; + streamCallback(streamedResponse); + } + } + ] + }); return response.response; }; From 458af70434ce3cfeb0306e241f02b22870eb82c5 Mon Sep 17 00:00:00 2001 From: Marchusness Date: Wed, 3 Apr 2024 22:12:29 +1030 Subject: [PATCH 3/3] removed automatic deployment github action --- .github/workflows/main.yml | 52 -------------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index c3746c2..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,52 +0,0 @@ -# Simple workflow for deploying static content to GitHub Pages -name: Deploy static content to Pages - -on: - # Runs on pushes targeting the default branch - push: - branches: ['main'] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow one concurrent deployment -concurrency: - group: 'pages' - cancel-in-progress: true - -jobs: - # Single deploy job since we're just deploying - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Node - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: 'npm' - - name: Install dependencies - run: | - npm install - - name: Build - run: npm run build:githubPages - - name: Setup Pages - uses: actions/configure-pages@v3 - - name: Upload artifact - uses: actions/upload-pages-artifact@v2 - with: - # Upload dist repository - path: './dist' - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v2