Skip to content

Commit

Permalink
Merge pull request #2 from ddiu8081/main
Browse files Browse the repository at this point in the history
master update 2
  • Loading branch information
chunkiuu authored Mar 9, 2023
2 parents ffdb449 + 2aaa832 commit fd5abb7
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 18 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ OPENAI_API_BASE_URL=
HEAD_SCRIPTS=
# Secret string for the project. Use for generating signatures for API calls
SECRET_KEY=
# Set password for site. If not set, site will be public
SITE_PASSWORD=
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

A demo repo based on [OpenAI GPT-3.5 Turbo API](https://platform.openai.com/docs/guides/chat).

> Notice: Our API Key limit has been exhausted. So the demo site is not available now.
## Run Locally

1. Setup & Install dependencies
Expand All @@ -21,12 +23,25 @@ A demo repo based on [OpenAI GPT-3.5 Turbo API](https://platform.openai.com/docs
```shell
npm run dev
```

## Deploy With Vercel

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fddiu8081%2Fchatgpt-demo&env=OPENAI_API_KEY&envDescription=OpenAI%20API%20Key&envLink=https%3A%2F%2Fplatform.openai.com%2Faccount%2Fapi-keys)

## Creative Forked Versions
## Environment Variables

You can control the website through environment variables.

| Name | Description | Default |
| --- | --- | --- |
| `OPENAI_API_KEY` | Your API Key for OpenAI. | `null` |
| `HTTPS_PROXY` | Provide proxy for OpenAI API. e.g. `http://127.0.0.1:7890` | `null` |
| `OPENAI_API_BASE_URL` | Custom base url for OpenAI API. | `https://api.openai.com` |
| `HEAD_SCRIPTS` | Inject analytics or other scripts before `</head>` of the page | `null` |
| `SECRET_KEY` | Secret string for the project. Use for generating signatures for API calls | `null` |
| `SITE_PASSWORD` | Set password for site. If not set, site will be public | `null` |

## Creative Variant Versions

- [ourongxing/chatgpt-vercel](https://github.com/ourongxing/chatgpt-vercel)

Expand Down
2 changes: 2 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import unocss from 'unocss/astro'
import { presetUno } from 'unocss'
import presetAttributify from '@unocss/preset-attributify'
import presetTypography from '@unocss/preset-typography'
import presetIcons from '@unocss/preset-icons'
import solidJs from '@astrojs/solid-js'
import vercelDisableBlocks from './plugins/vercelDisableBlocks'

Expand Down Expand Up @@ -33,6 +34,7 @@ export default defineConfig({
}
}
}),
presetIcons(),
]
}),
solidJs()
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "novel-gpt",
"name": "chatgpt-api-demo",
"type": "module",
"version": "0.0.1",
"scripts": {
Expand Down Expand Up @@ -29,8 +29,10 @@
"undici": "^5.20.0"
},
"devDependencies": {
"@iconify-json/carbon": "^1.1.16",
"@types/markdown-it": "^12.2.3",
"@unocss/preset-attributify": "^0.50.1",
"@unocss/preset-icons": "^0.50.4",
"@unocss/preset-typography": "^0.50.3",
"punycode": "^2.3.0",
"unocss": "^0.50.1"
Expand Down
24 changes: 24 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/components/Generator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default () => {
const requestWithLatestMessage = async () => {
setLoading(true)
setCurrentAssistantMessage('')
const storagePassword = localStorage.getItem('pass')
try {
const controller = new AbortController()
setController(controller)
Expand All @@ -57,6 +58,7 @@ export default () => {
body: JSON.stringify({
messages: requestMessageList,
time: timestamp,
pass: storagePassword,
sign: await generateSignature({
t: timestamp,
m: requestMessageList?.[requestMessageList.length - 1]?.content || '',
Expand Down
2 changes: 1 addition & 1 deletion src/components/MessageItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default ({ role, message, showRetry, onRetry }: Props) => {
const rawCode = fence(...args)

return `<div relative>
<div data-code=${encodeURIComponent(token.content)} class="copy-btn absolute top-12px right-12px z-3 flex justify-center items-center border b-transparent w-8 h-8 p-2 bg-dark-300 op-90 transition-all group cursor-pointer">
<div data-code=${encodeURIComponent(token.content)} class="copy-btn absolute top-12px right-12px z-3 flex justify-center items-center border b-transparent w-8 h-8 p-2 bg-light-300 dark:bg-dark-300 op-90 transition-all group cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"><path fill="currentColor" d="M28 10v18H10V10h18m0-2H10a2 2 0 0 0-2 2v18a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2Z" /><path fill="currentColor" d="M4 18H2V4a2 2 0 0 1 2-2h14v2H4Z" /></svg>
<div class="opacity-0 h-7 bg-black px-2.5 py-1 box-border text-xs c-white inline-flex justify-center items-center rounded absolute z-1 transition duration-600 whitespace-nowrap -top-8" group-hover:opacity-100>
${copied() ? 'Copied' : 'Copy'}
Expand Down
13 changes: 0 additions & 13 deletions src/components/Themetoggle.astro
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,13 @@
</style>

<script>
const initTheme = () => {
const darkSchema = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
const storageTheme = localStorage.getItem('theme')

if (storageTheme) {
document.documentElement.classList.toggle('dark', storageTheme === 'dark')
} else {
document.documentElement.classList.toggle('dark', darkSchema)
}
}

const listenColorSchema = () => {
const colorSchema = window.matchMedia('(prefers-color-scheme: dark)')
colorSchema.addEventListener('change', () => {
document.documentElement.classList.toggle('dark', colorSchema.matches)
})
}

initTheme()

listenColorSchema()

const handleToggleClick = () => {
Expand Down
15 changes: 15 additions & 0 deletions src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,18 @@ const { title } = Astro.props;
background-color: var(--c-bg);
}
</style>

<script>
const initTheme = () => {
const darkSchema = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
const storageTheme = localStorage.getItem('theme')
if (storageTheme) {
document.documentElement.classList.toggle('dark', storageTheme === 'dark')
} else {
document.documentElement.classList.toggle('dark', darkSchema)
}
}

initTheme()

</script>
12 changes: 12 additions & 0 deletions src/pages/api/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { APIRoute } from 'astro'

const realPassword = import.meta.env.SITE_PASSWORD

export const post: APIRoute = async (context) => {
const body = await context.request.json()

const { pass } = body
return new Response(JSON.stringify({
code: (!realPassword || pass === realPassword) ? 0 : -1,
}))
}
6 changes: 5 additions & 1 deletion src/pages/api/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ import { fetch, ProxyAgent } from 'undici'
const apiKey = import.meta.env.OPENAI_API_KEY
const httpsProxy = import.meta.env.HTTPS_PROXY
const baseUrl = (import.meta.env.OPENAI_API_BASE_URL || 'https://api.openai.com').trim().replace(/\/$/,'')
const sitePassword = import.meta.env.SITE_PASSWORD

export const post: APIRoute = async (context) => {
const body = await context.request.json()
const { sign, time, messages } = body
const { sign, time, messages, pass } = body
if (!messages) {
return new Response('No input text')
}
if (sitePassword && sitePassword !== pass) {
return new Response('Invalid password')
}
if (import.meta.env.PROD && !await verifySignature({ t: time, m: messages?.[messages.length - 1]?.content || '', }, sign)) {
return new Response('Invalid signature')
}
Expand Down
20 changes: 20 additions & 0 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,23 @@ import 'highlight.js/styles/atom-one-dark.css'
</main>
</Layout>

<script>
async function checkCurrentAuth() {
const password = localStorage.getItem('pass')
const response = await fetch('/api/auth', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pass: password,
}),
})
const responseJson = await response.json()
if (responseJson.code !== 0) {
window.location.href = '/password'
}
}
checkCurrentAuth()
</script>

72 changes: 72 additions & 0 deletions src/pages/password.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
import Layout from '../layouts/Layout.astro'
---

<Layout title="Password Protection">
<main class="h-screen flex flex-col items-center justify-center">
<div class="op-30">Please input password</div>
<div id="input_container" class="flex mt-4">
<input id="password_input" type="password" class="px-4 py-3 h-12 rounded-sm bg-slate bg-op-15 focus:bg-op-20 focus:ring-0 focus:outline-none" />
<div id="submit" class="flex items-center justify-center h-12 w-12 bg-slate cursor-pointer bg-op-20 hover:bg-op-50">
<div class="i-carbon-arrow-right" />
</div>
</div>
</main>
</Layout>

<script>
const inputContainer = document.getElementById('input_container') as HTMLDivElement
const input = document.getElementById('password_input') as HTMLInputElement
const submitButton = document.getElementById('submit') as HTMLDivElement

input.onkeydown = async (event) => {
if (event.key === 'Enter') {
handleSubmit()
}
}
submitButton.onclick = handleSubmit

async function handleSubmit() {
const password = input.value
const response = await fetch('/api/auth', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pass: password,
}),
})
const responseJson = await response.json()
if (responseJson.code === 0) {
localStorage.setItem('pass', password)
window.location.href = '/'
} else {
inputContainer.classList.add('invalid')
setTimeout(() => {
inputContainer.classList.remove('invalid')
}, 300)
}
}
</script>

<style>
@keyframes shake {
0% {
transform: translateX(0);
}
25% {
transform: translateX(0.5rem);
}
75% {
transform: translateX(-0.5rem);
}
100% {
transform: translateX(0);
}
}

.invalid {
animation: shake 0.2s ease-in-out 0s 2;
}
</style>

1 comment on commit fd5abb7

@vercel
Copy link

@vercel vercel bot commented on fd5abb7 Mar 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.