mirror of
https://github.com/coaidev/coai.git
synced 2025-05-20 05:20:15 +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";
|
||||
@line-height: 1.5;
|
||||
@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",
|
||||
home: "Home",
|
||||
login: "Login",
|
||||
"login-require": "You need to login to use this feature",
|
||||
logout: "Logout",
|
||||
quota: "Quota",
|
||||
"try-again": "Try again",
|
||||
@ -151,6 +152,7 @@ const resources = {
|
||||
"not-found": "页面未找到",
|
||||
home: "首页",
|
||||
login: "登录",
|
||||
"login-require": "您需要登录才能使用此功能",
|
||||
logout: "登出",
|
||||
quota: "配额",
|
||||
"try-again": "重试",
|
||||
@ -281,6 +283,7 @@ const resources = {
|
||||
"not-found": "Страница не найдена",
|
||||
home: "Главная",
|
||||
login: "Войти",
|
||||
"login-require": "Вам нужно войти, чтобы использовать эту функцию",
|
||||
logout: "Выйти",
|
||||
quota: "Квота",
|
||||
"try-again": "Попробуйте еще раз",
|
||||
|
@ -2,6 +2,7 @@ import { createBrowserRouter } from "react-router-dom";
|
||||
import Home from "./routes/Home.tsx";
|
||||
import NotFound from "./routes/NotFound.tsx";
|
||||
import Auth from "./routes/Auth.tsx";
|
||||
import Generation from "./routes/Generation.tsx";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@ -16,6 +17,11 @@ const router = createBrowserRouter([
|
||||
Component: Auth,
|
||||
ErrorBoundary: NotFound,
|
||||
},
|
||||
{
|
||||
id: "generation",
|
||||
path: "/generate",
|
||||
Component: Generation,
|
||||
}
|
||||
]);
|
||||
|
||||
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