Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update .env.example and README.md with new env variables (✓ Sandbox Passed) #23

Open
wants to merge 8 commits into
base: endless
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
# Your API Key for OpenAI
OPENAI_API_KEY=
# Default temperature for OpenAI API
OPENAI_API_TEMPERATURE=
OPENAI_API_KEY=your_openai_api_key_here
# Provide proxy for OpenAI API.
HTTPS_PROXY=
HTTPS_PROXY=http://your-proxy:port
# Custom base url for OpenAI API. default: https://api.openai.com
OPENAI_API_BASE_URL=
OPENAI_API_BASE_URL=https://api.openai.com
# Inject analytics or other scripts before </head> of the page
HEAD_SCRIPTS=
HEAD_SCRIPTS=<your_script_here>
# Secret string for the project. Use for generating signatures for API calls
SECRET_KEY=
SECRET_KEY=your_secret_key_here
# Set password for site. If not set, site will be public
SITE_PASSWORD=
SITE_PASSWORD=your_site_password_here
# ID of the model to use. https://platform.openai.com/docs/api-reference/chat/create#chat/create-model
OPENAI_API_MODEL=
OPENAI_API_MODEL=text-davinci-003
# Default temperature for OpenAI API
OPENAI_API_TEMPERATURE=0.5
# url of the tutorial markdown file
TUTORIAL_MD_URL=
TUTORIAL_MD_URL=https://your_tutorial_url.md
# url of the advertisement iframe
PUBLIC_IFRAME_URL=
PUBLIC_IFRAME_URL=https://your_ad_iframe_url
# user-agent for backend requests
UNDICI_UA=
UNDICI_UA=Your User Agent
# whether your messages should be right-aligned
PUBLIC_RIGHT_ALIGN_MY_MSG=
PUBLIC_RIGHT_ALIGN_MY_MSG=false
15 changes: 15 additions & 0 deletions .github/ISSUE_TEMPLATE/sweep-template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Sweep Issue
title: 'Sweep: '
description: For small bugs, features, refactors, and tests to be handled by Sweep, an AI-powered junior developer.
labels: sweep
body:
- type: textarea
id: description
attributes:
label: Details
description: Tell Sweep where and what to edit and provide enough context for a new developer to the codebase
placeholder: |
Unit Tests: Write unit tests for <FILE>. Test each function in the file. Make sure to test edge cases.
Bugs: The bug might be in <FILE>. Here are the logs: ...
Features: the new endpoint should use the ... class from <FILE> because it contains ... logic.
Refactors: We are migrating this function to ... version because ...
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ You can control the website through environment variables.

| Name | Description | Default |
| --- | --- | --- |
| `OPENAI_API_KEY` | Your API Key for OpenAI. | `null` |
| `OPENAI_API_KEY` | Your API Key for OpenAI. | `your_openai_api_key_here` |
| `OPENAI_API_TEMPERATURE` | Default `temperature` parameter for model. | `1.0` |
| `HTTPS_PROXY` | Provide proxy for OpenAI API. | `null` |
| `OPENAI_API_BASE_URL` | Custom base url for OpenAI API. | `https://api.openai.com` |
| `HTTPS_PROXY` | Provide proxy for OpenAI API. | `http://your-proxy:port` |
| `OPENAI_API_BASE_URL` | Custom base url for OpenAI API. Default: `https://api.openai.com`. | `https://api.openai.com` |
| `HEAD_SCRIPTS` | Inject analytics or other scripts before `</head>` of the page | `null` |
| `PUBLIC_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` |
| `OPENAI_API_MODEL` | ID of the model to use. [Model endpoint compatibility](https://platform.openai.com/docs/models/model-endpoint-compatibility) | `gpt-3.5-turbo-1106` |
| `TUTORIAL_MD_URL` | url of the tutorial markdown file | `null` |
| `PUBLIC_IFRAME_URL` | url of the advertisement iframe | `null` |
| `UNDICI_UA` | user-agent for backend requests | `(forward)` |
| `PUBLIC_SECRET_KEY` | Secret string for the project. Used for generating signatures for API calls. | `your_secret_key_here` |
| `SITE_PASSWORD` | Set a password for the site. If not set, the site will be public. | `your_site_password_here` |
| `OPENAI_API_MODEL` | ID of the model to use. See OpenAI's model endpoint compatibility documentation. | `text-davinci-003` |
| `TUTORIAL_MD_URL` | URL of the tutorial markdown file. | `https://your_tutorial_url.md` |
| `PUBLIC_IFRAME_URL` | URL of the advertisement iframe. | `https://your_ad_iframe_url` |
| `UNDICI_UA` | User-agent for backend requests. | `Your User Agent` |
| `PUBLIC_RIGHT_ALIGN_MY_MSG` | whether user messages should be right-aligned | `null` |
| `PUBLIC_CL100K_BASE_JSON_URL` | CDN url for `cl100k_base.json`, such as [file at jsdelivr.net](https://cdn.jsdelivr.net/npm/tiktoken@1.0.10/encoders/cl100k_base.json) | `null` |
| `PUBLIC_TIKTOKEN_BG_WASM_URL` | CDN url for `tiktoken_bg.wasm`, such as [file at esm.sh](https://esm.sh/tiktoken/lite/tiktoken_bg.wasm) | `null` |
Expand Down
22 changes: 18 additions & 4 deletions src/components/Generator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { useThrottleFn } from 'solidjs-use'
import { generateSignature } from '@/utils/auth'
import { fetchModeration, fetchTitle, fetchTranslation } from '@/utils/misc'
import { audioChunks, getAudioBlob, startRecording, stopRecording } from '@/utils/record'
import { countTokens } from '@/utils/tiktoken'
import { countTokens, tokenCountCache } from '@/utils/tiktoken'
import { MessagesEvent } from '@/utils/events'

import IconClear from './icons/Clear'
import MessageItem from './MessageItem'
import SystemRoleSettings from './SystemRoleSettings'
Expand Down Expand Up @@ -46,6 +47,10 @@ export default () => {
return systemRole
}

const syncMessageList = () => {
localStorage.setItem('messageList', JSON.stringify(messageList()))
}

const setStick = (stick: boolean) => {
_setStick(stick) ? localStorage.setItem('stickToBottom', 'stick') : localStorage.removeItem('stickToBottom')
return stick
Expand All @@ -66,8 +71,11 @@ export default () => {
setMounted(true)

try {
if (localStorage.getItem('messageList'))
if (localStorage.getItem('messageList')) {
setMessageList(JSON.parse(localStorage.getItem('messageList') ?? '[]'))
if (localStorage.getItem('title')) setPageTitle(localStorage.getItem('title')!)
else updatePageTitle(messageList()[0].content)
}

if (localStorage.getItem('stickToBottom') === 'stick')
setStick(true)
Expand Down Expand Up @@ -128,6 +136,7 @@ export default () => {
titleRef && (titleRef.innerHTML = title)
const subTitleRef: HTMLSpanElement | null = document.querySelector('span.gpt-subtitle')
subTitleRef?.classList.toggle('hidden', title !== 'Endless Chat')
title !== 'Free Chat' ? localStorage.setItem('title', title) : localStorage.removeItem('title')
}

const moderationCache: Record<string, string[]> = {}
Expand Down Expand Up @@ -198,6 +207,7 @@ export default () => {

smoothToBottom()
requestWithLatestMessage()
syncMessageList()
}

const toBottom = (behavior: 'smooth' | 'instant') => {
Expand Down Expand Up @@ -343,21 +353,24 @@ export default () => {
})
setStreaming(false)
setController(null)
localStorage.setItem('messageList', JSON.stringify(messageList()))
syncMessageList()
}
}

const clear = () => {
document.dispatchEvent(new MessagesEvent('clearMessages', messageList().length + Number(Boolean(currentSystemRoleSettings()))))
inputRef.value = ''
inputRef.style.height = 'auto'
tokenCountCache.clear()
batch(() => {
setInputValue('')
setMessageList([])
// setCurrentAssistantMessage('')
// setCurrentSystemRoleSettings('')
})
localStorage.setItem('messageList', JSON.stringify([]))

setMessageList([])
syncMessageList()
setCurrentError(null)
setPageTitle()
}
Expand All @@ -376,6 +389,7 @@ export default () => {
setMessageList(messageList().slice(0, -1))

requestWithLatestMessage()
syncMessageList()
}
}

Expand Down
23 changes: 17 additions & 6 deletions src/components/MessageItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ export default ({ role, message, showRetry, onRetry }: Props) => {
}
})

function heuristicPatch(markdown: string) {
const pattern = /(^|\n)```\S*$/
const matches = markdown.match(/```/g)

return (matches && matches.length % 2 === 1 && pattern.test(markdown))
? markdown.replace(pattern, '\n```')
: markdown
}

const htmlString = () => {
const md = MarkdownIt({
linkify: true,
Expand All @@ -58,12 +67,14 @@ export default ({ role, message, showRetry, onRetry }: Props) => {
</div>`
}

if (typeof message === 'function')
return md.render(message())
else if (typeof message === 'string')
return md.render(message)

return ''
switch (typeof message) {
case 'function':
return md.render(heuristicPatch(message()))
case 'string':
return md.render(heuristicPatch(message))
default:
return ''
}
}

return (
Expand Down
4 changes: 4 additions & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ interface ImportMetaEnv {
readonly SECRET_KEY: string
readonly SITE_PASSWORD: string
readonly OPENAI_API_MODEL: string
readonly OPENAI_API_TEMPERATURE: string
readonly TUTORIAL_MD_URL: string
readonly PUBLIC_IFRAME_URL: string
readonly UNDICI_UA: string
}

interface ImportMeta {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/misc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const fetchTitle = (input: string) => (fetch('/api/title-gen', { method: 'POST', body: input }).then(res => res.text()))
export const fetchTitle = (input: string) => (fetch('/api/title-gen', { method: 'POST', body: input, headers: (localStorage.getItem('apiKey')) ? { authorization: `Bearer ${localStorage.getItem('apiKey')}` } : {} }).then(res => res.text()))

export const fetchTranslation = (input: string) => (fetch(`/api/translate?text=${encodeURIComponent(input)}`).then(res => res.text()))

Expand Down
18 changes: 14 additions & 4 deletions src/utils/tiktoken.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
import type { ChatMessage } from '@/types'
import type { Tiktoken } from 'tiktoken'

export const tokenCountCache = new Map<string, number>()

const countTokensSingleMessage = (enc: Tiktoken, message: ChatMessage) => {
return 4 + enc.encode(message.content).length // im_start, im_end, role/name, "\n"
}

export const countTokens = (enc: Tiktoken | null, messages: ChatMessage[]) => {
const countTokensSingleMessageWithCache = (enc: Tiktoken, cacheIt: boolean, message: ChatMessage) => {
if (tokenCountCache.has(message.content)) return tokenCountCache.get(message.content)!

const count = countTokensSingleMessage(enc, message)
if (cacheIt) tokenCountCache.set(message.content, count)
return count
}

export const countTokens = (enc: Tiktoken, messages: ChatMessage[]) => {
if (messages.length === 0) return

if (!enc) return { total: Infinity }

const lastMsg = messages.at(-1)
const context = messages.slice(0, -1)

const countTokens: (message: ChatMessage) => number = countTokensSingleMessage.bind(null, enc)
const countTokens: (cacheIt: boolean, message: ChatMessage) => number = countTokensSingleMessageWithCache.bind(null, enc)

const countLastMsg = countTokens(lastMsg!)
const countContext = context.map(countTokens).reduce((a, b) => a + b, 3) // im_start, "assistant", "\n"
const countLastMsg = countTokens(false, lastMsg!)
const countContext = context.map(countTokens.bind(null, true)).reduce((a, b) => a + b, 3) // im_start, "assistant", "\n"

return { countContext, countLastMsg, total: countContext + countLastMsg }
}
Expand Down
24 changes: 24 additions & 0 deletions sweep.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

rules:
- "All new business logic should have corresponding unit tests."
- "Refactor large functions to be more modular."
- "Add docstrings to all functions and file headers."

branch: 'endless'

gha_enabled: True

description: 'The project is forked from @anse-app/chatgpt-demo, with an index site at https://free-chat.asia. The repository is mainly a TypeScript application using SolidJS and Svelte for the front end and UnoCSS for styling.'

draft: False

blocked_dirs: []

docs: []

sandbox:
install:
- trunk init
check:
- trunk fmt {file_path} || return 0
- trunk check --fix --print-failures {file_path}