mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-08-07 23:14:54 +08:00
feat: add basic ui
This commit is contained in:
35
app/components/button.module.css
Normal file
35
app/components/button.module.css
Normal file
@@ -0,0 +1,35 @@
|
||||
.icon-button {
|
||||
background-color: var(--white);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
|
||||
box-shadow: var(--card-shadow);
|
||||
cursor: pointer;
|
||||
transition: all .3s ease;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: var(--border-in-light);
|
||||
}
|
||||
|
||||
.icon-button:hover {
|
||||
filter: brightness(0.9) hue-rotate(0.01turn);
|
||||
}
|
||||
|
||||
.icon-button-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-button-text {
|
||||
margin-left: 5px;
|
||||
font-size: 12px;
|
||||
}
|
25
app/components/button.tsx
Normal file
25
app/components/button.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as React from "react";
|
||||
|
||||
import styles from "./button.module.css";
|
||||
|
||||
export function IconButton(props: {
|
||||
onClick?: () => void;
|
||||
icon: JSX.Element;
|
||||
text?: string;
|
||||
bordered?: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
styles["icon-button"] +
|
||||
` ${props.bordered && styles.border} ${props.className ?? ""}`
|
||||
}
|
||||
>
|
||||
<div className={styles["icon-button-icon"]}>{props.icon}</div>
|
||||
{props.text && (
|
||||
<div className={styles["icon-button-text"]}>{props.text}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
222
app/components/home.module.css
Normal file
222
app/components/home.module.css
Normal file
@@ -0,0 +1,222 @@
|
||||
.container {
|
||||
max-width: 1080px;
|
||||
max-height: 780px;
|
||||
min-width: 600px;
|
||||
width: 90vw;
|
||||
height: 90vh;
|
||||
background-color: var(--white);
|
||||
border: var(--border-in-light);
|
||||
border-radius: 20px;
|
||||
box-shadow: var(--shadow);
|
||||
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
max-width: 300px;
|
||||
padding: 20px;
|
||||
background-color: var(--second);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow:inset -2px 0px 2px 0px rgb(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
position: relative;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 18px;
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sidebar-sub-title {
|
||||
font-size: 12px;
|
||||
font-weight: 400px;
|
||||
}
|
||||
|
||||
.sidebar-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.chat-list {
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.chat-item {
|
||||
padding: 10px 14px;
|
||||
background-color: var(--white);
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: var(--card-shadow);
|
||||
transition: all .3s ease;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.chat-item:hover {
|
||||
background-color: var(--hover-color);
|
||||
}
|
||||
|
||||
.chat-item-selected {
|
||||
border: 2px solid var(--primary);
|
||||
}
|
||||
|
||||
.chat-item-title {
|
||||
font-size: 14px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.chat-item-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: rgb(166, 166, 166);
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.chat-item-count {}
|
||||
.chat-item-date {}
|
||||
|
||||
.sidebar-tail {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.sidebar-actions {
|
||||
display: inline-flex;
|
||||
}
|
||||
.sidebar-action:last-child {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.chat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
padding: 14px 20px;
|
||||
border-bottom: rgba(0, 0, 0, 0.1) 1px solid;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.chat-header-title {
|
||||
font-size: 20px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
.chat-header-sub-title {
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.chat-actions {
|
||||
display: inline-flex;
|
||||
}
|
||||
.chat-action-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.chat-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 20px;
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
.chat-message {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.chat-message-reverse {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.chat-message-container {
|
||||
width: 60%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.chat-message-reverse > .chat-message-container {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.chat-message-avtar {}
|
||||
.chat-message-item {
|
||||
border-radius: 10px;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
user-select: text;
|
||||
}
|
||||
.chat-message-actions{
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
width: 100%;
|
||||
padding: 5px 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.chat-message-action-date{
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
||||
.chat-message-action-button{}
|
||||
|
||||
.chat-input-panel {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat-input-panel-inner {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chat-input-panel-multi {}
|
||||
.chat-input {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
border: var(--border-in-light);
|
||||
box-shadow: var(--card-shadow);
|
||||
font-family: inherit;
|
||||
padding: 10px 14px;
|
||||
resize: none;
|
||||
outline: none;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.chat-input:focus {
|
||||
border: 1px solid var(--primary);
|
||||
}
|
||||
|
||||
.chat-input-send{
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
bottom: 10px;
|
||||
}
|
177
app/components/home.tsx
Normal file
177
app/components/home.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
"use client";
|
||||
|
||||
import { IconButton } from "./button";
|
||||
import styles from "./home.module.css";
|
||||
|
||||
import SettingsIcon from "../icons/settings.svg";
|
||||
import GithubIcon from "../icons/github.svg";
|
||||
import ChatGptIcon from "../icons/chatgpt.svg";
|
||||
import SendWhiteIcon from "../icons/send-white.svg";
|
||||
import BrainIcon from "../icons/brain.svg";
|
||||
import ExportIcon from "../icons/export.svg";
|
||||
import BotIcon from "../icons/bot.svg";
|
||||
import UserIcon from "../icons/user.svg";
|
||||
import AddIcon from "../icons/add.svg";
|
||||
|
||||
export function ChatItem(props: {
|
||||
onClick?: () => void;
|
||||
title: string;
|
||||
count: number;
|
||||
time: string;
|
||||
selected: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`${styles["chat-item"]} ${
|
||||
props.selected && styles["chat-item-selected"]
|
||||
}`}
|
||||
>
|
||||
<div className={styles["chat-item-title"]}>{props.title}</div>
|
||||
<div className={styles["chat-item-info"]}>
|
||||
<div className={styles["chat-item-count"]}>{props.count} 条对话</div>
|
||||
<div className={styles["chat-item-date"]}>{props.time}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChatList() {
|
||||
const listData = new Array(5).fill({
|
||||
title: "这是一个标题",
|
||||
count: 10,
|
||||
time: new Date().toLocaleString(),
|
||||
});
|
||||
|
||||
const selectedIndex = 0;
|
||||
|
||||
return (
|
||||
<div className={styles["chat-list"]}>
|
||||
{listData.map((item, i) => (
|
||||
<ChatItem {...item} key={i} selected={i === selectedIndex} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Chat() {
|
||||
const messages = [
|
||||
{
|
||||
role: "user",
|
||||
content: "这是一条消息",
|
||||
date: new Date().toLocaleString(),
|
||||
},
|
||||
{
|
||||
role: "bot",
|
||||
content: "这是一条回复".repeat(10),
|
||||
date: new Date().toLocaleString(),
|
||||
},
|
||||
];
|
||||
|
||||
const title = "这是一个标题";
|
||||
const count = 10;
|
||||
|
||||
return (
|
||||
<div className={styles.chat}>
|
||||
<div className={styles["chat-header"]}>
|
||||
<div>
|
||||
<div className={styles["chat-header-title"]}>{title}</div>
|
||||
<div className={styles["chat-header-sub-title"]}>
|
||||
与 ChatGPT 的 {count} 条对话
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles["chat-actions"]}>
|
||||
<div className={styles["chat-action-button"]}>
|
||||
<IconButton icon={<BrainIcon />} bordered />
|
||||
</div>
|
||||
<div className={styles["chat-action-button"]}>
|
||||
<IconButton icon={<ExportIcon />} bordered />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles["chat-body"]}>
|
||||
{messages.map((message, i) => {
|
||||
const isUser = message.role === "user";
|
||||
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={
|
||||
isUser ? styles["chat-message-reverse"] : styles["chat-message"]
|
||||
}
|
||||
>
|
||||
<div className={styles["chat-message-container"]}>
|
||||
<div className={styles["chat-message-avtar"]}>
|
||||
{message.role === "user" ? <UserIcon /> : <BotIcon />}
|
||||
</div>
|
||||
<div className={styles["chat-message-item"]}>
|
||||
{message.content}
|
||||
</div>
|
||||
{!isUser && (
|
||||
<div className={styles["chat-message-actions"]}>
|
||||
<div className={styles["chat-message-action-date"]}>
|
||||
{message.date}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className={styles["chat-input-panel"]}>
|
||||
<div className={styles["chat-input-panel-inner"]}>
|
||||
<textarea
|
||||
className={styles["chat-input"]}
|
||||
placeholder="输入消息"
|
||||
rows={3}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<SendWhiteIcon />}
|
||||
text={"发送"}
|
||||
className={styles["chat-input-send"]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Home() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.sidebar}>
|
||||
<div className={styles["sidebar-header"]}>
|
||||
<div className={styles["sidebar-title"]}>ChatGPT Next</div>
|
||||
<div className={styles["sidebar-sub-title"]}>
|
||||
Build your own AI assistant.
|
||||
</div>
|
||||
<div className={styles["sidebar-logo"]}>
|
||||
<ChatGptIcon />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles["sidebar-body"]}>
|
||||
<ChatList />
|
||||
</div>
|
||||
|
||||
<div className={styles["sidebar-tail"]}>
|
||||
<div className={styles["sidebar-actions"]}>
|
||||
<div className={styles["sidebar-action"]}>
|
||||
<IconButton icon={<SettingsIcon />} />
|
||||
</div>
|
||||
<div className={styles["sidebar-action"]}>
|
||||
<IconButton icon={<GithubIcon />} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<IconButton icon={<AddIcon />} text={"新的聊天"} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Chat key="chat" />
|
||||
</div>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user