mirror of
https://github.com/coaidev/coai.git
synced 2025-05-22 06:20:14 +09:00
add code generation feature
This commit is contained in:
parent
f10b26ec5f
commit
a6ebbb2802
88
app/src/assets/generation.less
Normal file
88
app/src/assets/generation.less
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
.generation-page {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - 56px);
|
||||||
|
|
||||||
|
.login-action {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: auto;
|
||||||
|
transform: translateY(-28px);
|
||||||
|
|
||||||
|
.tip {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: max(6vh, 24px);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.generation-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 12px 16px;
|
||||||
|
gap: 6px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.action {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.generation-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 15vh 0;
|
||||||
|
gap: 2rem;
|
||||||
|
|
||||||
|
.product {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2rem;
|
||||||
|
gap: 12px;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 80%;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 680px;
|
||||||
|
|
||||||
|
.input {
|
||||||
|
flex-grow: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
height: 46px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: 1px solid hsl(var(--border-hover));
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
width: 46px;
|
||||||
|
height: 46px;
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
@import "ui";
|
||||||
@font-family: Andika,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
|
@font-family: Andika,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
|
||||||
@line-height: 1.5;
|
@line-height: 1.5;
|
||||||
@font-weight: 400;
|
@font-weight: 400;
|
||||||
|
27
app/src/assets/ui.less
Normal file
27
app/src/assets/ui.less
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
.select-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.select-group-item {
|
||||||
|
padding: 0.35rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: .2s;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
background: hsl(var(--accent-secondary));
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: hsl(var(--accent));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: hsl(var(--text));
|
||||||
|
color: hsl(var(--background));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
app/src/components/SelectGroup.tsx
Normal file
25
app/src/components/SelectGroup.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
type SelectGroupProps = {
|
||||||
|
current: string,
|
||||||
|
list: string[],
|
||||||
|
onChange?: (select: string) => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectGroup(props: SelectGroupProps) {
|
||||||
|
return (
|
||||||
|
<div className={`select-group`}>
|
||||||
|
{
|
||||||
|
props.list.map((select: string, idx: number) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
onClick={() => props.onChange?.(select)}
|
||||||
|
className={`select-group-item ${select == props.current ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
{ select }
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectGroup;
|
@ -11,6 +11,7 @@ const resources = {
|
|||||||
"not-found": "Page not found",
|
"not-found": "Page not found",
|
||||||
home: "Home",
|
home: "Home",
|
||||||
login: "Login",
|
login: "Login",
|
||||||
|
"login-require": "You need to login to use this feature",
|
||||||
logout: "Logout",
|
logout: "Logout",
|
||||||
quota: "Quota",
|
quota: "Quota",
|
||||||
"try-again": "Try again",
|
"try-again": "Try again",
|
||||||
@ -151,6 +152,7 @@ const resources = {
|
|||||||
"not-found": "页面未找到",
|
"not-found": "页面未找到",
|
||||||
home: "首页",
|
home: "首页",
|
||||||
login: "登录",
|
login: "登录",
|
||||||
|
"login-require": "您需要登录才能使用此功能",
|
||||||
logout: "登出",
|
logout: "登出",
|
||||||
quota: "配额",
|
quota: "配额",
|
||||||
"try-again": "重试",
|
"try-again": "重试",
|
||||||
@ -281,6 +283,7 @@ const resources = {
|
|||||||
"not-found": "Страница не найдена",
|
"not-found": "Страница не найдена",
|
||||||
home: "Главная",
|
home: "Главная",
|
||||||
login: "Войти",
|
login: "Войти",
|
||||||
|
"login-require": "Вам нужно войти, чтобы использовать эту функцию",
|
||||||
logout: "Выйти",
|
logout: "Выйти",
|
||||||
quota: "Квота",
|
quota: "Квота",
|
||||||
"try-again": "Попробуйте еще раз",
|
"try-again": "Попробуйте еще раз",
|
||||||
|
@ -2,6 +2,7 @@ import { createBrowserRouter } from "react-router-dom";
|
|||||||
import Home from "./routes/Home.tsx";
|
import Home from "./routes/Home.tsx";
|
||||||
import NotFound from "./routes/NotFound.tsx";
|
import NotFound from "./routes/NotFound.tsx";
|
||||||
import Auth from "./routes/Auth.tsx";
|
import Auth from "./routes/Auth.tsx";
|
||||||
|
import Generation from "./routes/Generation.tsx";
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -16,6 +17,11 @@ const router = createBrowserRouter([
|
|||||||
Component: Auth,
|
Component: Auth,
|
||||||
ErrorBoundary: NotFound,
|
ErrorBoundary: NotFound,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "generation",
|
||||||
|
path: "/generate",
|
||||||
|
Component: Generation,
|
||||||
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
97
app/src/routes/Generation.tsx
Normal file
97
app/src/routes/Generation.tsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import "../assets/generation.less";
|
||||||
|
import {useSelector} from "react-redux";
|
||||||
|
import {selectAuthenticated} from "../store/auth.ts";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import {Button} from "../components/ui/button.tsx";
|
||||||
|
import {ChevronLeft, Info, LogIn, Send} from "lucide-react";
|
||||||
|
import {login} from "../conf.ts";
|
||||||
|
import router from "../router.ts";
|
||||||
|
import {Input} from "../components/ui/input.tsx";
|
||||||
|
import {useEffect, useRef, useState} from "react";
|
||||||
|
import SelectGroup from "../components/SelectGroup.tsx";
|
||||||
|
|
||||||
|
type WrapperProps = {
|
||||||
|
onSend?: (value: string) => boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
function Wrapper(props: WrapperProps) {
|
||||||
|
const ref = useRef(null);
|
||||||
|
const [ model, setModel ] = useState('GPT-3.5');
|
||||||
|
|
||||||
|
function handleSend() {
|
||||||
|
const target = ref.current as HTMLInputElement | null;
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
const value = target.value.trim();
|
||||||
|
if (!value.length) return;
|
||||||
|
|
||||||
|
if (props.onSend?.(value)) {
|
||||||
|
target.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current && (ref.current as HTMLInputElement).focus();
|
||||||
|
ref.current && (ref.current as HTMLInputElement).addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
handleSend();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div className={`generation-wrapper`}>
|
||||||
|
<div className={`product`}>
|
||||||
|
<img src={`/favicon.ico`} alt={""} />
|
||||||
|
AI Code Generator
|
||||||
|
</div>
|
||||||
|
<div className={`input-box`}>
|
||||||
|
<Input className={`input`} ref={ref} />
|
||||||
|
<Button size={`icon`} className={`action`} variant={`default`} onClick={handleSend}>
|
||||||
|
<Send className={`h-5 w-5`} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={`model-box`}>
|
||||||
|
<SelectGroup current={model} list={[
|
||||||
|
'GPT-3.5', 'GPT-3.5-16k', 'GPT-4', 'GPT-4-32k'
|
||||||
|
]} onChange={setModel} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
function Generation() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const auth = useSelector(selectAuthenticated);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`generation-page`}>
|
||||||
|
{
|
||||||
|
auth ?
|
||||||
|
<div className={`generation-container`}>
|
||||||
|
<Button className={`action`} variant={`ghost`} size={`icon`} onClick={
|
||||||
|
() => router.navigate("/")
|
||||||
|
}>
|
||||||
|
<ChevronLeft className={`h-5 w-5 back`} />
|
||||||
|
</Button>
|
||||||
|
<Wrapper onSend={(value: string) => {
|
||||||
|
console.log(value);
|
||||||
|
return true;
|
||||||
|
}} />
|
||||||
|
</div> :
|
||||||
|
<div className={`login-action`}>
|
||||||
|
<div className={`tip`}>
|
||||||
|
<Info className={`h-4 w-4 mr-2`} />
|
||||||
|
{ t('login-require') }
|
||||||
|
</div>
|
||||||
|
<Button size={`lg`} onClick={login}>
|
||||||
|
<LogIn className={`h-4 w-4 mr-2`} />
|
||||||
|
<p className={`text`}>
|
||||||
|
{ t('login') }
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Generation;
|
Loading…
Reference in New Issue
Block a user