Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
cabb1af
💄 UI: add google icon svg [KOBG-11]
minzee09 Aug 14, 2025
5c78cb1
✨ feat: add googleLoginBtn component [KOBG-11]
minzee09 Aug 14, 2025
5d97dc2
💄 UI: add onboarding [KOBG-11]
minzee09 Aug 14, 2025
4cf8cd2
💄 UI: add login [KOBG-11]
minzee09 Aug 14, 2025
e7cddb0
💄 UI: add chevron-left svg [KOBG-11]
minzee09 Aug 14, 2025
40883f3
✨ feat: add topAppBar component
minzee09 Aug 14, 2025
57a1c15
💄 UI: add designed logo [KOBG-11]
minzee09 Aug 15, 2025
6f9ddb1
📦 package: add framer-motion [KOBG-11]
minzee09 Aug 15, 2025
ee7af2f
🔥 remove: logo layout [KOBG-11]
minzee09 Aug 15, 2025
0563b76
✨ feat: add Logo component [KOBG-11]
minzee09 Aug 15, 2025
85da693
✨ feat: add splash screen [KOBG-11]
minzee09 Aug 15, 2025
a67f658
✨ feat: add login screen [KOBG-11]
minzee09 Aug 15, 2025
89f749c
✨ feat: add onClick props to btn [KOBG-11]
minzee09 Aug 15, 2025
b67d4fe
✨ feat: add route constants [KOBG-11]
minzee09 Aug 15, 2025
6c72528
♻️ refactor: add route constants [KOBG-11]
minzee09 Aug 15, 2025
efeedce
✨ feat: add google login util
minzee09 Aug 15, 2025
7bf4667
🎨 style: update format in login screen [KOBG-11]
minzee09 Aug 15, 2025
953534b
✨ feat: add google login handler [KOBG-11]
minzee09 Aug 15, 2025
e106e84
✨ feat: add go back button to TopAppBar component [KOBG-11]
minzee09 Aug 16, 2025
4f6d4ad
✨ feat: add sign step routes [KOBG-11]
minzee09 Aug 16, 2025
96d1894
💄 UI: add fade in motion component [KOBG-11]
minzee09 Aug 16, 2025
64cb8bc
💄 UI: delete padding [KOBG-11]
minzee09 Aug 16, 2025
165958d
✨ feat: add title text component [KOBG-11]
minzee09 Aug 16, 2025
7170314
✨ feat: add progress bar component [KOBG-11]
minzee09 Aug 16, 2025
e8e6650
💄 UI: modify height [KOBG-11]
minzee09 Aug 16, 2025
c3cdb21
✨ feat: modify route [KOBG-11]
minzee09 Aug 16, 2025
0796348
✨ feat: add signin screen [KOBG-11]
minzee09 Aug 16, 2025
f646cf5
📦 package: add zustand [KOBG-11]
minzee09 Aug 16, 2025
3110579
💄 UI: add chevron icons [KOBG-11]
minzee09 Aug 16, 2025
81451be
✨ feat: add language selection dropdown component [KOBG-11]
minzee09 Aug 16, 2025
03e4e35
✨ feat: add language global state [KOBG-11]
minzee09 Aug 16, 2025
eb759a4
💄 UI: add disabled style to dropdown [KOBG-11]
minzee09 Aug 16, 2025
635ed61
🎨 style: modify component folder name [KOBG-11]
minzee09 Aug 16, 2025
6a8b6bb
💄 UI: add margin [KOBG-11]
minzee09 Aug 16, 2025
e3a3ec0
✨ feat: add persist middleware to save in local storage [KOBG-11]
minzee09 Aug 16, 2025
6f518b7
✨ feat: add onChange prop to dropdown component [KOBG-11]
minzee09 Aug 16, 2025
e40544d
📦 package: add iso-639-1 [KOBG-11]
minzee09 Aug 18, 2025
06725ee
💄 UI: add icons [KOBG-11]
minzee09 Aug 18, 2025
a3d5662
✨ feat: add bottom sheet component [KOBG-11]
minzee09 Aug 18, 2025
a53cd94
🎨 style: modify file path [KOBG-11]
minzee09 Aug 18, 2025
19ce927
💄 UI: add all languages dropdown [KOBG-11]
minzee09 Aug 18, 2025
5f0146b
✨ feat: add language codes constants [KOBG-11]
minzee09 Aug 18, 2025
b87d4fb
💄 UI: add button component [KOBG-11]
minzee09 Aug 18, 2025
6ab9a7a
✨ feat: add step1 of signin screen [KOBG-11]
minzee09 Aug 18, 2025
2daad5c
✨ feat: modify next page route [KOBG-11]
minzee09 Aug 18, 2025
47660b2
💄 UI: add icons [KOBG-11]
minzee09 Aug 18, 2025
ed3d041
💄 UI: add scrollbar-hide in globals.css [KOBG-11]
minzee09 Aug 18, 2025
194c68e
✨ feat: add ordinal util [KOBG-11]
minzee09 Aug 18, 2025
bea3ae1
💄 UI: add text field components [KOBG-11]
minzee09 Aug 18, 2025
02586d9
✨ feat: modify props for TitleText component [KOBG-11]
minzee09 Aug 18, 2025
30d4579
✨ feat: add stores [KOBG-11]
minzee09 Aug 18, 2025
020ca2b
✨ feat: add school, voice constants [KOBG-11]
minzee09 Aug 18, 2025
25d6787
💄 UI: add school select dropdown component [KOBG-11]
minzee09 Aug 18, 2025
e06c1c3
💄 UI: add select type button component [KOBG-11]
minzee09 Aug 18, 2025
edaa5fd
💄 UI: add character voice toggle btn component [KOBG-11]
minzee09 Aug 18, 2025
7385288
💄 UI: modify color [KOBG-11]
minzee09 Aug 18, 2025
88221de
💄 UI: add character text component [KOBG-11]
minzee09 Aug 18, 2025
15d76a9
✨ feat: update route constants [KOBG-11]
minzee09 Aug 18, 2025
548af15
✨ feat: add signin screens [KOBG-11]
minzee09 Aug 18, 2025
165531b
✨ feat: add voice selection screens [KOBG-11]
minzee09 Aug 18, 2025
2d3f201
💄 UI: add navigation bar component [KOBG-11]
minzee09 Aug 18, 2025
0c29eb7
✨ feat: add main screens
minzee09 Aug 18, 2025
f0b2917
🐛 bug: fix text color(black) not showing up in android [KOBG-11]
minzee09 Aug 18, 2025
8cfed75
💄 UI: add icons [KOBG-11]
minzee09 Aug 19, 2025
44b9135
✨ feat: add my-learning route constants [KOBG-11]
minzee09 Aug 19, 2025
8b59fb9
✨ feat: add step1 rating constants [KOBG-11]
minzee09 Aug 19, 2025
10ac0d5
💄 UI: update to add center text [KOBG-11]
minzee09 Aug 19, 2025
e0bac81
💄 UI: update button color props [KOBG-11]
minzee09 Aug 19, 2025
805274a
🐛 bug: modify router route [KOBG-11]
minzee09 Aug 19, 2025
a5b138e
✨ feat: add toast message component [KOBG-11]
minzee09 Aug 19, 2025
55420a7
✨ feat: add locales for my-learning tab [KOBG-11]
minzee09 Aug 19, 2025
fd40569
💄 UI: add textfield component [KOBG-11]
minzee09 Aug 19, 2025
3a8e616
✨ feat: add voice & keyboard btn component [KOBG-11]
minzee09 Aug 19, 2025
2fce623
💄 UI: add character text component [KOBG-11]
minzee09 Aug 19, 2025
edfcc2f
✨ feat: add level finder from param hook [KOBG-11]
minzee09 Aug 19, 2025
1bc79d9
💄 UI: add my-learning screen [KOBG-11]
minzee09 Aug 19, 2025
4ddaddf
✨ feat: add my-learning components [KOBG-11]
minzee09 Aug 19, 2025
7cd145f
✨ feat: add my-learning intro screen [KOBG-11]
minzee09 Aug 19, 2025
ddc894f
✨ feat: add custom text highlighter util [KOBG-11]
minzee09 Aug 19, 2025
d614c8c
✨ feat: add my-learning step1 screen [KOBG-11]
minzee09 Aug 19, 2025
76eb390
✨ feat: add my-learning intro2 screen [KOBG-11]
minzee09 Aug 19, 2025
b5ed397
💄 UI: add icons [KOBG-11]
minzee09 Aug 19, 2025
3f66177
💄 UI: modify to align middle [KOBG-11]
minzee09 Aug 19, 2025
6ffbc2e
✨ feat: add step3 of my-learning screen [KOBG-11]
minzee09 Aug 19, 2025
d297075
✨ feat: add ending of my-learning screen [KOBG-11]
minzee09 Aug 19, 2025
f0e46a6
✨ feat: add level roadmap layout [KOBG-11]
minzee09 Aug 19, 2025
cccbdb9
💄 UI: add active icons [KOBG-11]
minzee09 Aug 19, 2025
2eef827
✨ feat: add text json files [KOBG-11]
minzee09 Aug 19, 2025
c24ba3b
✨ feat: update routes constants [KOBG-11]
minzee09 Aug 19, 2025
4ac1e41
✨ feat: 사용자 현재 레벨에 자동 스크롤 기능 추가 [KOBG-11]
minzee09 Aug 20, 2025
58cdf3c
✨ feat: 음성 녹음 기능 추가 [KOBG-11]
minzee09 Aug 20, 2025
596ed5e
💄 UI: add icons [KOBG-11]
minzee09 Aug 20, 2025
08a2ef7
📦 package: add deepgram [KOBG-11]
minzee09 Aug 20, 2025
9414e16
✨ feat: add POST api for STT [KOBG-11]
minzee09 Aug 20, 2025
d811b4c
💄 UI: add swipe btn component [KOBG-11]
minzee09 Aug 20, 2025
537f9f2
🐛 bug: 마이크 모드로 전환 안되고 바로 음성 녹음되는 오류 해결 [KOBG-11]
minzee09 Aug 20, 2025
beb4e63
💄 UI: modify margin style [KOBG-11]
minzee09 Aug 20, 2025
eebcd79
✨ feat: 레벤슈타인 거리 알고리즘 유틸 함수 추가 [KOBG-11]
minzee09 Aug 20, 2025
fb4fa2d
✨ feat: add conversation screen [KOBG-11]
minzee09 Aug 20, 2025
c455543
💄 UI: add mypage styling [KOBG-11]
minzee09 Aug 20, 2025
f839ce0
✨ feat: STT 호출 커스텀 훅 추가 [KOBG-11]
minzee09 Aug 20, 2025
ae8c870
✨ feat: update rating logic [KOBG-11]
minzee09 Aug 20, 2025
d0599e5
💄 UI: modify main layout styles [KOBG-11]
minzee09 Aug 20, 2025
34dbe2e
✨ feat: add voice recognition to step1 [KOBG-11]
minzee09 Aug 20, 2025
0bfe5c1
✨ feat: add chatting stage [KOGB-11]
minzee09 Aug 20, 2025
cb388f6
✏️ typo: remove lint errors [KOBG-11]
minzee09 Aug 20, 2025
36c8054
💬 comment: 타입 에러 예외 주석 추가 [KOBG-11]
minzee09 Aug 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions app/api/deepgram/transcribe/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { createClient } from '@deepgram/sdk';
import { NextRequest, NextResponse } from 'next/server';

export const POST = async (req: NextRequest) => {
try {
const deepgram = createClient(process.env.DEEPGRAM_API_KEY!);

// JSON으로 Base64 보내는 경우
const contentType = req.headers.get('content-type') || '';
let audioBuffer: Buffer | null = null;

if (contentType.includes('application/json')) {
const { audioBase64 } = await req.json();
if (!audioBase64) {
return NextResponse.json(
{ error: 'No audioBase64 provided' },
{ status: 400 },
);
}
audioBuffer = Buffer.from(audioBase64, 'base64');
}
// FormData로 보내는 경우
else if (contentType.includes('multipart/form-data')) {
const formData = await req.formData();
const audioFile = formData.get('file') as File;
if (!audioFile) {
return NextResponse.json(
{ error: 'No audio file provided' },
{ status: 400 },
);
}
const arrayBuffer = await audioFile.arrayBuffer();
audioBuffer = Buffer.from(arrayBuffer);
} else {
return NextResponse.json(
{ error: 'Unsupported Content-Type' },
{ status: 400 },
);
}

// Deepgram STT
const { result } = await deepgram.listen.prerecorded.transcribeFile(
audioBuffer,
{
model: 'nova-2',
language: 'ko',
smart_format: true,
},
);

// 안전하게 transcript 추출
const transcript =
result?.results?.channels?.[0]?.alternatives?.[0]?.transcript || '';

return NextResponse.json({ transcript });
} catch (err) {
console.error('STT Error:', err);
return NextResponse.json(
{ error: err instanceof Error ? err.message : 'Unknown error' },
{ status: 500 },
);
}
};
Binary file modified app/favicon.ico
Binary file not shown.
11 changes: 11 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

--background: #ffffff;
--foreground: #171717;

.scrollbar-hide::-webkit-scrollbar {
display: none;
}
}

@media (prefers-color-scheme: dark) {
Expand Down Expand Up @@ -171,6 +175,13 @@
.bg-gradient-reverse {
background-image: linear-gradient(to bottom, #E6F4FA, #FEFEFE);
}
/* 스크롤바 숨기기 */
.scrollbar-hide {
&::-webkit-scrollbar {
display: none;
}
scrollbar-width: none;
}
}

body {
Expand Down
62 changes: 62 additions & 0 deletions app/main/_components/NavigationBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use client';

import Image from 'next/image';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { ROUTES } from '@/constants/routes';

export default function NavigationBar() {
const pathname = usePathname();

const navItems = [
{
label: '나의 학습',
href: ROUTES.MAIN.MY_LEARNING.ROOT,
icon: '/icons/nav-book.svg',
activeIcon: '/icons/nav-book-blue.svg', // active용 아이콘
},
{
label: '대화',
href: ROUTES.MAIN.CONVERSATION.ROOT,
icon: '/icons/nav-phone.svg',
activeIcon: '/icons/nav-phone-blue.svg',
},
{
label: '마이페이지',
href: ROUTES.MAIN.MY_PAGE,
icon: '/icons/nav-user.svg',
activeIcon: '/icons/nav-user-blue.svg',
},
];

return (
<nav className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-900 flex justify-around items-center h-18 z-999">
{navItems.map(item => {
const isActive = pathname === item.href;
return (
<Link
key={item.href}
href={item.href}
className="flex flex-col items-center justify-center gap-2"
>
<Image
src={isActive ? item.activeIcon : item.icon}
alt={item.label}
width={24}
height={24}
/>
<span
className={
isActive
? 'text-secondary-400 text-cp2-bold'
: 'text-gray-500 text-cp2-regular'
}
>
{item.label}
</span>
</Link>
);
})}
</nav>
);
}
106 changes: 106 additions & 0 deletions app/main/conversation/_components/SwipeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
'use client';

import { ROUTES } from '@/constants/routes';
import {
motion,
useAnimation,
useMotionValue,
useMotionValueEvent,
} from 'framer-motion';
import Image from 'next/image';
import { useEffect, useRef, useState } from 'react';
import { usePathname } from 'next/navigation';

export default function SwipeButton() {
const pathname = usePathname();

const trackRef = useRef<HTMLDivElement>(null);
const knobRef = useRef<HTMLDivElement>(null);

const controls = useAnimation();
const x = useMotionValue(0);

const [maxX, setMaxX] = useState(180); // fallback
const [completed, setCompleted] = useState(false);

// 트랙과 노란원 실제 너비로 최대 이동거리 계산
useEffect(() => {
const measure = () => {
const trackW = trackRef.current?.offsetWidth ?? 244;
const knobW = knobRef.current?.offsetWidth ?? 56; // w-14 = 56px
setMaxX(trackW - knobW);
};
measure();
window.addEventListener('resize', measure);
return () => window.removeEventListener('resize', measure);
}, []);

// 라우트 바뀔 때/초기 마운트 때 항상 리셋
useEffect(() => {
setCompleted(false);
x.set(0);
controls.set({ x: 0 });
}, [pathname, controls, x]);

// bfcache로 뒤로가기 복귀 시에도 리셋
useEffect(() => {
const onPageShow = () => {
setCompleted(false);
x.set(0);
controls.set({ x: 0 });
};
window.addEventListener('pageshow', onPageShow);
return () => window.removeEventListener('pageshow', onPageShow);
}, [controls, x]);

// x 변화 구독: 끝에 '도착'하면 트리거
useMotionValueEvent(x, 'change', latest => {
if (!completed && latest >= maxX - 1) {
setCompleted(true);
window.location.href = ROUTES.MAIN.CONVERSATION.CALLING;
}
});

return (
<div className="flex items-center justify-center">
<div className="relative w-[244px]">
{/* 회색 트랙 */}
<div
ref={trackRef}
className="h-[43px] rounded-full bg-gray-950 flex items-center"
>
<span className="ml-16 text-gray-500 text-bd2-regular">
밀어서 시작하기 →
</span>
</div>

{/* 노란 원 아이콘 */}
<motion.div
ref={knobRef}
drag="x"
style={{ x }}
dragConstraints={{ left: 0, right: maxX }}
dragElastic={0.1}
onDragEnd={() => {
if (!completed) {
// 끝에 못 닿았으면 '툭!' 하고 복귀
controls.start({
x: 0,
transition: { duration: 0.15, ease: 'easeOut' },
});
}
}}
animate={controls}
className="absolute top-1/2 -translate-y-1/2 w-14 h-14 rounded-full bg-primary flex items-center justify-center cursor-pointer"
>
<Image
src="/icons/nav-phone-blue.svg"
alt="phone"
width={24}
height={24}
/>
</motion.div>
</div>
</div>
);
}
35 changes: 35 additions & 0 deletions app/main/conversation/_locales/text.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"conversation": {
"title": "코디에게 전화를 걸어\n대화할까요?",
"subText": {
"vt": "Chọn một ngôn ngữ!\nChúng tôi sẽ sử dụng nó để dịch.",
"en": "Lets call Kody and have a conversation!",
"jp": "言語を選択してください!\n翻訳に使用します。",
"chn": "选择一种语言!\n我们将用它进行翻译。"
},
"phrase1": {
"id": 1,
"kor": "너는 취미가 뭐야?",
"vt": "Không có ngôn ngữ mà tôi muốn.",
"en": "What is your hobby?",
"jp": "私の欲しい言語がありません。",
"chn": "没有我想要的语言。"
},
"phrase2": {
"id": 2,
"kor": "너는 이름이 뭐야?",
"vt": "Không có ngôn ngữ mà tôi muốn.",
"en": "What is your name?",
"jp": "あなたの名前は何ですか?",
"chn": "你叫什么名字?"
},
"phrase3": {
"id": 3,
"kor": "가장 좋아하는게 뭐야?",
"vt": "Không có ngôn ngữ mà tôi muốn.",
"en": "What is your favorite thing?",
"jp": "あなたの名前は何ですか?",
"chn": "你叫什么名字?"
}
}
}
12 changes: 12 additions & 0 deletions app/main/conversation/calling/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ReactNode } from 'react';

export default function CallingLayout({ children }: { children: ReactNode }) {
return (
<div
className="flex flex-col w-svw h-svh -mx-4 px-4 bg-center bg-cover"
style={{ backgroundImage: "url('/character/bg.webp')" }}
>
<main className="flex flex-col h-full w-full">{children}</main>
</div>
);
}
Loading