diff --git a/.env.local.demo b/.env.local.demo
index cfbb51e..2100f1c 100644
--- a/.env.local.demo
+++ b/.env.local.demo
@@ -32,4 +32,8 @@ GITHUB_SECRET=
# NEXT-AUTH Google Configure. https://next-auth.js.org/providers/google
GOOGLE_CLIENT_ID=
-GOOGLE_CLIENT_SECRET=
\ No newline at end of file
+GOOGLE_CLIENT_SECRET=
+
+# Redis Configure
+UPSTASH_REDIS_REST_URL=
+UPSTASH_REDIS_REST_TOKEN=
\ No newline at end of file
diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md
index eafa6d7..73ae8ab 100644
--- a/CHANGE_LOG.md
+++ b/CHANGE_LOG.md
@@ -1,5 +1,19 @@
# L-GPT Change Log
+## v0.5.0
+
+> 2023-05-30
+
+### Add
+
+- Added conversation sharing function
+
+### Changed
+
+- Move all Prisma variable definitions to .env.local
+- Standardize the global logo style
+- Multiple detailed optimizations
+
## v0.4.3
> 2023-05-28
diff --git a/CHANGE_LOG.zh_CN.md b/CHANGE_LOG.zh_CN.md
index 4fb9a1f..10f3412 100644
--- a/CHANGE_LOG.zh_CN.md
+++ b/CHANGE_LOG.zh_CN.md
@@ -1,5 +1,19 @@
# L-GPT 更新日志
+## v0.5.0
+
+> 2023-05-30
+
+### Add
+
+- 新增会话分享功能
+
+### Changed
+
+- 将 prisma 变量定义全部移动到.env.local
+- 统一全局 Logo 样式
+- 多处细节优化
+
## v0.4.3
> 2023-05-28
diff --git a/README.md b/README.md
index 024aa49..7f4093d 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,7 @@ Welcome to:[Telegram](https://t.me/+7fLJJoGV_bJhYTk1)
- [x] Introduce prompt words and prompt word templates
- [x] Chat record import and export
- [x] Account System
+- [x] Support conversation sharing
- [ ] Support for customizing the prompt repository
- [ ] Support GPT-4 and Claude
- [ ] Compress context to save chat tokens
diff --git a/README_CN.md b/README_CN.md
index 996c6eb..3e63ceb 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -27,6 +27,7 @@ L-GPT 是一款开源项目,通过提供不同的 AI 模型来帮助你提高
- [x] 引入提示词以及提示词模板
- [x] 聊天记录导入导出
- [x] 账号系统
+- [x] 支持会话分享
- [ ] 支持自定义 prompt 仓库
- [ ] 支持 GPT-4 和 Claude
- [ ] 压缩上下文,节省聊天 token
diff --git a/package.json b/package.json
index 1bc0fb1..9ac321b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "l-gpt",
- "version": "0.4.3",
+ "version": "0.5.0",
"private": true,
"scripts": {
"dev": "next dev",
@@ -8,9 +8,9 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
- "db-generate": "prisma generate",
- "db-pull": "prisma db pull",
- "db-push": "prisma db push"
+ "db-gn": "prisma generate",
+ "db-pull": "dotenv -e .env.local -- npx prisma db pull",
+ "db-push": "dotenv -e .env.local -- npx prisma db push"
},
"dependencies": {
"@emotion/css": "11.11.0",
@@ -30,6 +30,7 @@
"@types/node": "20.2.5",
"@types/react": "18.2.7",
"@types/react-dom": "18.2.4",
+ "@upstash/redis": "^1.20.6",
"@vercel/analytics": "1.0.1",
"ahooks": "3.7.7",
"autoprefixer": "10.4.14",
@@ -46,7 +47,7 @@
"next-auth": "4.22.1",
"next-intl": "2.14.6",
"next-themes": "0.2.1",
- "nodemailer": "6.9.2",
+ "nodemailer": "6.9.3",
"postcss": "8.4.24",
"react": "18.2.0",
"react-dom": "18.2.0",
@@ -69,6 +70,7 @@
"@types/math-random": "1.0.0",
"@types/react-syntax-highlighter": "15.5.7",
"@types/uuid": "9.0.1",
+ "dotenv-cli": "7.2.1",
"prisma": "4.14.1"
}
}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8332c70..2d1ef5f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,4 +1,8 @@
-lockfileVersion: '6.0'
+lockfileVersion: '6.1'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
dependencies:
'@emotion/css':
@@ -52,6 +56,9 @@ dependencies:
'@types/react-dom':
specifier: 18.2.4
version: 18.2.4
+ '@upstash/redis':
+ specifier: ^1.20.6
+ version: 1.20.6
'@vercel/analytics':
specifier: 1.0.1
version: 1.0.1
@@ -93,7 +100,7 @@ dependencies:
version: 13.4.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0)
next-auth:
specifier: 4.22.1
- version: 4.22.1(next@13.4.3)(nodemailer@6.9.2)(react-dom@18.2.0)(react@18.2.0)
+ version: 4.22.1(next@13.4.3)(nodemailer@6.9.3)(react-dom@18.2.0)(react@18.2.0)
next-intl:
specifier: 2.14.6
version: 2.14.6(next@13.4.3)(react@18.2.0)
@@ -101,8 +108,8 @@ dependencies:
specifier: 0.2.1
version: 0.2.1(next@13.4.3)(react-dom@18.2.0)(react@18.2.0)
nodemailer:
- specifier: 6.9.2
- version: 6.9.2
+ specifier: 6.9.3
+ version: 6.9.3
postcss:
specifier: 8.4.24
version: 8.4.24
@@ -165,6 +172,9 @@ devDependencies:
'@types/uuid':
specifier: 9.0.1
version: 9.0.1
+ dotenv-cli:
+ specifier: 7.2.1
+ version: 7.2.1
prisma:
specifier: 4.14.1
version: 4.14.1
@@ -1704,7 +1714,7 @@ packages:
next-auth: ^4
dependencies:
'@prisma/client': 4.14.1(prisma@4.14.1)
- next-auth: 4.22.1(next@13.4.3)(nodemailer@6.9.2)(react-dom@18.2.0)(react@18.2.0)
+ next-auth: 4.22.1(next@13.4.3)(nodemailer@6.9.3)(react-dom@18.2.0)(react@18.2.0)
dev: false
/@next/env@13.4.3:
@@ -3165,6 +3175,14 @@ packages:
eslint-visitor-keys: 3.4.1
dev: false
+ /@upstash/redis@1.20.6:
+ resolution: {integrity: sha512-q1izaYEUsq/WiXNOjf4oOjFZe8fIeBSZN8d5cEyOD4nem+zxc4jccieorQQrNlEahKPE1ZYLzVEkMODRUfch2g==}
+ dependencies:
+ isomorphic-fetch: 3.0.0
+ transitivePeerDependencies:
+ - encoding
+ dev: false
+
/@vercel/analytics@1.0.1:
resolution: {integrity: sha512-Ux0c9qUfkcPqng3vrR0GTrlQdqNJ2JREn/2ydrVuKwM3RtMfF2mWX31Ijqo1opSjNAq6rK76PwtANw6kl6TAow==}
dev: false
@@ -3672,7 +3690,6 @@ packages:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
- dev: false
/css-select@5.1.0:
resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
@@ -3899,6 +3916,26 @@ packages:
tslib: 2.5.0
dev: false
+ /dotenv-cli@7.2.1:
+ resolution: {integrity: sha512-ODHbGTskqRtXAzZapDPvgNuDVQApu4oKX8lZW7Y0+9hKA6le1ZJlyRS687oU9FXjOVEDU/VFV6zI125HzhM1UQ==}
+ hasBin: true
+ dependencies:
+ cross-spawn: 7.0.3
+ dotenv: 16.0.3
+ dotenv-expand: 10.0.0
+ minimist: 1.2.8
+ dev: true
+
+ /dotenv-expand@10.0.0:
+ resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==}
+ engines: {node: '>=12'}
+ dev: true
+
+ /dotenv@16.0.3:
+ resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
+ engines: {node: '>=12'}
+ dev: true
+
/electron-to-chromium@1.4.387:
resolution: {integrity: sha512-tutLf+alr1/0YqJwKPdstVvDLmxmLb5xNyDLNS0RZmenHcEYk9qKfpKDCVZEKJ00JVbnayJm1MZAbYhYDFpcOw==}
dev: false
@@ -5078,6 +5115,14 @@ packages:
/isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ /isomorphic-fetch@3.0.0:
+ resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==}
+ dependencies:
+ node-fetch: 2.6.11
+ whatwg-fetch: 3.6.2
+ transitivePeerDependencies:
+ - encoding
dev: false
/jiti@1.18.2:
@@ -5745,7 +5790,6 @@ packages:
/minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
- dev: false
/mkdirp@0.5.6:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
@@ -5788,7 +5832,7 @@ packages:
engines: {node: '>= 0.6'}
dev: false
- /next-auth@4.22.1(next@13.4.3)(nodemailer@6.9.2)(react-dom@18.2.0)(react@18.2.0):
+ /next-auth@4.22.1(next@13.4.3)(nodemailer@6.9.3)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-NTR3f6W7/AWXKw8GSsgSyQcDW6jkslZLH8AiZa5PQ09w1kR8uHtR9rez/E9gAq/o17+p0JYHE8QjF3RoniiObA==}
peerDependencies:
next: ^12.2.5 || ^13
@@ -5804,7 +5848,7 @@ packages:
cookie: 0.5.0
jose: 4.14.4
next: 13.4.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0)
- nodemailer: 6.9.2
+ nodemailer: 6.9.3
oauth: 0.9.15
openid-client: 5.4.2
preact: 10.13.2
@@ -5908,8 +5952,8 @@ packages:
resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==}
dev: false
- /nodemailer@6.9.2:
- resolution: {integrity: sha512-4+TYaa/e1nIxQfyw/WzNPYTEZ5OvHIDEnmjs4LPmIfccPQN+2CYKmGHjWixn/chzD3bmUTu5FMfpltizMxqzdg==}
+ /nodemailer@6.9.3:
+ resolution: {integrity: sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg==}
engines: {node: '>=6.0.0'}
dev: false
@@ -6135,7 +6179,6 @@ packages:
/path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
- dev: false
/path-key@4.0.0:
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
@@ -6685,12 +6728,10 @@ packages:
engines: {node: '>=8'}
dependencies:
shebang-regex: 3.0.0
- dev: false
/shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
- dev: false
/side-channel@1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
@@ -7277,6 +7318,10 @@ packages:
engines: {node: '>=10.13.0'}
dev: false
+ /whatwg-fetch@3.6.2:
+ resolution: {integrity: sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==}
+ dev: false
+
/whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
dependencies:
@@ -7321,7 +7366,6 @@ packages:
hasBin: true
dependencies:
isexe: 2.0.0
- dev: false
/word-wrap@1.2.3:
resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index aad3195..8e10170 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -37,6 +37,15 @@ model Session {
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
+model VerificationToken {
+ id String @id @default(cuid())
+ identifier String
+ token String @unique
+ expires DateTime
+
+ @@unique([identifier, token])
+}
+
model User {
id String @id @default(cuid())
name String?
@@ -48,13 +57,19 @@ model User {
createdAt DateTime @default(now())
updatedAt DateTime? @updatedAt
recentlyUse DateTime?
+ share Share[]
}
-model VerificationToken {
- id String @id @default(cuid())
- identifier String
- token String @unique
- expires DateTime
-
- @@unique([identifier, token])
+model Share {
+ id String @id @default(cuid())
+ channel_model Json
+ channel_name String?
+ channel_prompt String?
+ chat_content Json[]
+ anonymous Int @default(0)
+ createdAt DateTime @default(now())
+ updatedAt DateTime? @updatedAt
+ userId String?
+ userName String?
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
}
diff --git a/src/app/[locale]/(create)/create/[id]/page.tsx b/src/app/[locale]/(create)/create/[id]/page.tsx
new file mode 100644
index 0000000..a34863e
--- /dev/null
+++ b/src/app/[locale]/(create)/create/[id]/page.tsx
@@ -0,0 +1,30 @@
+import * as React from "react";
+import { notFound } from "next/navigation";
+import { prisma } from "@/lib/prisma";
+import type { Share } from "@prisma/client";
+import CreateChat from "@/components/share/createChat";
+
+type Props = {
+ params: { id: string };
+};
+
+async function getShareData(id: Share["id"]) {
+ const shareRes = await prisma.share.findUnique({
+ where: { id },
+ });
+
+ if (!shareRes) return null;
+
+ return shareRes;
+}
+
+export default async function Create({ params }: Props) {
+ const id = params.id;
+ if (!id) return notFound();
+
+ const content = await getShareData(id);
+
+ if (!content) return notFound();
+
+ return ;
+}
diff --git a/src/app/[locale]/(create)/layout.tsx b/src/app/[locale]/(create)/layout.tsx
new file mode 100644
index 0000000..ea1293f
--- /dev/null
+++ b/src/app/[locale]/(create)/layout.tsx
@@ -0,0 +1,25 @@
+import { cn } from "@/lib";
+import Logo from "@/components/logo";
+
+export default async function CreateLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
diff --git a/src/app/[locale]/(home)/page.tsx b/src/app/[locale]/(home)/page.tsx
index ca60940..4ceafe1 100644
--- a/src/app/[locale]/(home)/page.tsx
+++ b/src/app/[locale]/(home)/page.tsx
@@ -18,8 +18,7 @@ export default function Home() {
diff --git a/src/app/[locale]/(share)/layout.tsx b/src/app/[locale]/(share)/layout.tsx
new file mode 100644
index 0000000..d5d9890
--- /dev/null
+++ b/src/app/[locale]/(share)/layout.tsx
@@ -0,0 +1,27 @@
+import { cn } from "@/lib";
+import Logo from "@/components/logo";
+import BackHome from "@/components/share/backHome";
+
+export default async function AuthenticationLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
diff --git a/src/app/[locale]/(share)/share/[id]/opengraph-image.tsx b/src/app/[locale]/(share)/share/[id]/opengraph-image.tsx
new file mode 100644
index 0000000..fa704f8
--- /dev/null
+++ b/src/app/[locale]/(share)/share/[id]/opengraph-image.tsx
@@ -0,0 +1,159 @@
+import { ImageResponse } from "next/server";
+import { AiOutlineUser } from "react-icons/ai";
+
+const baseURL =
+ process.env.NODE_ENV === "development"
+ ? "http://localhost:3000"
+ : "https://gpt.ltopx.com";
+
+// Route segment config
+export const runtime = "edge";
+
+// Image metadata
+export const alt = "LGPT Share";
+export const size = {
+ width: 1200,
+ height: 630,
+};
+
+export const contentType = "image/png";
+
+type Props = {
+ params: { id: string };
+};
+
+async function getShareData(id: string) {
+ try {
+ const res = await fetch(`${baseURL}/api/share?id=${id}`).then((res) =>
+ res.json()
+ );
+ if (res.error) return null;
+ return res.data;
+ } catch (error) {
+ return null;
+ }
+}
+
+// Image generation
+export default async function Image({ params }: Props) {
+ const content = await getShareData(params.id);
+ const title = content?.channel_name || "Unnamed";
+ const chat_list: any[] = content?.chat_content?.slice(0, 2) || [];
+
+ return new ImageResponse(
+ (
+
+
+ {title}
+
+
+ {chat_list.map((item) => (
+
+
1
+
+
+ {item.role === "user" ? (
+
+ ) : (
+ // eslint-disable-next-line @next/next/no-img-element
+
+ )}
+
+
+
+
+ ))}
+
+
+ Powered by
+
+ LGPT
+
+
+ Share
+
+
+
+ )
+ );
+}
diff --git a/src/app/[locale]/(share)/share/[id]/page.tsx b/src/app/[locale]/(share)/share/[id]/page.tsx
new file mode 100644
index 0000000..c7bc967
--- /dev/null
+++ b/src/app/[locale]/(share)/share/[id]/page.tsx
@@ -0,0 +1,165 @@
+import * as React from "react";
+import Image from "next/image";
+import { Metadata } from "next";
+import { cn } from "@/lib";
+import { prisma } from "@/lib/prisma";
+import { redis } from "@/lib/redis";
+import type { Share } from "@prisma/client";
+import { AiOutlineUser } from "react-icons/ai";
+import { SiMicrosoftazure } from "react-icons/si";
+import Button from "@/components/ui/Button";
+import CopyIcon from "@/components/copyIcon";
+import ChatContent from "@/components/chatContent";
+import BasicInfo from "@/components/share/basicInfo";
+import NotFound from "@/components/share/notFound";
+import Continue from "@/components/share/continue";
+
+type Props = {
+ params: { id: string };
+};
+
+export async function generateMetadata({ params }: Props): Promise {
+ const shareRes: any = await prisma.share.findUnique({
+ where: { id: params.id },
+ });
+
+ if (!shareRes) return {};
+
+ return {
+ title: "L-GPT Share",
+ description:
+ "L-GPT是一款开源项目,通过提供不同的AI模型来帮助你提高学习、工作、生活的效率。",
+ keywords:
+ "gpt,gpt-3.5-turbo,gpt-4,azure openai,claude,chat,聊天,LGPT,L-GPT",
+ openGraph: {
+ title: shareRes.channel_name,
+ description: "L-GPT Share Chat",
+ siteName: "L-GPT",
+ url: "https://gpt.ltopx.com",
+ },
+ twitter: {
+ title: shareRes.channel_name,
+ description: "L-GPT Share Chat",
+ card: "summary_large_image",
+ site: "@peekbomb",
+ creator: "@peekbomb",
+ },
+ };
+}
+
+async function getShareData(id: Share["id"]) {
+ const shareRes = await prisma.share.findUnique({
+ where: { id },
+ });
+
+ if (!shareRes) return { content: null, viewsCount: 0 };
+
+ const viewsCount: number | null = await redis.get(id);
+ await redis.set(id, viewsCount ? Number(viewsCount) + 1 : 1);
+
+ return { content: shareRes, viewsCount: viewsCount ? viewsCount : 1 };
+}
+
+function getTime(timestamp: string) {
+ const date = new Date(Number(timestamp));
+ let month: any = date.getMonth() + 1;
+ month = month < 10 ? "0" + month : month;
+ let day: any = date.getDate();
+ day = day < 10 ? "0" + day : day;
+ let hour: any = date.getHours();
+ hour = hour < 10 ? "0" + hour : hour;
+ let minute: any = date.getMinutes();
+ minute = minute < 10 ? "0" + minute : minute;
+ let seconds: any = date.getSeconds();
+ seconds = seconds < 10 ? "0" + seconds : seconds;
+
+ return `${month}-${day} ${hour}:${minute}:${seconds}`;
+}
+
+export default async function Share({ params }: any) {
+ const { content, viewsCount } = await getShareData(params.id);
+ if (!content) return ;
+
+ const { userName, anonymous } = content;
+
+ const shareFrom = userName && !anonymous ? userName : "";
+
+ return (
+ <>
+
+
+
+ {content.channel_name}
+
+
+
+
+
+ }>
+ {(content.channel_model as any)?.type}
+
+
+
+
+
+ {content.chat_content.map((item: any) => (
+
+
+ {item.role === "assistant" && (
+
+
+
+ )}
+ {item.role === "user" && (
+
+ )}
+
+
+
+ {getTime(item.time)}
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/api/share/create/route.ts b/src/app/api/share/create/route.ts
new file mode 100644
index 0000000..ac58391
--- /dev/null
+++ b/src/app/api/share/create/route.ts
@@ -0,0 +1,72 @@
+import { getServerSession } from "next-auth/next";
+import { authOptions } from "@/app/api/auth/[...nextauth]/route";
+import { prisma } from "@/lib/prisma";
+import { NextResponse } from "next/server";
+import type { ChatItem } from "@/hooks";
+
+interface ChannelModel {
+ supplier: string;
+ type: string;
+}
+
+export interface IShare {
+ channel_model: ChannelModel;
+ channel_name?: string;
+ channel_prompt?: string;
+ chat_content: ChatItem[];
+ userId?: string;
+ userName?: string;
+}
+
+export async function POST(request: Request) {
+ const session = await getServerSession(authOptions);
+ const { channel_model, channel_name, channel_prompt, chat_content }: IShare =
+ await request.json();
+
+ if (!channel_model) {
+ return NextResponse.json(
+ { error: -1, msg: "channel_model cannot be empty" },
+ { status: 500 }
+ );
+ }
+
+ if (!chat_content?.length) {
+ return NextResponse.json(
+ { error: -1, msg: "chat_content cannot be empty" },
+ { status: 500 }
+ );
+ }
+
+ // create share
+ let params: IShare = {
+ channel_model,
+ channel_name,
+ channel_prompt,
+ chat_content,
+ };
+
+ // 登录用户加上用户信息,但是可以选择匿名分享
+ if (session)
+ params = {
+ ...params,
+ userId: session?.user.id,
+ userName: session?.user.name as string,
+ };
+
+ try {
+ const createShareRes = await prisma.share.create({
+ data: params as any,
+ select: { id: true },
+ });
+ return NextResponse.json(
+ { error: 0, data: createShareRes },
+ { status: 200 }
+ );
+ } catch (error) {
+ console.log(error, "create share error");
+ return NextResponse.json(
+ { error: -1, msg: "create share error" },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/share/delete/route.ts b/src/app/api/share/delete/route.ts
new file mode 100644
index 0000000..3f87523
--- /dev/null
+++ b/src/app/api/share/delete/route.ts
@@ -0,0 +1,24 @@
+import { prisma } from "@/lib/prisma";
+import { NextResponse } from "next/server";
+
+export async function DELETE(request: Request) {
+ const { id } = await request.json();
+
+ if (!id) {
+ return NextResponse.json(
+ { error: -1, msg: "id cannot be empty" },
+ { status: 500 }
+ );
+ }
+
+ try {
+ await prisma.share.delete({
+ where: { id },
+ });
+
+ return NextResponse.json({ error: 0 }, { status: 200 });
+ } catch (error) {
+ console.log("delete share error", error);
+ return NextResponse.json({ error: -1, msg: "error" }, { status: 500 });
+ }
+}
diff --git a/src/app/api/share/route.ts b/src/app/api/share/route.ts
new file mode 100644
index 0000000..43afdb8
--- /dev/null
+++ b/src/app/api/share/route.ts
@@ -0,0 +1,25 @@
+import { prisma } from "@/lib/prisma";
+import { NextResponse } from "next/server";
+
+export async function GET(request: Request) {
+ const { searchParams } = new URL(request.url);
+
+ const id = searchParams.get("id");
+
+ if (!id) {
+ return NextResponse.json(
+ { error: -1, msg: "id cannot be empty" },
+ { status: 500 }
+ );
+ }
+
+ try {
+ const shareRes: any = await prisma.share.findUnique({
+ where: { id },
+ });
+
+ return NextResponse.json({ error: 0, data: shareRes }, { status: 200 });
+ } catch (error) {
+ return NextResponse.json({ error: -1, msg: "error" }, { status: 500 });
+ }
+}
diff --git a/src/app/api/share/update/route.ts b/src/app/api/share/update/route.ts
new file mode 100644
index 0000000..5629b60
--- /dev/null
+++ b/src/app/api/share/update/route.ts
@@ -0,0 +1,25 @@
+import { prisma } from "@/lib/prisma";
+import { NextResponse } from "next/server";
+
+export async function POST(request: Request) {
+ const { id, anonymous } = await request.json();
+
+ if (!id) {
+ return NextResponse.json(
+ { error: -1, msg: "id cannot be empty" },
+ { status: 500 }
+ );
+ }
+
+ try {
+ await prisma.share.update({
+ data: { anonymous: Number(!!anonymous) },
+ where: { id },
+ });
+
+ return NextResponse.json({ error: 0 }, { status: 200 });
+ } catch (error) {
+ console.log("update share error", error);
+ return NextResponse.json({ error: -1, msg: "error" }, { status: 500 });
+ }
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 09d78cf..8ba3cd6 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -10,7 +10,7 @@ interface RootLayoutProps {
children: React.ReactNode;
}
-// const inter = Inter({ subsets: ["latin"] });
+const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "L-GPT",
@@ -24,7 +24,7 @@ export const metadata: Metadata = {
export default function RootLayout({ children }: RootLayoutProps) {
return (
-
+
{children}
diff --git a/src/components/announcement/index.tsx b/src/components/announcement/index.tsx
index cb20d5f..fdd88e4 100644
--- a/src/components/announcement/index.tsx
+++ b/src/components/announcement/index.tsx
@@ -37,8 +37,6 @@ const Announcement: React.FC = () => {
{t("text1")}
{t("text2")}
{t("text3")}
- {t("text4")}
- {t("text5")}
diff --git a/src/components/chatContent/codeblock.tsx b/src/components/chatContent/codeblock.tsx
index 4849e5f..f7090e9 100644
--- a/src/components/chatContent/codeblock.tsx
+++ b/src/components/chatContent/codeblock.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import * as React from "react";
import { useTranslations } from "next-intl";
import { useClipboard } from "l-hooks";
diff --git a/src/components/chatSection/chatFooter/index.tsx b/src/components/chatSection/chatFooter/index.tsx
index 4ef4576..2ca5e2a 100644
--- a/src/components/chatSection/chatFooter/index.tsx
+++ b/src/components/chatSection/chatFooter/index.tsx
@@ -1,10 +1,13 @@
import * as React from "react";
-import clsx from "clsx";
import { useTranslations } from "next-intl";
import { useRouter } from "next-intl/client";
import { v4 as uuidv4 } from "uuid";
import type { ChatItem } from "@/hooks/useChannel";
-import { AiOutlineRedo, AiOutlineClear } from "react-icons/ai";
+import {
+ AiOutlineRedo,
+ AiOutlineClear,
+ AiOutlineShareAlt,
+} from "react-icons/ai";
import { BsStop } from "react-icons/bs";
import { useDebounceFn } from "ahooks";
import toast from "react-hot-toast";
@@ -19,10 +22,12 @@ import Confirm from "@/components/ui/Confirm";
import Button from "@/components/ui/Button";
import Textarea from "@/components/ui/Textarea";
import { useScrollToBottom } from "@/components/scrollToBottoms";
-import { isMobile, getReadableStream } from "@/lib";
+import { isMobile, getReadableStream, cn } from "@/lib";
import { PROMPT_BASE } from "@/prompt";
import { GPTTokens } from "@/lib/gpt-tokens";
import type { supportModelType } from "@/lib/gpt-tokens";
+import type { IShare } from "@/app/api/share/create/route";
+import Action from "@/components/share/action";
const ChatFooter: React.FC = () => {
// data
@@ -39,13 +44,16 @@ const ChatFooter: React.FC = () => {
} = useChat();
const [inputValue, setInputValue] = React.useState("");
const LLMOptions = React.useMemo(() => [openai, azure], [openai, azure]);
+ const [loadingShare, setLoadingShare] = React.useState(false);
// ref
const inputRef = React.useRef(null);
+ const actionRef = React.useRef(null);
const router = useRouter();
const t = useTranslations("chat");
+ const tShare = useTranslations("share");
const tMenu = useTranslations("menu");
const tPrompt = useTranslations("prompt");
const tCommon = useTranslations("common");
@@ -412,6 +420,52 @@ const ChatFooter: React.FC = () => {
});
};
+ // share functions
+ const handleShare = () => {
+ if (!findChannel) return;
+
+ const findModels =
+ LLMOptions.find((e) => e.value === findChannel.channel_model.type)
+ ?.models || [];
+
+ const findType = findModels.find(
+ (e) => e.value === findChannel.channel_model.name
+ )?.label;
+
+ if (!findType) {
+ return toast.error(tShare("model-error"), { id: "model-error" });
+ }
+
+ const params: IShare = {
+ channel_model: {
+ supplier: findChannel.channel_model.type,
+ type: findType,
+ },
+ channel_name: findChannel.channel_name,
+ channel_prompt: findChannel.channel_prompt,
+ chat_content: findChannel.chat_list,
+ };
+
+ setLoadingShare(true);
+ fetch("/api/share/create", {
+ method: "POST",
+ body: JSON.stringify(params),
+ })
+ .then((res) => res.json())
+ .then((res) => {
+ if (res.error || !res.data?.id) {
+ return toast.error(res.msg || tCommon("service-error"), {
+ id: "share-error",
+ });
+ }
+ toast.success(tShare("share-success"), { id: "share-success" });
+ actionRef.current?.init(res.data.id);
+ })
+ .finally(() => {
+ setLoadingShare(false);
+ });
+ };
+
React.useEffect(() => {
// Cancel response immediately when switching channels if data is being requested or generated.
if (loadingResponseStart || loadingResponseFinish) {
@@ -427,59 +481,68 @@ const ChatFooter: React.FC = () => {
}, [channel.activeId]);
return (
-
- {!!findChannel?.chat_list?.length && (
-
-
- ) : (
-
- )
- }
- >
- {loadingResponseFinish ? t("stop-generate") : t("re-generate")}
-
-
- )}
-
-
-
- }
- onOk={clearNowConversation}
+ <>
+
+ {!!findChannel?.chat_list?.length && (
+
+
+ ) : (
+
+ )
+ }
+ >
+ {loadingResponseFinish ? t("stop-generate") : t("re-generate")}
+
+
}
+ loading={loadingShare}
+ onClick={handleShare}
+ />
+
+ )}
+
+
+
+ }
+ onOk={clearNowConversation}
+ />
+
+
-
-
+
+ >
);
};
diff --git a/src/components/chatSection/chatList/index.tsx b/src/components/chatSection/chatList/index.tsx
index 88bb1f4..9f27b5d 100644
--- a/src/components/chatSection/chatList/index.tsx
+++ b/src/components/chatSection/chatList/index.tsx
@@ -6,8 +6,11 @@ import { useDateFormat } from "l-hooks";
import CopyIcon from "@/components/copyIcon";
import ChatContent from "@/components/chatContent";
import { useScrollToBottom } from "@/components/scrollToBottoms";
-import { AiOutlineLoading, AiOutlineDelete } from "react-icons/ai";
-import { FaUserAlt } from "react-icons/fa";
+import {
+ AiOutlineLoading,
+ AiOutlineDelete,
+ AiOutlineUser,
+} from "react-icons/ai";
import { cn } from "@/lib";
import { useChannel, useRevoke, useChat } from "@/hooks";
import type { ChatItem } from "@/hooks";
@@ -104,7 +107,7 @@ const ChatList: React.FC = () => {
height={32}
/>
) : (
-
+
)}
)}
diff --git a/src/components/copyIcon/index.tsx b/src/components/copyIcon/index.tsx
index f29a2b5..dc8d0cb 100644
--- a/src/components/copyIcon/index.tsx
+++ b/src/components/copyIcon/index.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import * as React from "react";
import { AiOutlineCopy, AiOutlineCheck } from "react-icons/ai";
import { useClipboard } from "l-hooks";
diff --git a/src/components/logo/index.tsx b/src/components/logo/index.tsx
index 41f224f..38f25de 100644
--- a/src/components/logo/index.tsx
+++ b/src/components/logo/index.tsx
@@ -3,13 +3,21 @@
import * as React from "react";
import { useRouter } from "next-intl/client";
import { cn } from "@/lib";
+import pkg from "../../../package.json";
interface LogoProps {
disabled?: boolean;
+ share?: boolean;
+ version?: boolean;
size?: "default" | "large";
}
-const Logo: React.FC = ({ disabled = false, size = "default" }) => {
+const Logo: React.FC = ({
+ disabled = false,
+ share = false,
+ version = true,
+ size = "default",
+}) => {
const router = useRouter();
const onClick = () => {
@@ -20,15 +28,25 @@ const Logo: React.FC = ({ disabled = false, size = "default" }) => {
return (
-
- L - GPT
-
+
+
+ L-GPT
+
+ {!!share && Share}
+
+ {!!version && (
+
+ v{pkg.version}
+
+ )}
);
};
diff --git a/src/components/menu/index.tsx b/src/components/menu/index.tsx
index 6099737..c779689 100644
--- a/src/components/menu/index.tsx
+++ b/src/components/menu/index.tsx
@@ -23,8 +23,8 @@ import ContextMenu from "@/components/ui/ContextMenu";
import type { ContextMenuOption } from "@/components/ui/ContextMenu";
import Dropdown from "@/components/ui/Dropdown";
import type { IDropdownItems } from "@/components/ui/Dropdown";
+import Logo from "@/components/logo";
import MenuIcon from "./icon";
-import pkg from "../../../package.json";
export const lans: IDropdownItems[] = [
{
@@ -159,20 +159,13 @@ const Menu: React.FC = () => {
return (
-
-
- L - GPT
-
-
-
- v{pkg.version}
-
-
+
+
-
+
{t("title")}
diff --git a/src/components/share/action.tsx b/src/components/share/action.tsx
new file mode 100644
index 0000000..0686df1
--- /dev/null
+++ b/src/components/share/action.tsx
@@ -0,0 +1,149 @@
+import * as React from "react";
+import { useTranslations } from "next-intl";
+import { toast } from "react-hot-toast";
+import { useClipboard } from "l-hooks";
+import {
+ AiOutlineLink,
+ AiOutlineTwitter,
+ AiOutlineDelete,
+ AiOutlineLoading,
+} from "react-icons/ai";
+import { BsThreeDots } from "react-icons/bs";
+import { FiUserMinus } from "react-icons/fi";
+import { cn } from "@/lib";
+import Modal from "@/components/ui/Modal";
+import Dropdown from "@/components/ui/Dropdown";
+import Button from "@/components/ui/Button";
+import type { IDropdownItems } from "@/components/ui/Dropdown";
+
+const Action = React.forwardRef((_, forwardedRef) => {
+ const t = useTranslations("share");
+ const tCommon = useTranslations("common");
+ const { copy } = useClipboard();
+
+ const shareId = React.useRef
("");
+ const [shareLink, setShareLink] = React.useState("");
+ const [loadingAction, setLoadingAction] = React.useState(false);
+
+ const options: IDropdownItems[] = [
+ {
+ label: t("share-anonymously"),
+ value: "anonymous",
+ icon: ,
+ },
+ {
+ label: t("share-to-twitter"),
+ value: "twitter",
+ icon: ,
+ },
+ {
+ label: t("share-remove"),
+ value: "delete",
+ icon: ,
+ },
+ ];
+
+ const [open, setOpen] = React.useState(false);
+
+ const onClose = () => setOpen(false);
+
+ const onCopyLink = () => {
+ copy(shareLink);
+ toast.success(t("share-link-copy-success"), { id: "copy-success" });
+ };
+
+ const onSelect = async (value: string) => {
+ if (value === "delete") {
+ setLoadingAction(true);
+ const res = await fetch("/api/share/delete", {
+ method: "DELETE",
+ body: JSON.stringify({ id: shareId.current }),
+ }).then((res) => res.json());
+ setLoadingAction(false);
+ if (res.error) {
+ return toast.error(tCommon("service-error"), { id: "delete-error" });
+ }
+ toast.success(t("share-remove-success"), { id: "delete-success" });
+ onClose();
+ } else if (value === "twitter") {
+ window.open(`https://twitter.com/share?url=${shareLink}`);
+ } else if (value === "anonymous") {
+ setLoadingAction(true);
+ const res = await fetch("/api/share/update", {
+ method: "POST",
+ body: JSON.stringify({ id: shareId.current, anonymous: 1 }),
+ }).then((res) => res.json());
+ setLoadingAction(false);
+ if (res.error) {
+ return toast.error(tCommon("service-error"), { id: "update-error" });
+ }
+ toast.success(t("share-update-success"), { id: "update-success" });
+ }
+ };
+
+ React.useImperativeHandle(forwardedRef, () => ({
+ init(id: string) {
+ shareId.current = id;
+ const baseURL =
+ process.env.NODE_ENV === "development"
+ ? "http://localhost:3000"
+ : "https://gpt.ltopx.com";
+ setShareLink(`${baseURL}/share/${id}`);
+ setOpen(true);
+ },
+ }));
+
+ return (
+
+
+
🎉🎉 {t("share-created")}
+
{t("share-logged-user-tip")}
+
❌❌ {t("share-delete-tip")}
+
+
+
}
+ onClick={onCopyLink}
+ >
+ {t("copy-link")}
+
+ {loadingAction ? (
+
+ ) : (
+
+
+
+ }
+ />
+ )}
+
+
+ );
+});
+
+Action.displayName = "Action";
+
+export default Action;
diff --git a/src/components/share/backHome.tsx b/src/components/share/backHome.tsx
new file mode 100644
index 0000000..f2e403d
--- /dev/null
+++ b/src/components/share/backHome.tsx
@@ -0,0 +1,31 @@
+"use client";
+
+import * as React from "react";
+import { useRouter } from "next-intl/client";
+import { useTranslations } from "next-intl";
+import Button from "@/components/ui/Button";
+import { AiFillCaretLeft } from "react-icons/ai";
+
+const BackHome: React.FC = () => {
+ const router = useRouter();
+ const t = useTranslations("share");
+ const [loading, setLoading] = React.useState(false);
+
+ const onClick = () => {
+ setLoading(true);
+ router.push("/");
+ };
+
+ return (
+ }
+ loading={loading}
+ onClick={onClick}
+ >
+ {t("back-home")}
+
+ );
+};
+
+export default BackHome;
diff --git a/src/components/share/basicInfo.tsx b/src/components/share/basicInfo.tsx
new file mode 100644
index 0000000..2617b9f
--- /dev/null
+++ b/src/components/share/basicInfo.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import * as React from "react";
+import { useTranslations } from "next-intl";
+import { AiOutlineFieldTime, AiOutlineUsergroupAdd } from "react-icons/ai";
+import { useDateFormat } from "l-hooks";
+
+const ViewsCount: React.FC<{ count: number; time: any; from: string }> = ({
+ count,
+ time,
+ from,
+}) => {
+ const t = useTranslations("share");
+ const { format } = useDateFormat();
+
+ return (
+
+ {!!from && (
+
+ {t("from")} {from}
+
+ )}
+
+
+
+
{format(time, "YYYY-MM-DD")}
+
+
|
+
+
+ {t("page-views")}:{count}
+
+
+
+ );
+};
+
+export default ViewsCount;
diff --git a/src/components/share/continue.tsx b/src/components/share/continue.tsx
new file mode 100644
index 0000000..9e7c201
--- /dev/null
+++ b/src/components/share/continue.tsx
@@ -0,0 +1,36 @@
+"use client";
+
+import * as React from "react";
+import { useTranslations } from "next-intl";
+import { useRouter } from "next-intl/client";
+import { VscDebugContinue } from "react-icons/vsc";
+import Button from "@/components/ui/Button";
+
+interface IProps {
+ id: string;
+}
+
+const Continue: React.FC = ({ id }) => {
+ const t = useTranslations("share");
+ const router = useRouter();
+ const [loading, setLoading] = React.useState(false);
+
+ const onContinue = () => {
+ setLoading(true);
+ router.push(`/create/${id}`);
+ };
+
+ return (
+ }
+ loading={loading}
+ onClick={onContinue}
+ >
+ {t("continue-conversation")}
+
+ );
+};
+
+export default Continue;
diff --git a/src/components/share/createChat.tsx b/src/components/share/createChat.tsx
new file mode 100644
index 0000000..b6a34d4
--- /dev/null
+++ b/src/components/share/createChat.tsx
@@ -0,0 +1,74 @@
+"use client";
+
+import * as React from "react";
+import { useTranslations } from "next-intl";
+import { useRouter } from "next-intl/client";
+import { v4 as uuidv4 } from "uuid";
+import { useLLM } from "@/hooks";
+
+interface IProps {
+ content: any;
+}
+
+const CreateChat: React.FC = ({ content }) => {
+ const t = useTranslations("share");
+ const router = useRouter();
+ const { openai, azure } = useLLM();
+
+ const LLMOptions = React.useMemo(() => [openai, azure], [openai, azure]);
+
+ const init = (content: any) => {
+ const localChannelList = localStorage.getItem("channelList");
+
+ const channel_model_type = content.channel_model?.supplier || "openai";
+ const findModels =
+ LLMOptions.find((item) => item.value === channel_model_type)?.models ||
+ [];
+ const channel_model_name =
+ findModels.find((item) => item.label === content.channel_model?.type)
+ ?.value || "gpt-3.5-turbo";
+
+ const chat_item = {
+ channel_id: uuidv4(),
+ channel_icon: "RiChatSmile2Line",
+ channel_name: content.channel_name || "",
+ channel_model: {
+ type: channel_model_type,
+ name: channel_model_name,
+ },
+ channel_prompt: content.channel_prompt || "",
+ channel_cost: {
+ tokens: 0,
+ usd: 0,
+ function_tokens: 0,
+ function_usd: 0,
+ total_tokens: 0,
+ total_usd: 0,
+ },
+ chat_list: content.chat_content ?? [],
+ };
+
+ if (!localChannelList) {
+ const arr = [chat_item];
+ localStorage.setItem("channelList", JSON.stringify(arr));
+ localStorage.setItem("activeId", chat_item.channel_id);
+ } else {
+ try {
+ const parseArr = JSON.parse(localChannelList);
+ parseArr.push(chat_item);
+ localStorage.setItem("channelList", JSON.stringify(parseArr));
+ localStorage.setItem("activeId", chat_item.channel_id);
+ } catch (error) {}
+ }
+
+ router.push("/");
+ };
+
+ React.useEffect(() => {
+ init(content);
+ }, []);
+
+ return {t("create-conversation")}
;
+};
+
+export default CreateChat;
diff --git a/src/components/share/notFound.tsx b/src/components/share/notFound.tsx
new file mode 100644
index 0000000..17a3d2d
--- /dev/null
+++ b/src/components/share/notFound.tsx
@@ -0,0 +1,12 @@
+"use client";
+
+import * as React from "react";
+import { useTranslations } from "next-intl";
+
+const NotFound: React.FC = () => {
+ const t = useTranslations("share");
+
+ return <>{t("not-exist")}>;
+};
+
+export default NotFound;
diff --git a/src/components/ui/Button/index.tsx b/src/components/ui/Button/index.tsx
index 1ade4bf..5323bfa 100644
--- a/src/components/ui/Button/index.tsx
+++ b/src/components/ui/Button/index.tsx
@@ -1,8 +1,10 @@
+"use client";
+
import * as React from "react";
import { cn } from "@/lib";
import { AiOutlineLoading } from "react-icons/ai";
-type ButtonType = "default" | "primary" | "danger" | "outline";
+type ButtonType = "default" | "primary" | "success" | "danger" | "outline";
type ButtonSize = "xs" | "sm" | "base" | "lg";
@@ -80,6 +82,16 @@ const Button = React.forwardRef(
type === "danger",
},
+ // success
+ {
+ "border-green-400 bg-green-400 hover:border-green-500 hover:bg-green-500 active:border-green-600 active:bg-green-600 text-white":
+ type === "success",
+ },
+ {
+ "dark:border-green-400/90 dark:bg-green-400/90 dark:hover:border-green-500/90 dark:hover:bg-green-500/90 dark:active:border-green-600/90 dark:active:bg-green-600/90":
+ type === "success",
+ },
+
// outline
{
"border-sky-400 text-sky-400 bg-white/80 backdrop-blur-sm hover:bg-sky-100/80 active:bg-sky-200/60":
diff --git a/src/lib/redis.ts b/src/lib/redis.ts
new file mode 100644
index 0000000..dc9458a
--- /dev/null
+++ b/src/lib/redis.ts
@@ -0,0 +1,8 @@
+import { Redis } from "@upstash/redis";
+
+export const redis = new Redis({
+ url: process.env.UPSTASH_REDIS_REST_URL as string,
+ token: process.env.UPSTASH_REDIS_REST_TOKEN as string,
+});
+
+// const data = await redis.set('foo', 'bar');
diff --git a/src/locales/en.json b/src/locales/en.json
index be853db..ccddb45 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -46,7 +46,7 @@
"copy-code": "Copy Code",
"delete": "Delete",
"enter-message": "Please enter a message",
- "re-generate": "Regenerate response",
+ "re-generate": "Regenerate",
"stop-generate": "Stop Generating",
"type-message": "Type message here...",
"type-message-commandEnter": "Type message here... (send with Command + Enter, can be modified in Settings)",
@@ -87,6 +87,27 @@
"10003": "Language model parameter error",
"10004": "Please configure Azure API Version first"
},
+ "share": {
+ "back-home": "Back Home",
+ "continue-conversation": "Continue this conversation",
+ "copy-link": "Copy Link",
+ "create-conversation": "Creating conversation...",
+ "from": "Shared from",
+ "not-exist": "Shared content does not exist or has expired, you can click on the button on the upper right corner to return to the homepage and start a new chat.",
+ "model-error": "Conversation model parameter error",
+ "page-views": "Pageviews",
+ "share-anonymously": "Share anonymously",
+ "share-created": "You have successfully created a sharing link for the current session.",
+ "share-delete-tip": "All unattended sharing links will be automatically cleared after 7 days.",
+ "share-link": "Share Link to Conversation",
+ "share-link-copy-success": "Successfully copied conversation sharing link",
+ "share-logged-user-tip": "Logged-in users can view and manage their shared links in their personal center after sharing the session link.",
+ "share-remove": "Remove Share",
+ "share-remove-success": "Conversation sharing deleted successfully",
+ "share-success": "Conversation shared successfully",
+ "share-to-twitter": "Share to Twitter",
+ "share-update-success": "Conversation sharing updated successfully"
+ },
"setting": {
"api-key-error": "Invalid API key. Please make sure your API key is still working properly.",
"api-proxy": "API Proxy",
@@ -150,10 +171,8 @@
},
"zLog": {
"title": "v0.4.3 Change Log 🔥🔥",
- "text1": "Fixed the issue where unauthenticated users could not have a normal conversation even after configuring the API key",
- "text2": "Upgrade the default Azure OpenAI Service API version from 2023-03-15-preview to 2023-05-15.",
- "text3": "Refactor the calculation logic for session consumption, now supporting display of current session content consumption and total session consumption.",
- "text4": "Refactor the API Key configuration interface and move it to a separate page for configuration. It is now possible to configure Azure OpenAI Service-related information in a more user-friendly way.",
- "text5": "Other details optimization and bug fixing"
+ "text1": "🎉🎉 This update is not flashy, mainly updating the conversation sharing function. Now you can share your current conversation with your friends and partners.",
+ "text2": "Unified global logo style",
+ "text3": "Other UI refinement and bug fixes"
}
}
\ No newline at end of file
diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json
index 3bfbc46..c06fcbf 100644
--- a/src/locales/zh-CN.json
+++ b/src/locales/zh-CN.json
@@ -87,6 +87,27 @@
"10003": "语言模型参数错误",
"10004": "请先配置Azure API Version"
},
+ "share": {
+ "back-home": "返回首页",
+ "continue-conversation": "继续此对话",
+ "copy-link": "复制链接",
+ "create-conversation": "会话创建中…",
+ "from": "分享来自",
+ "not-exist": "分享内容不存在或已过期。您可以点击右上角按钮返回首页开始新的聊天",
+ "model-error": "会话模型参数错误",
+ "page-views": "总浏览量",
+ "share-anonymously": "匿名分享",
+ "share-created": "您已经成功创建当前会话的分享链接",
+ "share-delete-tip": "所有未操作的分享链接将在7日被后自动清除",
+ "share-link": "分享对话链接",
+ "share-link-copy-success": "会话分享链接复制成功",
+ "share-logged-user-tip": "已经登录的用户分享会话链接后可在个人中心查看和管理自己的分享链接",
+ "share-remove": "删除分享",
+ "share-remove-success": "会话分享删除成功",
+ "share-success": "会话分享成功",
+ "share-to-twitter": "分享到 Twitter",
+ "share-update-success": "会话分享更新成功"
+ },
"setting": {
"api-key-error": "无效的 API 密钥。请确保您的 API 密钥配置正确。",
"api-proxy": "API 代理地址",
@@ -149,11 +170,9 @@
"welcome": "欢迎来到"
},
"zLog": {
- "title": "v0.4.3版本更新日志 🔥🔥",
- "text1": "修复未登录用户配置 api key 依旧无法正常对话的问题",
- "text2": "升级默认的 Azure OpenAI Service API Version 从 2023-03-15-preview 到 2023-05-15",
- "text3": "重构会话消耗的计算逻辑,现在支持展示当前会话内容消耗以及当前会话总消耗",
- "text4": "重构 API Key 配置界面,将其移动到单独的界面进行配置。现在能更友好的配置Azure OpenAI Service相关信息",
- "text5": "其他细节优化和 bug 修复"
+ "title": "v0.5.0版本更新啦 🔥🔥",
+ "text1": "🎉🎉 本次更新不花里胡哨,主要就是更新了会话分享功能。现在能够分享你当前会话给你的朋友和伙伴了",
+ "text2": "统一全局 Logo 样式",
+ "text3": "其他UI细节优化和 bug 修复"
}
}
\ No newline at end of file
diff --git a/tailwind.config.js b/tailwind.config.js
index 42ae40b..0d51c66 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -8,6 +8,10 @@ module.exports = {
],
theme: {
extend: {
+ backgroundImage: {
+ "share-ico":
+ "linear-gradient(to right top,#d16ba5,#c777b9,#ba83ca,#aa8fd8,#9a9ae1,#8aa7ec,#79b3f4,#69bff8,#52cffe,#41dfff,#46eefa,#5ffbf1)",
+ },
keyframes: {
fadeIn: {
"0%": { opacity: 0 },