Skip to content
This repository has been archived by the owner on Jan 17, 2025. It is now read-only.

Commit

Permalink
Support passcode and multiple API keys
Browse files Browse the repository at this point in the history
  • Loading branch information
bclswl0827 committed Feb 1, 2024
1 parent ceb32e4 commit aba73b4
Show file tree
Hide file tree
Showing 21 changed files with 329 additions and 77 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# MD5 形式站点访问密码,留空则为公开站点
# 默认值:<空>
REACT_APP_PASSCODE_MD5=
# 站点标题,会显示在浏览器标签页上
# 默认值:ChatGemini
REACT_APP_TITLE_SITE=
Expand Down
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,14 @@ REACT_APP_GEMINI_API_KEY="您的密钥"

各配置项说明如下:

| 配置项 | 必填 | 可选值 | 默认值 | 说明 |
| :------------------------: | :--- | :-------------- | :----------- | :--------------------------------------- |
| `REACT_APP_GEMINI_API_KEY` || `string` || 填入申请得到的 Gemini API 密钥 |
| `REACT_APP_GEMINI_API_URL` || `string` || 自定义 Gemini API 地址,具体参考下方说明 |
| `REACT_APP_GEMINI_API_SSE` || `true`\|`false` | `true` | 是否逐字输出 Gemini 回应,即是否使能 SSE |
| `REACT_APP_TITLE_SITE` || `string` | `ChatGemini` | 站点标题,将显示在浏览器标签页上 |
| `REACT_APP_TITLE_HEADER` || `string` | `Gemini Pro` | 应用名称,显示在应用菜单栏和头部 |
| 配置项 | 必填 | 可选值 | 默认值 | 说明 | 备注 |
| :------------------------: | :--- | :------------------- | :----------- | :--------------------------------------- | :----------------------------------------- |
| `REACT_APP_GEMINI_API_KEY` || `string`\|`string[]` || 填入 Gemini API 密钥,多个以 `\|` 分隔 | 存在多个密钥时,每次应用加载时随机选用一个 |
| `REACT_APP_GEMINI_API_URL` || `string` || 自定义 Gemini API 地址,具体参考下方说明 ||
| `REACT_APP_GEMINI_API_SSE` || `true`\|`false` | `true` | 是否逐字输出 Gemini 回应,即是否使能 SSE ||
| `REACT_APP_TITLE_SITE` || `string` | `ChatGemini` | 站点标题,将显示在浏览器标签页上 ||
| `REACT_APP_TITLE_HEADER` || `string` | `Gemini Pro` | 应用标题,显示在应用侧边栏和头部 ||
| `REACT_APP_PASSCODE_MD5` || `string`\|`string[]` || MD5 格式通行码,多个以 `\|` 分隔 | 存在多个通行码时,任意一个通过验证即可登入 |

### 直连 Gemini API

Expand Down Expand Up @@ -149,6 +150,20 @@ REACT_APP_GEMINI_API_URL="https://example.org/gemini.php?token=Nt6PRcQ2BZ8FY9y7L

*若反代同网站位于相同基础路径下,也可简写为 `/gemini.php?token=Nt6PRcQ2BZ8FY9y7Lnk35S&path=`,跨域则须填写完整地址。*

### 站点通行码

启用通行码后,用户在每次访问应用时,需要先输入通行码,才能开始使用应用。

若要为您的站点启用通行码,可以在 `.env` 中的 `REACT_APP_PASSCODE_MD5` 字段填入 MD5 格式的通行码,多个以 `|` 分隔,例如:

```bash
REACT_APP_PASSCODE_MD5="E10ADC3949BA59ABBE56E057F20F883E|C33367701511B4F6020EC61DED352059"
```

要生成 MD5 格式的通行码,可以使用相关在线工具,例如 [MD5 Hash Generator](https://passwordsgenerator.net/md5-hash-generator/)

**注意:本应用通行码为无盐值 MD5 格式,有一定概率被破解,因此请勿将您的重要密码作为通行码。**

## 开源许可

本项目基于 MIT 协议开源,具体请参阅 [LICENSE](https://github.com/bclswl0827/ChatGemini/blob/master/LICENSE)
13 changes: 13 additions & 0 deletions package-lock.json

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

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chatgemini",
"version": "0.1.0",
"version": "0.2.0",
"homepage": ".",
"private": true,
"dependencies": {
Expand All @@ -9,6 +9,7 @@
"@google/generative-ai": "^0.1.3",
"@reduxjs/toolkit": "^2.1.0",
"@tailwindcss/typography": "^0.5.10",
"crypto-js": "^4.2.0",
"file-saver": "^2.0.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down Expand Up @@ -52,6 +53,7 @@
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@types/crypto-js": "^4.2.2",
"@types/file-saver": "^2.0.7",
"@types/node": "^16.18.76",
"@types/react": "^18.2.48",
Expand Down
108 changes: 74 additions & 34 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { globalConfig } from "./config/global";
import { Sidebar } from "./components/Sidebar";
import { Container } from "./components/Container";
Expand All @@ -21,13 +21,16 @@ import { getBase64Img } from "./helpers/getBase64Img";
import { sendUserAlert } from "./helpers/sendUserAlert";
import { sendUserConfirm } from "./helpers/sendUserConfirm";
import { PageScroller } from "./components/PageScroller";
import { LoginForm } from "./components/LoginForm";
import siteLogo from "./assets/logo.svg";
import setLocalStorage from "./helpers/setLocalStorage";

const ModelPlaceholder = "正在思考中...";

const App = () => {
const { routes } = routerConfig;
const { sse, title } = globalConfig;
const { sse, title, passcodes } = globalConfig;
const { header, site } = title;
const { routes } = routerConfig;

const navigate = useNavigate();
const dispatch = useDispatch();
Expand All @@ -37,6 +40,7 @@ const App = () => {
const ai = useSelector((state: ReduxStoreProps) => state.ai.ai);
const mainSectionRef = useRef<HTMLDivElement>(null);

const [hasLogined, setHasLogined] = useState(false);
const [uploadInlineData, setUploadInlineData] =
useState<GenerativeContentBlob>({ data: "", mimeType: "" });
const [sidebarExpand, setSidebarExpand] = useState(window.innerWidth > 768);
Expand All @@ -50,7 +54,7 @@ const App = () => {
Intl.DateTimeFormat().resolvedOptions().timeZone
}\n- 对话时间 ${sessionTime}\n- 导出时间 ${exportTime}\n\n---\n\n`;
session.forEach(({ role, parts, timestamp, attachment }) => {
if (attachment?.data.length) {
if (!!attachment?.data.length) {
const { data, mimeType } = attachment;
const base64ImgData = `data:${mimeType};base64,${data}`;
parts += `\n\n<img alt="图片附件" src="${base64ImgData}" />`;
Expand Down Expand Up @@ -84,6 +88,14 @@ const App = () => {
});
};

const handleLogout = () => {
sendUserConfirm("登出后对话记录仍会保留,确定要登出吗?", () => {
sendUserAlert("已退出登入");
setHasLogined(false);
setLocalStorage("passcode", "", false);
});
};

const handleUpload = async (file: File | null) => {
if (file) {
const base64EncodedData = await getBase64Img(file);
Expand Down Expand Up @@ -183,38 +195,66 @@ const App = () => {
setUploadInlineData({ data: "", mimeType: "" });
};

useEffect(() => {
if (!hasLogined && passcodes.length) {
document.title = `登入 - ${site}`;
}
console.log(passcodes);
}, [hasLogined, passcodes, site]);

return (
<Container toaster={true}>
<Sidebar
title={header}
sessions={sessions}
expand={sidebarExpand}
newChatUrl={routes.index.prefix}
onExportSession={handleExportSession}
onDeleteSession={handleDeleteSession}
/>
<Container
ref={mainSectionRef}
className={`min-w-full overflow-y-auto overflow-x-hidden flex flex-col h-screen justify-between ${
!sidebarExpand ? "col-span-2" : ""
}`}
>
<Header
newChatUrl={routes.index.prefix}
title={!sidebarExpand ? header : ""}
onPurgeSessions={handlePurgeSessions}
onToggleSidebar={() => setSidebarExpand((state) => !state)}
/>
<RouterView routes={routes} suspense={<Skeleton />} />
<InputArea
minHeight={45}
maxHeight={120}
disabled={ai.busy}
onSubmit={handleSubmit}
onUpload={handleUpload}
<Container
className={
!hasLogined && passcodes.length
? "flex flex-col items-center justify-center min-h-screen p-8"
: ""
}
toaster={true}
>
{hasLogined || !passcodes.length ? (
<>
<Sidebar
title={header}
sessions={sessions}
expand={sidebarExpand}
newChatUrl={routes.index.prefix}
onExportSession={handleExportSession}
onDeleteSession={handleDeleteSession}
/>
<Container
ref={mainSectionRef}
className={`min-w-full overflow-y-auto overflow-x-hidden flex flex-col h-screen justify-between ${
!sidebarExpand ? "col-span-2" : ""
}`}
>
<Header
newChatUrl={routes.index.prefix}
title={!sidebarExpand ? header : ""}
onPurgeSessions={handlePurgeSessions}
onToggleSidebar={() =>
setSidebarExpand((state) => !state)
}
onLogout={handleLogout}
/>
<RouterView routes={routes} suspense={<Skeleton />} />
<InputArea
minHeight={45}
maxHeight={120}
disabled={ai.busy}
onSubmit={handleSubmit}
onUpload={handleUpload}
/>
<PageScroller monitorRef={mainSectionRef} />{" "}
</Container>
</>
) : (
<LoginForm
title={header}
logo={siteLogo}
passcodes={passcodes}
onPasscodeCorrect={() => setHasLogined(true)}
/>
<PageScroller monitorRef={mainSectionRef} />
</Container>
)}
</Container>
);
};
Expand Down
1 change: 1 addition & 0 deletions src/assets/icons/right-from-bracket-solid.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
2 changes: 1 addition & 1 deletion src/components/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const Container = forwardRef(
<div
ref={ref}
className={
(className || "").length > 0
!!(className || "").length
? className
: `h-screen w-full grid md:grid-cols-[15rem_1fr] grid-cols-[12rem_1fr]`
}
Expand Down
13 changes: 11 additions & 2 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import menuIcon from "../assets/icons/bars-staggered-solid.svg";
import newChatIcon from "../assets/icons/square-plus-regular.svg";
import purgeIcon from "../assets/icons/broom-ball-solid.svg";
import logoutIcon from "../assets/icons/right-from-bracket-solid.svg";
import { Link } from "react-router-dom";

interface HeaderProps {
readonly title?: string;
readonly newChatUrl: string;
readonly onLogout: () => void;
readonly onToggleSidebar: () => void;
readonly onPurgeSessions?: () => void;
readonly onPurgeSessions: () => void;
}

export const Header = (props: HeaderProps) => {
const { title, newChatUrl, onToggleSidebar, onPurgeSessions } = props;
const { title, newChatUrl, onLogout, onToggleSidebar, onPurgeSessions } =
props;
return (
<header className="z-10 sticky top-0 flex px-2 py-3 items-center justify-between border-b bg-white">
<button
Expand All @@ -34,6 +37,12 @@ export const Header = (props: HeaderProps) => {
>
<img src={purgeIcon} className="size-4" alt="" />
</button>
<button
className="hover:bg-gray-200 rounded-lg p-2"
onClick={onLogout}
>
<img src={logoutIcon} className="size-4" alt="" />
</button>
</div>
</header>
);
Expand Down
8 changes: 4 additions & 4 deletions src/components/InputArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const InputArea = (props: InputAreaProps) => {
<button
className="bg-gray-100 hover:bg-gray-200 rounded-lg p-3"
onClick={({ currentTarget }) => {
if (attachmentName.length > 0) {
if (!!attachmentName.length) {
setAttachmentName("");
onUpload && onUpload(null);
sendUserAlert("已取消上传文件");
Expand All @@ -98,14 +98,14 @@ export const InputArea = (props: InputAreaProps) => {
>
<img
className={
attachmentName.length > 0 ? "size-5" : "hidden"
!!attachmentName.length ? "size-5" : "hidden"
}
src={ejectionIcon}
alt=""
/>
<img
className={
attachmentName.length > 0 ? "hidden" : "size-5"
!!attachmentName.length ? "hidden" : "size-5"
}
src={attachmentIcon}
alt=""
Expand Down Expand Up @@ -180,7 +180,7 @@ export const InputArea = (props: InputAreaProps) => {
/>
</button>
</div>
{attachmentName.length > 0 && (
{!!attachmentName.length && (
<div className="text-center text-gray-500 text-xs truncate">
<img
className="inline-block size-3 mr-0.5"
Expand Down
Loading

0 comments on commit aba73b4

Please sign in to comment.