|
|
// frontend/components/automation/NewView.tsx — auto.jsx NewView 이식 (자연어 → 규칙)
|
|
|
"use client";
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
|
import { Icon } from "@/components/Icon";
|
|
|
import type { ParsePreview } from "@/lib/types";
|
|
|
import { AU_CAT, AU_TONE, Flow } from "./Flow";
|
|
|
|
|
|
// 원본 auto-data.js examples 입력 문장(고정 골든 — 칩 표시·정확 일치 트리거)
|
|
|
const EXAMPLE_TEXTS = [
|
|
|
"출장 전날엔 저녁 일정 비워줘",
|
|
|
"뉴스레터는 모아서 저녁에 보여줘",
|
|
|
"운동을 3일 거르면 산책 잡아줘",
|
|
|
];
|
|
|
|
|
|
export function NewView({
|
|
|
parse,
|
|
|
onCreate,
|
|
|
}: {
|
|
|
parse: (text: string) => Promise<ParsePreview>;
|
|
|
onCreate: (p: ParsePreview) => void;
|
|
|
}) {
|
|
|
const [text, setText] = useState("");
|
|
|
const [preview, setPreview] = useState<ParsePreview | null>(null);
|
|
|
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
|
|
useEffect(() => {
|
|
|
const t = text.trim();
|
|
|
if (!t) {
|
|
|
setPreview(null);
|
|
|
return;
|
|
|
}
|
|
|
if (timer.current) clearTimeout(timer.current);
|
|
|
timer.current = setTimeout(async () => {
|
|
|
try {
|
|
|
const pr = await parse(t);
|
|
|
setPreview(pr.matched ? pr : null);
|
|
|
} catch {
|
|
|
setPreview(null);
|
|
|
}
|
|
|
}, 300);
|
|
|
return () => {
|
|
|
if (timer.current) clearTimeout(timer.current);
|
|
|
};
|
|
|
}, [text, parse]);
|
|
|
|
|
|
const match = preview?.matched ? preview : null;
|
|
|
|
|
|
return (
|
|
|
<div className="board">
|
|
|
<section className="card sp2 hero">
|
|
|
<span className="hero-spark">
|
|
|
<Icon name="zap" />새 자동화 만들기
|
|
|
</span>
|
|
|
<p className="brief">
|
|
|
하고 싶은 일을 <b>한 문장</b>으로 적으세요. 트리거·조건·동작은 아리가 찾아요.
|
|
|
</p>
|
|
|
<div className="cmd">
|
|
|
<Icon name="pen" />
|
|
|
<input
|
|
|
value={text}
|
|
|
onChange={(e) => setText(e.target.value)}
|
|
|
onKeyDown={(e) => {
|
|
|
if (e.key === "Enter" && match) onCreate(match);
|
|
|
}}
|
|
|
placeholder="예: 출장 전날엔 저녁 일정 비워줘"
|
|
|
aria-label="새 규칙 문장"
|
|
|
autoFocus
|
|
|
/>
|
|
|
<button
|
|
|
className="send"
|
|
|
disabled={!match}
|
|
|
onClick={() => match && onCreate(match)}
|
|
|
aria-label="규칙 만들기"
|
|
|
>
|
|
|
<Icon name="arrow" />
|
|
|
</button>
|
|
|
</div>
|
|
|
<div className="chips">
|
|
|
{EXAMPLE_TEXTS.map((t) => (
|
|
|
<button key={t} className="chip" onClick={() => setText(t)}>
|
|
|
<Icon name="spark" />
|
|
|
{t}
|
|
|
</button>
|
|
|
))}
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
{/* 미리보기 */}
|
|
|
<section className="card sp2">
|
|
|
<div className="ch">
|
|
|
<div className="ico">
|
|
|
<Icon name="spark" />
|
|
|
</div>
|
|
|
<div className="htext">
|
|
|
<h3>미리보기</h3>
|
|
|
<div className="sub">
|
|
|
{match ? "아리가 이렇게 이해했어요" : "문장을 적으면 여기에 규칙이 그려져요"}
|
|
|
</div>
|
|
|
</div>
|
|
|
{match && (
|
|
|
<span className="rl-cat" style={{ background: AU_TONE[match.cat] }}>
|
|
|
{AU_CAT[match.cat]}
|
|
|
</span>
|
|
|
)}
|
|
|
</div>
|
|
|
{match ? (
|
|
|
<>
|
|
|
<div className="nv-name">{match.name}</div>
|
|
|
<Flow p={match.parse} />
|
|
|
<button className="rb-make-btn" onClick={() => onCreate(match)}>
|
|
|
<Icon name="plus" />이 규칙 만들기
|
|
|
</button>
|
|
|
<div className="nv-hint">
|
|
|
<Icon name="shield" />만든 규칙은 ‘내 규칙’에서 언제든 끄거나 지울 수 있어요.
|
|
|
</div>
|
|
|
</>
|
|
|
) : (
|
|
|
<div className="nv-empty">
|
|
|
<span className="nv-empty-ic">
|
|
|
<Icon name="zap" />
|
|
|
</span>
|
|
|
<p>
|
|
|
예시 문장을 누르거나 직접 적어보세요. 아리가 <b>트리거 → 조건 → 동작</b>으로 나눠
|
|
|
보여드려요.
|
|
|
</p>
|
|
|
</div>
|
|
|
)}
|
|
|
</section>
|
|
|
</div>
|
|
|
);
|
|
|
}
|