Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
111 changes: 111 additions & 0 deletions src/app/agent/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
'use client';

import { useState, useEffect } from 'react';
import Image from 'next/image';
import { colors } from '@/styles/colors';
import * as S from './style';

const USER_MESSAGE = "μ΅œμ• μ˜ 사인 ν”„λ‘œμ νŠΈμ˜ CPU μ‚¬μš©λŸ‰μ„ μ•Œκ³  μ‹Άμ–΄";
const AI_MESSAGE = "μ’‹μŠ΅λ‹ˆλ‹€, λ‹€μŒμ€ μ΅œμ• μ˜ 사인 ν”„λ‘œμ νŠΈμ˜ CPU μ‚¬μš©λŸ‰ μž…λ‹ˆλ‹€.";

export default function AgentPage() {
const [isChatStarted, setIsChatStarted] = useState(false);
const [streamedText, setStreamedText] = useState("");
const [inputValue, setInputValue] = useState("");

useEffect(() => {
if (isChatStarted) {
let currentIndex = 0;
const interval = setInterval(() => {
if (currentIndex <= AI_MESSAGE.length) {
setStreamedText(AI_MESSAGE.slice(0, currentIndex));
currentIndex++;
} else {
clearInterval(interval);
}
}, 50);

return () => clearInterval(interval);
}
}, [isChatStarted]);

const handleSearch = (e?: React.FormEvent) => {
e?.preventDefault();
if (!isChatStarted) {
setIsChatStarted(true);
}
setInputValue("");
};

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
handleSearch();
}
};

return (
<S.Container isChat={isChatStarted}>
{!isChatStarted && (
<S.LogoSection>
<Image
src="/assets/logo.svg"
alt="M-ADP Logo"
width={100}
height={92}
priority
/>
<S.LogoTitle>M-ADP</S.LogoTitle>
</S.LogoSection>
)}

{isChatStarted && (
<S.ChatArea>
<S.MessageRow>
<S.Avatar color={colors.primary.default}>
N
</S.Avatar>
<S.UserMessageCard>
{USER_MESSAGE}
</S.UserMessageCard>
</S.MessageRow>

<S.MessageRow>
<S.Avatar>
<Image
src="/assets/logo.svg"
alt="AI Avatar"
width={24}
height={24}
/>
</S.Avatar>
<S.AIMessageCard>
{streamedText}
</S.AIMessageCard>
</S.MessageRow>
</S.ChatArea>
)}
Comment on lines 64 to 69
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Chat always displays the same hardcoded user message regardless of actual input.

When chat starts, USER_MESSAGE (the hardcoded constant) is rendered rather than inputValue at the time of submission. The user's actual typed text is discarded. If this is intentional for the demo, consider adding a brief comment; otherwise, capture the input before clearing it:

+const [userMessage, setUserMessage] = useState("");
 ...
 const handleSearch = (e?: React.FormEvent) => {
   e?.preventDefault();
+  if (!inputValue.trim()) return;
+  setUserMessage(inputValue);
   if (!isChatStarted) {
     setIsChatStarted(true);
   }
   setInputValue("");
 };

Then render {userMessage} instead of {USER_MESSAGE} in the UserMessageCard.

πŸ€– Prompt for AI Agents
In `@src/app/agent/page.tsx` around lines 61 - 86, The user message is currently
hardcoded using USER_MESSAGE inside the UI (rendered in S.UserMessageCard) so
typed inputValue is never shown; update the submit flow to capture the
inputValue into a state/local variable (e.g., userMessage) before you clear
inputValue in the submit handler, then render that state (userMessage) in
S.UserMessageCard instead of USER_MESSAGE; ensure the change is applied where
isChatStarted controls rendering so the submitted text is preserved for the chat
view.


<S.SearchContainer>
<S.InputWrapper>
<S.IconCircle>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 4L14.4 9.6L20 12L14.4 14.4L12 20L9.6 14.4L4 12L9.6 9.6L12 4Z" fill="#969696" />
</svg>
</S.IconCircle>
<S.SearchInput
placeholder="무엇을 μ›ν•˜μ‹œλ‚˜μš”?"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
/>
</S.InputWrapper>

<S.IconCircle className="send-button" onClick={handleSearch}>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 19V5M12 5L5 12M12 5L19 12" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</S.IconCircle>
</S.SearchContainer>
</S.Container>
);
}
159 changes: 159 additions & 0 deletions src/app/agent/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import styled from '@emotion/styled';
import { colors } from '@/styles/colors';
import { typography } from '@/styles/typography';

export const Container = styled.div<{ isChat?: boolean }>`
display: flex;
flex-direction: column;
align-items: center;
justify-content: ${({ isChat }) => (isChat ? 'space-between' : 'center')};
width: 100%;
height: 100%;
background-color: #ffffff;
padding: ${({ isChat }) => (isChat ? '40px 0' : '0')};
box-sizing: border-box;
`;
Comment on lines +4 to +14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check the current state of style.ts
cat -n src/app/agent/style.ts

Repository: M-ADP/M-ADP-CLIENT

Length of output: 1065


🏁 Script executed:

# Check how Container is used in page.tsx
rg -n 'isChat' --type=ts --type=tsx -C 3

Repository: M-ADP/M-ADP-CLIENT

Length of output: 89


🏁 Script executed:

# Also check for any imports or usage of Container component
rg -n 'Container' src/app/agent/ --type=ts --type=tsx -C 2

Repository: M-ADP/M-ADP-CLIENT

Length of output: 89


🏁 Script executed:

# Search for isChat usage more broadly
rg 'isChat' --type-add 'typescripts:*.ts' --type-add 'typescripts:*.tsx'

Repository: M-ADP/M-ADP-CLIENT

Length of output: 713


🏁 Script executed:

# Look at the agent page.tsx file directly
find . -name 'page.tsx' -path '*/agent/*' -type f

Repository: M-ADP/M-ADP-CLIENT

Length of output: 86


🏁 Script executed:

# Search for Container usage in agent directory
rg 'Container' src/app/agent/ -A 2 -B 2

Repository: M-ADP/M-ADP-CLIENT

Length of output: 734


Use transient prop syntax for isChat to prevent React warnings about unknown DOM attributes.

Emotion's styled passes non-transient props to underlying DOM elements. Since isChat is not a valid HTML attribute, prefix it with $ to mark it as transient (consumed only by the styled component, not forwarded to the DOM).

Required changes
-export const Container = styled.div<{ isChat?: boolean }>`
+export const Container = styled.div<{ $isChat?: boolean }>`
   display: flex;
   flex-direction: column;
   align-items: center;
-  justify-content: ${({ isChat }) => (isChat ? 'space-between' : 'center')};
+  justify-content: ${({ $isChat }) => ($isChat ? 'space-between' : 'center')};
   width: 100%;
   height: 100%;
   background-color: `#ffffff`;
-  padding: ${({ isChat }) => (isChat ? '40px 0' : '0')};
+  padding: ${({ $isChat }) => ($isChat ? '40px 0' : '0')};
   box-sizing: border-box;
 `;

Also update src/app/agent/page.tsx to pass $isChat instead of isChat:

-<S.Container isChat={isChatStarted}>
+<S.Container $isChat={isChatStarted}>
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const Container = styled.div<{ isChat?: boolean }>`
display: flex;
flex-direction: column;
align-items: center;
justify-content: ${({ isChat }) => (isChat ? 'space-between' : 'center')};
width: 100%;
height: 100%;
background-color: #ffffff;
padding: ${({ isChat }) => (isChat ? '40px 0' : '0')};
box-sizing: border-box;
`;
export const Container = styled.div<{ $isChat?: boolean }>`
display: flex;
flex-direction: column;
align-items: center;
justify-content: ${({ $isChat }) => ($isChat ? 'space-between' : 'center')};
width: 100%;
height: 100%;
background-color: `#ffffff`;
padding: ${({ $isChat }) => ($isChat ? '40px 0' : '0')};
box-sizing: border-box;
`;
πŸ€– Prompt for AI Agents
In `@src/app/agent/style.ts` around lines 4 - 14, Change the styled Container to
use a transient prop so the boolean does not get forwarded to the DOM: update
the generic prop name from isChat to $isChat (e.g., Container = styled.div<{
$isChat?: boolean }>`...`) and update the template usages to reference ${({
$isChat }) => ...} for justify-content and padding; then update the usage in
src/app/agent/page.tsx to pass $isChat instead of isChat when rendering
<Container>.


export const LogoSection = styled.div`
display: flex;
align-items: center;
gap: 28px;
margin-bottom: 60px;
`;

export const LogoTitle = styled.span`
font-family: 'IBM Plex Sans KR', sans-serif;
font-weight: 700;
font-size: 80px;
line-height: normal;
color: ${colors.black[300]};
margin: 0;
`;

export const SearchContainer = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: 800px;
height: 52px;
padding: 0 20px;
background: #ffffff;
border: 1px solid ${colors.black[50]};
border-radius: 26px;
box-sizing: border-box;
transition: border-color 0.2s ease;
margin-bottom: 20px;

&:focus-within {
border-color: ${colors.primary.default};
}
`;

export const InputWrapper = styled.div`
display: flex;
align-items: center;
gap: 12px;
flex: 1;
`;

export const IconCircle = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;

&.send-button {
background: ${colors.primary.default};
cursor: pointer;
border: 1px solid ${colors.primary.default};
}
`;

export const SearchInput = styled.input`
border: none;
background: transparent;
width: 100%;
padding: 0;
font-family: ${typography.text16Medium.fontFamily};
font-size: ${typography.text16Medium.fontSize};
font-weight: ${typography.text16Medium.fontWeight};
line-height: ${typography.text16Medium.lineHeight};
color: ${colors.black[75]};
outline: none;

&::placeholder {
color: ${colors.black[75]};
}
`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Placeholder and input text share the same color β€” users can't tell them apart.

Both the input value (line 86) and ::placeholder (line 90) resolve to colors.black[75]. There's no visual cue to indicate whether the field is empty or already has typed content. Use a lighter shade (e.g., colors.black[50]) for the placeholder, or a darker shade for the input text.

Proposed fix
   &::placeholder {
-    color: ${colors.black[75]};
+    color: ${colors.black[50]};
   }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const SearchInput = styled.input`
border: none;
background: transparent;
width: 100%;
padding: 0;
font-family: ${typography.text16Medium.fontFamily};
font-size: ${typography.text16Medium.fontSize};
font-weight: ${typography.text16Medium.fontWeight};
line-height: ${typography.text16Medium.lineHeight};
color: ${colors.black[75]};
outline: none;
&::placeholder {
color: ${colors.black[75]};
}
`;
export const SearchInput = styled.input`
border: none;
background: transparent;
width: 100%;
padding: 0;
font-family: ${typography.text16Medium.fontFamily};
font-size: ${typography.text16Medium.fontSize};
font-weight: ${typography.text16Medium.fontWeight};
line-height: ${typography.text16Medium.lineHeight};
color: ${colors.black[75]};
outline: none;
&::placeholder {
color: ${colors.black[50]};
}
`;
πŸ€– Prompt for AI Agents
In `@src/app/agent/style.ts` around lines 77 - 92, The SearchInput styled
component currently uses the same color for the input text and the ::placeholder
(both colors.black[75]); change the placeholder to a lighter shade so users can
distinguish empty state from typed text β€” update the ::placeholder color in the
SearchInput definition to a lighter token (e.g., colors.black[50]) or
alternatively make the main input color darker, ensuring the placeholder uses a
different color than the input text.


export const ChatArea = styled.div`
display: flex;
flex-direction: column;
width: 100%;
max-width: 1240px;
height: 100%;
overflow-y: auto;
padding: 20px;
gap: 20px;
flex: 1;
`;

export const MessageRow = styled.div`
display: flex;
gap: 20px;
align-items: flex-start;
width: 100%;
justify-content: center;
`;

export const Avatar = styled.div<{ color?: string }>`
width: 40px;
height: 40px;
border-radius: 50%;
background-color: ${({ color }) => color || '#e2e8f0'};
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
font-family: ${typography.text16Regular.fontFamily};
font-size: ${typography.text16Regular.fontSize};
font-weight: ${typography.text16Regular.fontWeight};
line-height: ${typography.text16Regular.lineHeight};
color: #ffffff;
`;

export const UserMessageCard = styled.div`
border: 1px solid #e2e8f0;
border-radius: 14px;
padding: 15px 21px;
width: 800px;
box-sizing: border-box;
background-color: #ffffff;
color: ${colors.primary.default};
font-family: ${typography.text18Regular.fontFamily};
font-size: ${typography.text18Regular.fontSize};
font-weight: ${typography.text18Regular.fontWeight};
line-height: ${typography.text18Regular.lineHeight};
`;

export const AIMessageCard = styled.div`
background: #ffffff;
border-radius: 14px;
padding: 22px;
width: 800px;
box-sizing: border-box;
box-shadow: 14px 27px 45px 4px rgba(112, 144, 176, 0.2);
color: #1b2559;
font-family: ${typography.text18Regular.fontFamily};
font-size: ${typography.text18Regular.fontSize};
font-weight: ${typography.text18Regular.fontWeight};
line-height: ${typography.text18Regular.lineHeight};
white-space: pre-wrap;
min-height: 100px;
`;