aura-ai
Version:
AI-powered marketing strategist CLI tool for developers
446 lines (445 loc) • 17 kB
JavaScript
#!/usr/bin/env -S node --no-warnings
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
import { useState, useRef, useEffect } from "react";
import { useInput, Box, Text, Spacer } from "ink";
import "@inkjs/ui";
import TextInput from "ink-text-input";
import SelectInput from "ink-select-input";
import fs from "fs/promises";
import path, { dirname } from "path";
import { fileURLToPath } from "url";
const CompositeInput = ({
options = [],
onSubmit,
placeholder = "Type your answer...",
initialValue = "",
onBack = null,
isReview = false,
onPrevious = null,
showTypeYourAnswer = true,
directSubmitValues = []
}) => {
const [mode, setMode] = useState(options.length > 0 ? "select" : "text");
const [inputValue, setInputValue] = useState("");
const [exitPrompt, setExitPrompt] = useState(false);
const lastCtrlC = useRef(0);
useInput((input, key) => {
if (key.ctrl && input === "c") {
const now = Date.now();
if (now - lastCtrlC.current < 2e3) {
if (onBack) onBack();
} else {
setExitPrompt(true);
lastCtrlC.current = now;
setTimeout(() => {
setExitPrompt(false);
}, 2e3);
}
}
});
useEffect(() => {
setMode(options.length > 0 ? "select" : "text");
setInputValue(initialValue);
setExitPrompt(false);
}, [options, initialValue]);
const selectItems = options.length > 0 ? [
...options.map((opt) => ({
label: opt.label,
value: opt.value
})),
...showTypeYourAnswer ? [
{
label: "Type your answer...",
value: "__custom_input__"
}
] : []
] : [];
const handleSelect = (item) => {
if (item.value === "__custom_input__") {
setMode("text");
setInputValue("");
} else if (directSubmitValues.includes(item.value)) {
onSubmit(item.value);
} else {
setMode("text");
setInputValue(item.label);
}
};
const handleTextSubmit = (value) => {
if (value.trim()) {
if (value.toLowerCase() === "/back" && !isReview && onPrevious) {
onPrevious();
return;
}
onSubmit(value);
}
};
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
/* @__PURE__ */ jsx(Box, { flexDirection: "column", children: mode === "select" && options.length > 0 ? /* @__PURE__ */ jsx(
Box,
{
paddingX: 1,
paddingY: 0,
borderColor: "whiteBright",
borderStyle: "round",
width: "100%",
children: /* @__PURE__ */ jsx(SelectInput, { items: selectItems, onSelect: handleSelect })
}
) : /* @__PURE__ */ jsxs(
Box,
{
paddingX: 1,
paddingY: 0,
borderColor: "whiteBright",
borderStyle: "round",
width: "100%",
children: [
/* @__PURE__ */ jsx(Text, { color: "blue", children: "> " }),
/* @__PURE__ */ jsx(
TextInput,
{
value: inputValue,
onChange: setInputValue,
onSubmit: handleTextSubmit,
placeholder,
focus: true
}
)
]
}
) }),
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, fontSize: 12, children: mode === "select" ? /* @__PURE__ */ jsx(Fragment, { children: "Use ↑↓ to navigate, Enter to select" }) : isReview ? /* @__PURE__ */ jsx(Fragment, { children: "Use /1, /2, etc. to jump to specific questions" }) : /* @__PURE__ */ jsx(Fragment, { children: "Use /back to go to previous question" }) }) }),
exitPrompt && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "Press Ctrl+C again to return to main menu" }) })
] });
};
const EmojiSpinner = ({
label = "",
emojis = ["💬", "💭"],
interval = 500
}) => {
const [currentEmojiIndex, setCurrentEmojiIndex] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCurrentEmojiIndex((prev) => (prev + 1) % emojis.length);
}, interval);
return () => clearInterval(timer);
}, [emojis, interval]);
return /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
/* @__PURE__ */ jsx(Text, { children: emojis[currentEmojiIndex] }),
label && /* @__PURE__ */ jsx(Text, { children: label })
] });
};
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const InitPage = ({ onBack }) => {
const [questions, setQuestions] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const [answers, setAnswers] = useState([]);
const [showConfirmSubmit, setShowConfirmSubmit] = useState(false);
const [showModifyInput, setShowModifyInput] = useState(false);
const [modifyInputValue, setModifyInputValue] = useState("");
const [editingQuestionIndex, setEditingQuestionIndex] = useState(-1);
const [isProcessing, setIsProcessing] = useState(false);
useEffect(() => {
const loadQuestions = async () => {
try {
const possiblePaths = [
path.join(__dirname, "..", "src", "init", "generated", "questions.json"),
// From dist to src/init
path.join(path.dirname(__dirname), "src", "init", "generated", "questions.json"),
// Alternative dist path
path.resolve("./src/init/generated/questions.json"),
// Absolute from project root
"/Users/leo/Documents/GitHub/rina/src/init/generated/questions.json"
// Fallback absolute path
];
let data = null;
for (const jsonPath of possiblePaths) {
try {
data = await fs.readFile(jsonPath, "utf-8");
break;
} catch (err) {
}
}
if (!data) {
throw new Error("Questions file not found in any expected location");
}
const parsed = JSON.parse(data);
setQuestions(parsed);
setLoading(false);
} catch (err) {
setError(`Failed to load questions: ${err.message}`);
setLoading(false);
}
};
loadQuestions();
}, []);
const handlePrevious = () => {
if (currentQuestionIndex > 0) {
setCurrentQuestionIndex((prev) => prev - 1);
setEditingQuestionIndex(-1);
}
};
const handleCompositeSubmit = (answer) => {
if (answer.startsWith("/")) {
const questionNum = parseInt(answer.substring(1));
if (questionNum >= 1 && questionNum <= questions.totalQuestions) {
setCurrentQuestionIndex(questionNum - 1);
setEditingQuestionIndex(questionNum - 1);
return;
}
}
setIsProcessing(true);
const wasReAnswering = answers.some(
(ans) => ans.question === questions.questions[currentQuestionIndex].question
);
setTimeout(() => {
setAnswers((prev) => {
const newAnswers = [...prev];
const existingIndex = newAnswers.findIndex(
(ans) => ans.question === questions.questions[currentQuestionIndex].question
);
if (existingIndex >= 0) {
newAnswers[existingIndex] = {
question: questions.questions[currentQuestionIndex].question,
answer
};
} else {
newAnswers.push({
question: questions.questions[currentQuestionIndex].question,
answer
});
}
return newAnswers;
});
setEditingQuestionIndex(-1);
setIsProcessing(false);
if (wasReAnswering) {
const answeredQuestions = new Set(answers.map((ans) => ans.question));
answeredQuestions.add(questions.questions[currentQuestionIndex].question);
for (let i = 0; i < questions.questions.length; i++) {
if (!answeredQuestions.has(questions.questions[i].question)) {
setCurrentQuestionIndex(i);
return;
}
}
setShowConfirmSubmit(true);
} else {
if (currentQuestionIndex < questions.questions.length - 1) {
setCurrentQuestionIndex((prev) => prev + 1);
} else {
setShowConfirmSubmit(true);
}
}
}, 2e3);
};
const handleConfirmSubmit = (confirmed) => {
{
if (onBack) onBack();
}
};
const handleModifyInput = (value) => {
if (value.trim()) {
if (value.startsWith("/")) {
const questionNum2 = parseInt(value.substring(1));
if (questionNum2 >= 1 && questionNum2 <= questions.totalQuestions) {
setCurrentQuestionIndex(questionNum2 - 1);
setEditingQuestionIndex(questionNum2 - 1);
setShowModifyInput(false);
return;
}
}
const questionNum = parseInt(value);
if (questionNum >= 1 && questionNum <= questions.totalQuestions) {
setCurrentQuestionIndex(questionNum - 1);
setEditingQuestionIndex(questionNum - 1);
setShowModifyInput(false);
}
}
};
if (loading) {
return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "Loading questions..." }) });
}
if (error) {
return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: error }) });
}
if (showConfirmSubmit) {
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
/* @__PURE__ */ jsx(Box, { marginBottom: 2, children: /* @__PURE__ */ jsx(Text, { color: "green", bold: true, children: "🎉 All questions completed!" }) }),
/* @__PURE__ */ jsxs(
Box,
{
paddingX: 2,
paddingY: 1,
borderColor: "white",
borderStyle: "round",
marginBottom: 2,
flexDirection: "column",
children: [
/* @__PURE__ */ jsx(Text, { color: "yellow", children: "Your answers:" }),
answers.map((ans, idx) => {
const questionIndex = questions.questions.findIndex((q) => q.question === ans.question);
const isEditing = editingQuestionIndex === questionIndex;
return /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
/* @__PURE__ */ jsxs(Text, { color: "white", children: [
"Q",
idx + 1,
": ",
ans.question
] }),
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
"A",
idx + 1,
": ",
isEditing ? "[editing]" : ans.answer
] })
] }, idx);
})
]
}
),
isProcessing && /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(EmojiSpinner, { label: "Aura is thinking..." }) }),
/* @__PURE__ */ jsxs(Box, { columnGap: 1, marginBottom: 2, children: [
/* @__PURE__ */ jsx(Text, { children: "🌀" }),
/* @__PURE__ */ jsx(Text, { bold: true, children: "Ready to submit?" })
] }),
/* @__PURE__ */ jsx(
CompositeInput,
{
options: [
{ label: "Yes,submit for deep analysis", value: "submit" },
{ label: "No, modify answers...", value: "modify" }
],
onSubmit: (value) => {
setIsProcessing(true);
setTimeout(() => {
if (value === "submit") {
handleConfirmSubmit();
} else if (value === "modify") {
setShowConfirmSubmit(false);
setShowModifyInput(true);
setModifyInputValue("");
}
setIsProcessing(false);
}, 2e3);
},
onBack,
isReview: true,
showTypeYourAnswer: false,
directSubmitValues: ["submit", "modify"]
}
)
] });
}
if (showModifyInput) {
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
answers.length > 0 && /* @__PURE__ */ jsxs(Box, { marginBottom: 2, flexDirection: "column", children: [
/* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: "Your answers:" }),
answers.map((ans, idx) => {
const questionIndex = questions.questions.findIndex((q) => q.question === ans.question);
const isEditing = editingQuestionIndex === questionIndex;
return /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
/* @__PURE__ */ jsxs(Text, { color: "white", children: [
"Q",
idx + 1,
": ",
ans.question
] }),
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
"A",
idx + 1,
": ",
isEditing ? "[editing]" : ans.answer
] })
] }, idx);
})
] }),
/* @__PURE__ */ jsxs(Box, { columnGap: 1, marginBottom: 2, children: [
/* @__PURE__ */ jsx(Text, { children: "🌀" }),
/* @__PURE__ */ jsx(Text, { bold: true, children: "Which question do you want to modify?" })
] }),
/* @__PURE__ */ jsx(
CompositeInput,
{
options: [],
onSubmit: handleModifyInput,
placeholder: "/1, /2, etc. to jump to specific questions",
initialValue: modifyInputValue,
onBack,
isReview: true,
showTypeYourAnswer: false
}
)
] });
}
if (questions && questions.questions[currentQuestionIndex]) {
const currentQuestion = questions.questions[currentQuestionIndex];
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
/* @__PURE__ */ jsxs(Box, { marginBottom: 1, flexDirection: "row", children: [
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { bold: true, children: [
"📝 Question ",
currentQuestionIndex + 1,
" of ",
questions.totalQuestions
] }) }),
/* @__PURE__ */ jsx(Spacer, {}),
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Box, { marginBottom: 1, marginRight: 2, children: (() => {
{
const completed = currentQuestionIndex + 1;
const remaining = questions.totalQuestions - completed;
return /* @__PURE__ */ jsxs(Text, { children: [
/* @__PURE__ */ jsx(Text, { color: "white", children: "🐤".repeat(completed) }),
/* @__PURE__ */ jsx(Text, { color: "white", children: "🥚".repeat(remaining) })
] });
}
})() }) })
] }),
answers.length > 0 && answers.length === questions.totalQuestions && /* @__PURE__ */ jsx(Box, { marginBottom: 2, flexDirection: "column", width: "100%", children: answers.map((ans, idx) => {
const questionIndex = questions.questions.findIndex((q) => q.question === ans.question);
const isEditing = editingQuestionIndex === questionIndex;
return /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
/* @__PURE__ */ jsxs(Text, { color: "white", children: [
"Q",
idx + 1,
": ",
ans.question
] }),
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
"A",
idx + 1,
": ",
isEditing ? "[editing]" : ans.answer
] })
] }, idx);
}) }),
/* @__PURE__ */ jsxs(Box, { columnGap: 1, marginBottom: 2, children: [
/* @__PURE__ */ jsx(Text, { children: "🌀" }),
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", rowGap: 1, children: [
/* @__PURE__ */ jsx(Text, { bold: true, children: currentQuestion.question }),
currentQuestion.explainer && /* @__PURE__ */ jsx(Text, { marginLeft: 2, dimColor: true, fontSize: 12, children: currentQuestion.explainer })
] })
] }),
isProcessing && /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(EmojiSpinner, { label: "Processing your answer..." }) }),
/* @__PURE__ */ jsx(
CompositeInput,
{
options: currentQuestion.options || [],
onSubmit: handleCompositeSubmit,
placeholder: "Type your answer...",
initialValue: editingQuestionIndex === currentQuestionIndex ? answers.find((ans) => ans.question === currentQuestion.question)?.answer || "" : "",
onBack,
onPrevious: handlePrevious,
isReview: answers.length === questions.totalQuestions
}
)
] });
}
return /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: "No questions available" }) });
};
export {
EmojiSpinner as E,
InitPage as I
};
//# sourceMappingURL=page-CHKhZAAX.js.map