feat:generate article form
This commit is contained in:
parent
6931edf52d
commit
922ddcc828
|
|
@ -0,0 +1,10 @@
|
||||||
|
import GenerateArticleForm from "@/components/form/article/generate-article-form";
|
||||||
|
import { Card } from "@nextui-org/react";
|
||||||
|
|
||||||
|
export default function GenerateArticle() {
|
||||||
|
return (
|
||||||
|
<Card className="rounded-md border bg-transparent">
|
||||||
|
<GenerateArticleForm />
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -8,13 +8,19 @@ export default function BasicPage() {
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-hidden overflow-y-scroll rounded-lg border-2">
|
<div className="overflow-x-hidden overflow-y-scroll rounded-lg border-2">
|
||||||
<div className="px-2 md:px-4 w-full">
|
<div className="px-2 md:px-4 w-full">
|
||||||
<div className="rounded-md my-5 px-5 py-2 bg-white dark:bg-[#18181b]">
|
<div className="rounded-md my-5 px-5 py-2 bg-white dark:bg-[#18181b] flex flex-row gap-3">
|
||||||
<Link href="/admin/article/create">
|
<Link href="/admin/article/create">
|
||||||
<Button size="md" color="primary" className="w-min">
|
<Button size="md" color="primary" className="w-min">
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
New Article
|
New Article
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link href="/admin/article/generate">
|
||||||
|
<Button size="md" color="primary" className="w-min">
|
||||||
|
<AddIcon />
|
||||||
|
Generate Article
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white dark:bg-[#18181b] rounded-xl my-5 p-2">
|
<div className="bg-white dark:bg-[#18181b] rounded-xl my-5 p-2">
|
||||||
<ArticleTable />
|
<ArticleTable />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,605 @@
|
||||||
|
"use client";
|
||||||
|
import { error } from "@/config/swal";
|
||||||
|
import { createArticle } from "@/service/article";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Chip,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
SelectItem,
|
||||||
|
SelectSection,
|
||||||
|
Selection,
|
||||||
|
Spinner,
|
||||||
|
} from "@nextui-org/react";
|
||||||
|
import JoditEditor from "jodit-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import * as z from "zod";
|
||||||
|
import ReactSelect from "react-select";
|
||||||
|
import makeAnimated from "react-select/animated";
|
||||||
|
import GenerateSingleArticle from "./generate-single-article-form";
|
||||||
|
import { getDetailArticle } from "@/service/generate-article";
|
||||||
|
import { delay } from "@/utils/global";
|
||||||
|
import GenerateBulkArticle from "./generate-bulk-article-form";
|
||||||
|
import generatedArticleIds from "@/store/generated-article-store";
|
||||||
|
import SpeechToTextOperator from "./speech-to-text-form";
|
||||||
|
import GenerateRewriteArticle from "./generate-rewrite-form";
|
||||||
|
import SpeechToText from "./speech-to-text-form";
|
||||||
|
|
||||||
|
const articleSchema = z.object({
|
||||||
|
title: z.string().min(1, { message: "Required" }),
|
||||||
|
article: z.string().min(1, { message: "Required" }),
|
||||||
|
slug: z.string().min(1, { message: "Required" }),
|
||||||
|
tags: z.string().min(0, { message: "Required" }).optional(),
|
||||||
|
description: z.string().min(0, { message: "Required" }).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const dummyCategory = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
label: "Category 1",
|
||||||
|
value: "category-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
label: "Category 2",
|
||||||
|
value: "category-2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
label: "Category 3",
|
||||||
|
value: "category-3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
label: "Category 4",
|
||||||
|
value: "category-4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
label: "Category 5",
|
||||||
|
value: "category-5",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function GenerateArticleForm() {
|
||||||
|
const animatedComponents = makeAnimated();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const [title, setTitle] = useState<string>("");
|
||||||
|
const [article, setArticle] = React.useState<Selection>(new Set([]));
|
||||||
|
const [slug, setSlug] = useState<string>("");
|
||||||
|
const [tags, setTags] = useState<string[]>([]);
|
||||||
|
const [newTags, setNewTags] = useState<string>("");
|
||||||
|
const editor = useRef(null);
|
||||||
|
const [content, setContent] = useState("");
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
const [selectedImages, setSelectedImages] = useState<File[]>([]);
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<any>();
|
||||||
|
const [selectedContentType, setSelectedContentType] =
|
||||||
|
useState("single-article");
|
||||||
|
|
||||||
|
const [collectSingleArticleId, setCollectSingleArticleId] = useState<
|
||||||
|
number[]
|
||||||
|
>([]);
|
||||||
|
const [collectBulkArticleId, setCollectBulkArticleId] = useState<number[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [collectRewriteArticleId, setCollectRewriteArticleId] = useState<
|
||||||
|
number[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const [generatedTranscriptId, setGeneratedTranscriptId] = useState<number>();
|
||||||
|
|
||||||
|
const generatedArticleIdStore = generatedArticleIds(
|
||||||
|
(state) => state.articleIds
|
||||||
|
);
|
||||||
|
const setGeneratedArticleIdStore = generatedArticleIds(
|
||||||
|
(state) => state.setArticleIds
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selectedGeneratedArticleId, setSelectedGeneratedArticleId] =
|
||||||
|
useState<number>();
|
||||||
|
|
||||||
|
const formOptions = { resolver: zodResolver(articleSchema) };
|
||||||
|
type MicroIssueSchema = z.infer<typeof articleSchema>;
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<MicroIssueSchema>(formOptions);
|
||||||
|
|
||||||
|
const TypeId = [
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
label: "Article",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 2,
|
||||||
|
label: "Magazine",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.files) {
|
||||||
|
const files = Array.from(event.target.files);
|
||||||
|
setSelectedImages((prevImages) => [...prevImages, ...files]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveImage = (index: number) => {
|
||||||
|
setSelectedImages((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = (tagsToRemove: string) => {
|
||||||
|
setTags(tags.filter((tag) => tag !== tagsToRemove));
|
||||||
|
if (tags.length === 1) {
|
||||||
|
setTags([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddTags = (e: any) => {
|
||||||
|
if (newTags.trim() !== "") {
|
||||||
|
setTags([...tags, newTags.trim()]);
|
||||||
|
setNewTags("");
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (event: any) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
handleAddTags(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function save(data: any) {
|
||||||
|
const formData = {
|
||||||
|
title: title,
|
||||||
|
typeId: parseInt(String(Array.from(article)[0])),
|
||||||
|
slug: slug,
|
||||||
|
tags: tags.join(","),
|
||||||
|
description: content,
|
||||||
|
htmlDescription: content,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Form Data:", formData);
|
||||||
|
const response = await createArticle(formData);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
successSubmit("/admin/article");
|
||||||
|
}
|
||||||
|
|
||||||
|
function successSubmit(redirect: any) {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
router.push(redirect);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSubmit(data: any) {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Simpan Data",
|
||||||
|
text: "",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#d33",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "Simpan",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
save(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const singleArticleId = (id: number) => {
|
||||||
|
let temp = [...collectSingleArticleId, id];
|
||||||
|
setCollectSingleArticleId(temp);
|
||||||
|
};
|
||||||
|
const bulkArticleId = (id: number[]) => {
|
||||||
|
let temp: number[] = [...collectBulkArticleId, ...id];
|
||||||
|
setCollectBulkArticleId(temp);
|
||||||
|
};
|
||||||
|
const rewriteArticleId = (id: number) => {
|
||||||
|
let temp = [...collectRewriteArticleId, id];
|
||||||
|
setCollectRewriteArticleId(temp);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getArticleDetail();
|
||||||
|
}, [selectedGeneratedArticleId]);
|
||||||
|
|
||||||
|
const checkArticleStatus = async (data: string | null) => {
|
||||||
|
if (data === null) {
|
||||||
|
delay(10000).then(() => {
|
||||||
|
getArticleDetail();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getArticleDetail = async () => {
|
||||||
|
if (selectedGeneratedArticleId) {
|
||||||
|
const res = await getDetailArticle(selectedGeneratedArticleId);
|
||||||
|
const data = res?.data?.data?.articleBody;
|
||||||
|
checkArticleStatus(data);
|
||||||
|
if (data !== null) {
|
||||||
|
setContent(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-5 my-5 overflow-y-auto">
|
||||||
|
<div>
|
||||||
|
<Card className="rounded-md p-5 space-y-5">
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
type="title"
|
||||||
|
{...register("title")}
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
label="Judul"
|
||||||
|
variant="bordered"
|
||||||
|
placeholder="Enter Text"
|
||||||
|
labelPlacement="outside"
|
||||||
|
/>
|
||||||
|
<div className="text-sm text-red-500">
|
||||||
|
{title.length === 0 && errors.title && errors.title.message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
label="Jenis Artikel"
|
||||||
|
{...register("article")}
|
||||||
|
variant="bordered"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder="Select"
|
||||||
|
selectedKeys={article}
|
||||||
|
className="max-w-xs"
|
||||||
|
onSelectionChange={setArticle}
|
||||||
|
>
|
||||||
|
{TypeId.map((data) => (
|
||||||
|
<SelectItem key={data.key} value={data.key}>
|
||||||
|
{data.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<div className="text-sm text-red-500">
|
||||||
|
{errors.article?.message}
|
||||||
|
</div>
|
||||||
|
{/* <p>{article}</p> */}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm mb-1">Category</p>
|
||||||
|
<ReactSelect
|
||||||
|
className="basic-single text-black z-50"
|
||||||
|
classNames={{
|
||||||
|
control: (state: any) =>
|
||||||
|
"!rounded-xl bg-white !border-1 !border-gray-200",
|
||||||
|
}}
|
||||||
|
classNamePrefix="select"
|
||||||
|
onChange={setSelectedCategory}
|
||||||
|
closeMenuOnSelect={false}
|
||||||
|
components={animatedComponents}
|
||||||
|
isClearable={true}
|
||||||
|
isSearchable={true}
|
||||||
|
isMulti={true}
|
||||||
|
placeholder="Category ..."
|
||||||
|
name="sub-module"
|
||||||
|
options={dummyCategory}
|
||||||
|
/>
|
||||||
|
<div className="text-sm text-red-500">
|
||||||
|
{(!selectedCategory || selectedCategory?.length < 1) && (
|
||||||
|
<p>Required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
{...register("slug")}
|
||||||
|
value={slug}
|
||||||
|
onChange={(e) => setSlug(e.target.value)}
|
||||||
|
label="Slug"
|
||||||
|
variant="bordered"
|
||||||
|
placeholder="Enter Text"
|
||||||
|
labelPlacement="outside"
|
||||||
|
/>
|
||||||
|
<div className="text-sm text-red-500">
|
||||||
|
{slug.length === 0 && errors.slug && errors.slug.message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
label="Tags (Optional)"
|
||||||
|
{...register("tags")}
|
||||||
|
labelPlacement="outside"
|
||||||
|
type="text"
|
||||||
|
value={newTags}
|
||||||
|
onChange={(e) => setNewTags(e.target.value)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
placeholder="Tambahkan tag baru dan tekan Enter"
|
||||||
|
/>
|
||||||
|
<div className="text-sm text-red-500">
|
||||||
|
{tags.length === 0 && errors.tags && errors.tags.message}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 border border-inherit mt-2 rounded-md p-1 items-center h-11">
|
||||||
|
{tags.map((tag, index) => (
|
||||||
|
<Chip
|
||||||
|
color="primary"
|
||||||
|
key={index}
|
||||||
|
onClose={() => handleClose(tag)}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</Chip>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Select
|
||||||
|
label="Content Type"
|
||||||
|
variant="bordered"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder=""
|
||||||
|
selectedKeys={[selectedContentType]}
|
||||||
|
onChange={(e) =>
|
||||||
|
e.target.value !== ""
|
||||||
|
? setSelectedContentType(e.target.value)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
className="w-full md:w-1/4 py-4"
|
||||||
|
>
|
||||||
|
<SelectSection>
|
||||||
|
<SelectItem key="single-article">Single Article</SelectItem>
|
||||||
|
<SelectItem key="bulk-article">Bulk Article</SelectItem>
|
||||||
|
<SelectItem key="rewrite">Content Rewrite</SelectItem>
|
||||||
|
<SelectItem key="speech-to-text">Speech to Text</SelectItem>
|
||||||
|
</SelectSection>
|
||||||
|
</Select>
|
||||||
|
{selectedContentType === "single-article" ? (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<GenerateSingleArticle
|
||||||
|
articleId={(data) => singleArticleId(data)}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{collectSingleArticleId.length > 0 &&
|
||||||
|
collectSingleArticleId.map((data) => (
|
||||||
|
<Button
|
||||||
|
key={data}
|
||||||
|
color={
|
||||||
|
selectedGeneratedArticleId === data
|
||||||
|
? "primary"
|
||||||
|
: "warning"
|
||||||
|
}
|
||||||
|
onPress={() => setSelectedGeneratedArticleId(data)}
|
||||||
|
>
|
||||||
|
{data}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{selectedGeneratedArticleId &&
|
||||||
|
collectSingleArticleId.includes(selectedGeneratedArticleId) &&
|
||||||
|
(selectedGeneratedArticleId && content !== "" ? (
|
||||||
|
<div>
|
||||||
|
<p className="pb-2">Description</p>
|
||||||
|
<JoditEditor
|
||||||
|
ref={editor}
|
||||||
|
value={content}
|
||||||
|
onChange={(newContent) => setContent(newContent)}
|
||||||
|
className="dark:text-black"
|
||||||
|
/>
|
||||||
|
<div className="text-sm text-red-500">
|
||||||
|
{content?.length === 0 &&
|
||||||
|
errors?.description &&
|
||||||
|
errors?.description?.message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : selectedGeneratedArticleId && content == "" ? (
|
||||||
|
<Spinner size="lg" />
|
||||||
|
) : collectSingleArticleId.length > 0 ? (
|
||||||
|
<p className="text-lg">Select Article ID</p>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : selectedContentType === "bulk-article" ? (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<GenerateBulkArticle articleId={(data) => bulkArticleId(data)} />
|
||||||
|
<div className="flex flex-wrap gap-3 mt-3">
|
||||||
|
{collectBulkArticleId.length > 0 &&
|
||||||
|
collectBulkArticleId.map((data) => (
|
||||||
|
<Button
|
||||||
|
key={data}
|
||||||
|
color={
|
||||||
|
selectedGeneratedArticleId === data
|
||||||
|
? "primary"
|
||||||
|
: "warning"
|
||||||
|
}
|
||||||
|
onPress={() => setSelectedGeneratedArticleId(data)}
|
||||||
|
>
|
||||||
|
{data}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{selectedGeneratedArticleId &&
|
||||||
|
collectBulkArticleId.includes(selectedGeneratedArticleId) &&
|
||||||
|
(selectedGeneratedArticleId && content !== "" ? (
|
||||||
|
<div>
|
||||||
|
<p className="pb-2">Description</p>
|
||||||
|
<JoditEditor
|
||||||
|
ref={editor}
|
||||||
|
value={content}
|
||||||
|
onChange={(newContent) => setContent(newContent)}
|
||||||
|
className="dark:text-black"
|
||||||
|
/>
|
||||||
|
<div className="text-sm text-red-500">
|
||||||
|
{content?.length === 0 &&
|
||||||
|
errors?.description &&
|
||||||
|
errors?.description?.message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : selectedGeneratedArticleId && content == "" ? (
|
||||||
|
<Spinner size="lg" />
|
||||||
|
) : collectSingleArticleId.length > 0 ? (
|
||||||
|
<p className="text-lg">Select Article ID</p>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : selectedContentType === "rewrite" ? (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<GenerateRewriteArticle
|
||||||
|
articleId={(data) => rewriteArticleId(data)}
|
||||||
|
initTranscript={generatedTranscriptId}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-wrap gap-3 mt-3">
|
||||||
|
{collectRewriteArticleId.length > 0 &&
|
||||||
|
collectRewriteArticleId.map((data) => (
|
||||||
|
<Button
|
||||||
|
key={data}
|
||||||
|
color={
|
||||||
|
selectedGeneratedArticleId === data
|
||||||
|
? "primary"
|
||||||
|
: "warning"
|
||||||
|
}
|
||||||
|
onPress={() => setSelectedGeneratedArticleId(data)}
|
||||||
|
>
|
||||||
|
{data}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{selectedGeneratedArticleId &&
|
||||||
|
collectRewriteArticleId.includes(selectedGeneratedArticleId) &&
|
||||||
|
(selectedGeneratedArticleId && content !== "" ? (
|
||||||
|
<div>
|
||||||
|
<p className="pb-2">Description</p>
|
||||||
|
<JoditEditor
|
||||||
|
ref={editor}
|
||||||
|
value={content}
|
||||||
|
onChange={(newContent) => setContent(newContent)}
|
||||||
|
className="dark:text-black"
|
||||||
|
/>
|
||||||
|
<div className="text-sm text-red-500">
|
||||||
|
{content?.length === 0 &&
|
||||||
|
errors?.description &&
|
||||||
|
errors?.description?.message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : selectedGeneratedArticleId && content == "" ? (
|
||||||
|
<Spinner size="lg" />
|
||||||
|
) : collectRewriteArticleId.length > 0 ? (
|
||||||
|
<p className="text-lg">Select Article ID</p>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : selectedContentType === "speech-to-text" ? (
|
||||||
|
<SpeechToText
|
||||||
|
generateArticleFromTranscript={(transcriptId) => {
|
||||||
|
setGeneratedTranscriptId(transcriptId);
|
||||||
|
setSelectedContentType("rewrite");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>Attachment (Opsional)</p>
|
||||||
|
<div className="flex items-center justify-center w-full pt-2 ">
|
||||||
|
<label
|
||||||
|
htmlFor="dropzone-file"
|
||||||
|
className="flex flex-col items-center justify-center w-full h-36 border border-gray-100 border-dashed rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-200"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center justify-center pt-5 pb-6 ">
|
||||||
|
<svg
|
||||||
|
className="w-10 h-10 mb-3 text-gray-400"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
Drag and drop files here
|
||||||
|
</p>
|
||||||
|
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{/* or{" "} */}
|
||||||
|
<span className="font-semibold underline text-amber-800">
|
||||||
|
Click to upload
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="dropzone-file"
|
||||||
|
type="file"
|
||||||
|
onChange={handleImageChange}
|
||||||
|
className="hidden"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{selectedImages?.length > 0 ? (
|
||||||
|
<div>
|
||||||
|
<h4>Pratinjau:</h4>
|
||||||
|
<div className="flex gap-2 pt-2">
|
||||||
|
{selectedImages.map((image, index) => (
|
||||||
|
<div key={index} className="flex flex-col items-end">
|
||||||
|
<Chip
|
||||||
|
color="danger"
|
||||||
|
size="sm"
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => handleRemoveImage(index)}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</Chip>
|
||||||
|
<img
|
||||||
|
src={URL.createObjectURL(image)}
|
||||||
|
alt="Pratinjau Gambar"
|
||||||
|
style={{ maxWidth: "200px", maxHeight: "200px" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end gap-3">
|
||||||
|
<Link href={`/admin/article`}>
|
||||||
|
<Button color="danger" variant="ghost">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Button type="button" color="primary" variant="solid">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,506 @@
|
||||||
|
"use client";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
SelectItem,
|
||||||
|
SelectSection,
|
||||||
|
} from "@nextui-org/react";
|
||||||
|
import { FormEvent, useState } from "react";
|
||||||
|
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||||
|
import * as z from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { close, error, loading } from "@/config/swal";
|
||||||
|
import {
|
||||||
|
generateDataArticle,
|
||||||
|
getGenerateKeywords,
|
||||||
|
getGenerateTitle,
|
||||||
|
getGenerateTopicKeywords,
|
||||||
|
saveBulkArticle,
|
||||||
|
} from "@/service/generate-article";
|
||||||
|
|
||||||
|
const writingStyle = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Friendly",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Professional",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Informational",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Neutral",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "Witty",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const articleSize = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "News (300 - 900 words)",
|
||||||
|
value: "News",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Info (900 - 2000 words)",
|
||||||
|
value: "Info",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Detail (2000 - 5000 words)",
|
||||||
|
value: "Detail",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
rows: z.array(
|
||||||
|
z.object({
|
||||||
|
mainKeyword: z.string().min(1, {
|
||||||
|
message: "Main Keyword must be at least 2 characters.",
|
||||||
|
}),
|
||||||
|
title: z.string().min(1, {
|
||||||
|
message: "Title must be at least 2 characters.",
|
||||||
|
}),
|
||||||
|
additionalKeyword: z.string().min(1, {
|
||||||
|
message: "Additional Keyword must be at least 2 characters.",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function GenerateBulkArticle(props: {
|
||||||
|
articleId: (data: number[]) => void;
|
||||||
|
}) {
|
||||||
|
const [selectedWritingSyle, setSelectedWritingStyle] =
|
||||||
|
useState("Informational");
|
||||||
|
const [selectedArticleSize, setSelectedArticleSize] = useState("News");
|
||||||
|
const [selectedLanguage, setSelectedLanguage] = useState("id");
|
||||||
|
|
||||||
|
const formOptions = {
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
rows: [{ mainKeyword: "", title: "", additionalKeyword: "" }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
type UserSettingSchema = z.infer<typeof formSchema>;
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
setValue,
|
||||||
|
getValues,
|
||||||
|
trigger,
|
||||||
|
} = useForm<UserSettingSchema>(formOptions);
|
||||||
|
|
||||||
|
const { fields, append, remove } = useFieldArray({
|
||||||
|
control,
|
||||||
|
name: "rows",
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
loading();
|
||||||
|
const listData = [];
|
||||||
|
for (let i = 0; i < fields.length; i++) {
|
||||||
|
listData.push({
|
||||||
|
title: getValues(`rows.${i}.title`),
|
||||||
|
mainKeyword: getValues(`rows.${i}.mainKeyword`),
|
||||||
|
additionalKeywords: getValues(`rows.${i}.additionalKeyword`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const request = {
|
||||||
|
style: "Friendly",
|
||||||
|
website: "None",
|
||||||
|
connectToWeb: true,
|
||||||
|
lang: selectedLanguage,
|
||||||
|
pointOfView: "0",
|
||||||
|
imageSource: "Web",
|
||||||
|
targetCountry: null,
|
||||||
|
articleSize: selectedArticleSize,
|
||||||
|
projectId: 2,
|
||||||
|
data: listData,
|
||||||
|
createdBy: "123123",
|
||||||
|
clientId: "humasClientIdtest",
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await saveBulkArticle(request);
|
||||||
|
if (res.error) {
|
||||||
|
error(res.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("res?s", res?.data?.data);
|
||||||
|
const temp: number[] = [];
|
||||||
|
res?.data?.data.map((data: any) => temp.push(data.id));
|
||||||
|
props.articleId(temp);
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateTitle = async (keyword: string | undefined, index: number) => {
|
||||||
|
if (keyword) {
|
||||||
|
const req = {
|
||||||
|
keyword: keyword,
|
||||||
|
style: selectedWritingSyle,
|
||||||
|
website: "None",
|
||||||
|
connectToWeb: true,
|
||||||
|
lang: selectedLanguage,
|
||||||
|
pointOfView: "None",
|
||||||
|
clientId: "",
|
||||||
|
};
|
||||||
|
setValue(`rows.${index}.title`, "process...");
|
||||||
|
|
||||||
|
const res = await getGenerateTitle(req);
|
||||||
|
const data = res?.data?.data;
|
||||||
|
setValue(`rows.${index}.title`, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateKeywords = async (
|
||||||
|
keyword: string | undefined,
|
||||||
|
index: number
|
||||||
|
) => {
|
||||||
|
if (keyword) {
|
||||||
|
const req = {
|
||||||
|
keyword: keyword,
|
||||||
|
style: selectedWritingSyle,
|
||||||
|
website: "None",
|
||||||
|
connectToWeb: true,
|
||||||
|
lang: selectedLanguage,
|
||||||
|
pointOfView: "0",
|
||||||
|
clientId: "",
|
||||||
|
};
|
||||||
|
setValue(`rows.${index}.additionalKeyword`, "process...");
|
||||||
|
const res = await getGenerateKeywords(req);
|
||||||
|
const data = res?.data?.data;
|
||||||
|
setValue(`rows.${index}.additionalKeyword`, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const processAll = async () => {
|
||||||
|
let emptyMainKeyword = 0;
|
||||||
|
const mainKeyword = getValues(`rows.0.mainKeyword`);
|
||||||
|
for (let i = 0; i < fields.length; i++) {
|
||||||
|
const mainKeyNow = getValues(`rows.${i}.mainKeyword`);
|
||||||
|
if (mainKeyNow === "") {
|
||||||
|
emptyMainKeyword++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mainKeyword !== "") {
|
||||||
|
if (emptyMainKeyword > 0) {
|
||||||
|
loading();
|
||||||
|
const res = await getGenerateTopicKeywords({
|
||||||
|
keyword: mainKeyword,
|
||||||
|
count: emptyMainKeyword,
|
||||||
|
});
|
||||||
|
const data = res?.data?.data;
|
||||||
|
let j = 0;
|
||||||
|
for (let i = 0; i < fields.length; i++) {
|
||||||
|
const mainKeyNow = getValues(`rows.${i}.mainKeyword`);
|
||||||
|
if (mainKeyNow === "") {
|
||||||
|
setValue(`rows.${i}.mainKeyword`, data[j]);
|
||||||
|
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < fields.length; i++) {
|
||||||
|
const mainKeyNow = getValues(`rows.${i}.mainKeyword`);
|
||||||
|
if (getValues(`rows.${i}.title`) == "") {
|
||||||
|
generateTitle(mainKeyNow, i);
|
||||||
|
}
|
||||||
|
if (getValues(`rows.${i}.additionalKeyword`) == "") {
|
||||||
|
generateKeywords(mainKeyNow, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close();
|
||||||
|
} else {
|
||||||
|
loading();
|
||||||
|
for (let i = 0; i < fields.length; i++) {
|
||||||
|
const mainKeyNow = getValues(`rows.${i}.mainKeyword`);
|
||||||
|
if (getValues(`rows.${i}.title`) == "") {
|
||||||
|
generateTitle(mainKeyNow, i);
|
||||||
|
}
|
||||||
|
if (getValues(`rows.${i}.additionalKeyword`) == "") {
|
||||||
|
generateKeywords(mainKeyNow, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const processRow = (index: number) => {
|
||||||
|
const addKeyword = getValues(`rows.${index}.mainKeyword`);
|
||||||
|
console.log("index", index, addKeyword);
|
||||||
|
generateTitle(addKeyword, index);
|
||||||
|
|
||||||
|
generateKeywords(addKeyword, index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateFields = async () => {
|
||||||
|
const validation = await trigger();
|
||||||
|
if (validation) {
|
||||||
|
onSubmit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<fieldset>
|
||||||
|
<form
|
||||||
|
className="flex flex-col gap-2 w-full"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 w-full">
|
||||||
|
<Select
|
||||||
|
label="Writing Style"
|
||||||
|
variant="bordered"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder=""
|
||||||
|
selectedKeys={[selectedWritingSyle]}
|
||||||
|
onChange={(e) =>
|
||||||
|
e.target.value !== ""
|
||||||
|
? setSelectedWritingStyle(e.target.value)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<SelectSection>
|
||||||
|
{writingStyle.map((style) => (
|
||||||
|
<SelectItem key={style.name}>{style.name}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectSection>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
label="Article Size"
|
||||||
|
variant="bordered"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder=""
|
||||||
|
selectedKeys={[selectedArticleSize]}
|
||||||
|
onChange={(e) =>
|
||||||
|
e.target.value !== ""
|
||||||
|
? setSelectedArticleSize(e.target.value)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<SelectSection>
|
||||||
|
{articleSize.map((size) => (
|
||||||
|
<SelectItem key={size.value}>{size.name}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectSection>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
label="Language"
|
||||||
|
variant="bordered"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder=""
|
||||||
|
selectedKeys={[selectedLanguage]}
|
||||||
|
onChange={(e) =>
|
||||||
|
e.target.value !== "" ? setSelectedLanguage(e.target.value) : ""
|
||||||
|
}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<SelectSection>
|
||||||
|
<SelectItem key="id">Indonesia</SelectItem>
|
||||||
|
<SelectItem key="en">English</SelectItem>
|
||||||
|
</SelectSection>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className={`flex flex-col md:gap-3 `}>
|
||||||
|
{fields.map((field, index) => (
|
||||||
|
<div
|
||||||
|
key={field.id}
|
||||||
|
className={` grid grid-cols-1 md:grid-cols-3 gap-5 w-full ${
|
||||||
|
fields.length === 1
|
||||||
|
? ""
|
||||||
|
: "border-b-2 md:border-none py-3 md:py-1"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div className="text-[10px] md:text-base flex items-center justify-between">
|
||||||
|
<div className="flex flex-row items-center gap-1">
|
||||||
|
<p className="text-sm">Main Keyword</p>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
if (index === 0) {
|
||||||
|
processAll();
|
||||||
|
} else {
|
||||||
|
processRow(index);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Process {index === 0 ? "All" : ""}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="w-[42px] flex flex-row gap-1 md:hidden">
|
||||||
|
{index === fields.length - 1 && (
|
||||||
|
<button
|
||||||
|
className="w-[20px] mb-2 "
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
append({
|
||||||
|
mainKeyword: "",
|
||||||
|
title: "",
|
||||||
|
additionalKeyword: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{index !== 0 && (
|
||||||
|
<a
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
className="cursor-pointer mb-2 w-[20px] h-[20px]text-center rounded-full flex justify-center items-center"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`rows.${index}.mainKeyword`}
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="mainKeyword"
|
||||||
|
placeholder=""
|
||||||
|
label=""
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
labelPlacement="outside"
|
||||||
|
className="w-full"
|
||||||
|
isDisabled={value === "process..."}
|
||||||
|
variant="bordered"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-[10px] md:text-base flex items-center gap-1">
|
||||||
|
<p className="text-sm">Title</p>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
generateTitle(
|
||||||
|
getValues(`rows.${index}.mainKeyword`),
|
||||||
|
index
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Process
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`rows.${index}.title`}
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="title"
|
||||||
|
placeholder=""
|
||||||
|
label=""
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
labelPlacement="outside"
|
||||||
|
className="w-full"
|
||||||
|
isDisabled={value === "process..."}
|
||||||
|
variant="bordered"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full flex flex-row items-end gap-1">
|
||||||
|
<div className="full grow">
|
||||||
|
<div>
|
||||||
|
<div className="text-[10px] md:text-base flex items-center gap-1">
|
||||||
|
<p className="text-sm">SEO</p>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
generateKeywords(
|
||||||
|
getValues(`rows.${index}.mainKeyword`),
|
||||||
|
index
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Process
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`rows.${index}.additionalKeyword`}
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="title"
|
||||||
|
placeholder=""
|
||||||
|
label=""
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
labelPlacement="outside"
|
||||||
|
className="w-full"
|
||||||
|
isDisabled={value === "process..."}
|
||||||
|
variant="bordered"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-[42px] flex-row gap-1 hidden md:flex">
|
||||||
|
{index === fields.length - 1 && (
|
||||||
|
<button
|
||||||
|
className="w-[20px] mb-2 "
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
append({
|
||||||
|
mainKeyword: "",
|
||||||
|
title: "",
|
||||||
|
additionalKeyword: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{index !== 0 && (
|
||||||
|
<a
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
className="cursor-pointer mb-2 w-[20px] h-[20px]text-center rounded-full flex justify-center items-center"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={() => validateFields()}
|
||||||
|
// disabled={validateFields()}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Generate
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,273 @@
|
||||||
|
"use client";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
SelectItem,
|
||||||
|
SelectSection,
|
||||||
|
Textarea,
|
||||||
|
} from "@nextui-org/react";
|
||||||
|
import { FormEvent, useEffect, useState } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import * as z from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { close, error, loading } from "@/config/swal";
|
||||||
|
import { getGenerateRewriter } from "@/service/generate-article";
|
||||||
|
import TranscriptDraftTable from "@/components/table/disestages/transcript-draft-table";
|
||||||
|
import ArticleDraftTable from "@/components/table/disestages/article-draft-table";
|
||||||
|
|
||||||
|
const writingStyle = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Friendly",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Professional",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Informational",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Neutral",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "Witty",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const articleSize = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "News (300 - 900 words)",
|
||||||
|
value: "News",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Info (900 - 2000 words)",
|
||||||
|
value: "Info",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Detail (2000 - 5000 words)",
|
||||||
|
value: "Detail",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
field1: z.string().min(2, {
|
||||||
|
message: "Required",
|
||||||
|
}),
|
||||||
|
advancedConfiguration: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function GenerateRewriteArticle(props: {
|
||||||
|
articleId: (data: number) => void;
|
||||||
|
initTranscript?: number;
|
||||||
|
}) {
|
||||||
|
const [selectedWritingSyle, setSelectedWritingStyle] =
|
||||||
|
useState("Informational");
|
||||||
|
const [selectedArticleSize, setSelectedArticleSize] = useState("News");
|
||||||
|
const [selectedLanguage, setSelectedLanguage] = useState("id");
|
||||||
|
const [contextType, setContextType] = useState("text");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.initTranscript) {
|
||||||
|
setContextType("transcript");
|
||||||
|
}
|
||||||
|
}, [props.initTranscript]);
|
||||||
|
|
||||||
|
const formOptions = { resolver: zodResolver(formSchema) };
|
||||||
|
type UserSettingSchema = z.infer<typeof formSchema>;
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
setValue,
|
||||||
|
} = useForm<UserSettingSchema>(formOptions);
|
||||||
|
|
||||||
|
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
||||||
|
loading();
|
||||||
|
const request = {
|
||||||
|
advConfig: values.advancedConfiguration || "",
|
||||||
|
context: contextType == "url" ? null : values.field1,
|
||||||
|
style: selectedWritingSyle,
|
||||||
|
sentiment: "Humorous",
|
||||||
|
clientId: "humasClientIdtest",
|
||||||
|
createdBy: "123123",
|
||||||
|
contextType: contextType,
|
||||||
|
urlContext: contextType === "url" ? values.field1 : null,
|
||||||
|
lang: selectedLanguage,
|
||||||
|
};
|
||||||
|
const res = await getGenerateRewriter(request);
|
||||||
|
close();
|
||||||
|
if (res?.error) {
|
||||||
|
error("Error");
|
||||||
|
}
|
||||||
|
props.articleId(res?.data?.data?.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue("field1", "");
|
||||||
|
}, [contextType]);
|
||||||
|
return (
|
||||||
|
<fieldset>
|
||||||
|
<form
|
||||||
|
className="flex flex-col gap-2 w-full"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-5 w-full">
|
||||||
|
<Select
|
||||||
|
label="Context Type"
|
||||||
|
variant="bordered"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder=""
|
||||||
|
selectedKeys={[contextType]}
|
||||||
|
onChange={(e) =>
|
||||||
|
e.target.value !== "" ? setContextType(e.target.value) : ""
|
||||||
|
}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<SelectSection>
|
||||||
|
<SelectItem key="text">Text</SelectItem>
|
||||||
|
<SelectItem key="article">Article</SelectItem>
|
||||||
|
<SelectItem key="transcript">Transcript</SelectItem>
|
||||||
|
<SelectItem key="url">URL</SelectItem>
|
||||||
|
</SelectSection>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
label="Writing Style"
|
||||||
|
variant="bordered"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder=""
|
||||||
|
selectedKeys={[selectedWritingSyle]}
|
||||||
|
onChange={(e) =>
|
||||||
|
e.target.value !== ""
|
||||||
|
? setSelectedWritingStyle(e.target.value)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<SelectSection>
|
||||||
|
{writingStyle.map((style) => (
|
||||||
|
<SelectItem key={style.name}>{style.name}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectSection>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
label="Article Size"
|
||||||
|
variant="bordered"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder=""
|
||||||
|
selectedKeys={[selectedArticleSize]}
|
||||||
|
onChange={(e) =>
|
||||||
|
e.target.value !== ""
|
||||||
|
? setSelectedArticleSize(e.target.value)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<SelectSection>
|
||||||
|
{articleSize.map((size) => (
|
||||||
|
<SelectItem key={size.value}>{size.name}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectSection>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
label="Language"
|
||||||
|
variant="bordered"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder=""
|
||||||
|
selectedKeys={[selectedLanguage]}
|
||||||
|
onChange={(e) =>
|
||||||
|
e.target.value !== "" ? setSelectedLanguage(e.target.value) : ""
|
||||||
|
}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<SelectSection>
|
||||||
|
<SelectItem key="id">Indonesia</SelectItem>
|
||||||
|
<SelectItem key="en">English</SelectItem>
|
||||||
|
</SelectSection>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3 mt-3">
|
||||||
|
{(contextType === "text" || contextType === "url") && (
|
||||||
|
<p className="text-sm">
|
||||||
|
{contextType === "text" ? "Enter your text here" : "Insert URL"}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{(contextType === "text" || contextType === "url") && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="field1"
|
||||||
|
render={({ field: { onChange, value } }) =>
|
||||||
|
contextType === "text" ? (
|
||||||
|
<Textarea
|
||||||
|
label=""
|
||||||
|
id="adv_config"
|
||||||
|
variant="bordered"
|
||||||
|
disableAnimation
|
||||||
|
className="pb-3"
|
||||||
|
placeholder="Type your custom instruction here!"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="mainKeyword"
|
||||||
|
placeholder=""
|
||||||
|
label=""
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
labelPlacement="outside"
|
||||||
|
className="w-full"
|
||||||
|
variant="bordered"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{errors.field1?.message && (
|
||||||
|
<p className="text-red-400 text-sm">{errors.field1?.message}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{contextType === "article" && (
|
||||||
|
<>
|
||||||
|
<ArticleDraftTable
|
||||||
|
articleContent={(data) => setValue("field1", data)}
|
||||||
|
/>
|
||||||
|
{errors.field1?.message && (
|
||||||
|
<p className="text-red-400 text-sm">Select Transcript</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{contextType === "transcript" && (
|
||||||
|
<>
|
||||||
|
<TranscriptDraftTable
|
||||||
|
transriptContent={(data) => setValue("field1", data)}
|
||||||
|
initTranscriptId={String(props.initTranscript)}
|
||||||
|
/>
|
||||||
|
{errors.field1?.message && (
|
||||||
|
<p className="text-red-400 text-sm">Select Transcript</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
className="my-5 w-full py-5 text-xs md:text-base"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Generate
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,334 @@
|
||||||
|
"use client";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
SelectItem,
|
||||||
|
SelectSection,
|
||||||
|
} from "@nextui-org/react";
|
||||||
|
import { FormEvent, useState } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import * as z from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { close, error, loading } from "@/config/swal";
|
||||||
|
import {
|
||||||
|
generateDataArticle,
|
||||||
|
getGenerateKeywords,
|
||||||
|
getGenerateTitle,
|
||||||
|
} from "@/service/generate-article";
|
||||||
|
|
||||||
|
const writingStyle = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Friendly",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Professional",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Informational",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Neutral",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "Witty",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const articleSize = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "News (300 - 900 words)",
|
||||||
|
value: "News",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Info (900 - 2000 words)",
|
||||||
|
value: "Info",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Detail (2000 - 5000 words)",
|
||||||
|
value: "Detail",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const singleArticleSchema = z.object({
|
||||||
|
mainKeyword: z.string().min(2, {
|
||||||
|
message: "Main Keyword must be at least 2 characters.",
|
||||||
|
}),
|
||||||
|
title: z.string().min(2, {
|
||||||
|
message: "Main Keyword must be at least 2 characters.",
|
||||||
|
}),
|
||||||
|
additionalKeyword: z.string().min(2, {
|
||||||
|
message: "Main Keyword must be at least 2 characters.",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function GenerateSingleArticle(props: {
|
||||||
|
articleId: (data: number) => void;
|
||||||
|
}) {
|
||||||
|
const [selectedWritingSyle, setSelectedWritingStyle] =
|
||||||
|
useState("Informational");
|
||||||
|
const [selectedArticleSize, setSelectedArticleSize] = useState("News");
|
||||||
|
const [selectedLanguage, setSelectedLanguage] = useState("id");
|
||||||
|
|
||||||
|
const formOptions = { resolver: zodResolver(singleArticleSchema) };
|
||||||
|
type UserSettingSchema = z.infer<typeof singleArticleSchema>;
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
setValue,
|
||||||
|
getValues,
|
||||||
|
} = useForm<UserSettingSchema>(formOptions);
|
||||||
|
|
||||||
|
const generateAll = async (keyword: string | undefined) => {
|
||||||
|
if (keyword) {
|
||||||
|
generateTitle(keyword);
|
||||||
|
generateKeywords(keyword);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateTitle = async (keyword: string | undefined) => {
|
||||||
|
if (keyword) {
|
||||||
|
loading();
|
||||||
|
const req = {
|
||||||
|
keyword: keyword,
|
||||||
|
style: selectedWritingSyle,
|
||||||
|
website: "None",
|
||||||
|
connectToWeb: true,
|
||||||
|
lang: selectedLanguage,
|
||||||
|
pointOfView: "None",
|
||||||
|
clientId: "",
|
||||||
|
};
|
||||||
|
const res = await getGenerateTitle(req);
|
||||||
|
const data = res?.data?.data;
|
||||||
|
setValue("title", data);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateKeywords = async (keyword: string | undefined) => {
|
||||||
|
if (keyword) {
|
||||||
|
const req = {
|
||||||
|
keyword: keyword,
|
||||||
|
style: selectedWritingSyle,
|
||||||
|
website: "None",
|
||||||
|
connectToWeb: true,
|
||||||
|
lang: selectedLanguage,
|
||||||
|
pointOfView: "0",
|
||||||
|
clientId: "",
|
||||||
|
};
|
||||||
|
loading();
|
||||||
|
const res = await getGenerateKeywords(req);
|
||||||
|
const data = res?.data?.data;
|
||||||
|
setValue("additionalKeyword", data);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (values: z.infer<typeof singleArticleSchema>) => {
|
||||||
|
loading();
|
||||||
|
const request = {
|
||||||
|
advConfig: "",
|
||||||
|
style: selectedWritingSyle,
|
||||||
|
website: "None",
|
||||||
|
connectToWeb: true,
|
||||||
|
lang: selectedLanguage,
|
||||||
|
pointOfView: "None",
|
||||||
|
title: values.title,
|
||||||
|
imageSource: "Web",
|
||||||
|
mainKeyword: values.mainKeyword,
|
||||||
|
additionalKeywords: values.additionalKeyword,
|
||||||
|
targetCountry: null,
|
||||||
|
articleSize: selectedArticleSize,
|
||||||
|
projectId: 2,
|
||||||
|
createdBy: "123123",
|
||||||
|
clientId: "humasClientIdtest",
|
||||||
|
};
|
||||||
|
console.log("reqq", request);
|
||||||
|
const res = await generateDataArticle(request);
|
||||||
|
close();
|
||||||
|
console.log("res", res?.data?.data);
|
||||||
|
if (res?.error) {
|
||||||
|
error("Error");
|
||||||
|
}
|
||||||
|
props.articleId(res?.data?.data?.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<fieldset>
|
||||||
|
<form
|
||||||
|
className="flex flex-col gap-2 w-full"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 w-full">
|
||||||
|
<Select
|
||||||
|
label="Writing Style"
|
||||||
|
variant="bordered"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder=""
|
||||||
|
selectedKeys={[selectedWritingSyle]}
|
||||||
|
onChange={(e) =>
|
||||||
|
e.target.value !== ""
|
||||||
|
? setSelectedWritingStyle(e.target.value)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<SelectSection>
|
||||||
|
{writingStyle.map((style) => (
|
||||||
|
<SelectItem key={style.name}>{style.name}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectSection>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
label="Article Size"
|
||||||
|
variant="bordered"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder=""
|
||||||
|
selectedKeys={[selectedArticleSize]}
|
||||||
|
onChange={(e) =>
|
||||||
|
e.target.value !== ""
|
||||||
|
? setSelectedArticleSize(e.target.value)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<SelectSection>
|
||||||
|
{articleSize.map((size) => (
|
||||||
|
<SelectItem key={size.value}>{size.name}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectSection>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
label="Language"
|
||||||
|
variant="bordered"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder=""
|
||||||
|
selectedKeys={[selectedLanguage]}
|
||||||
|
onChange={(e) =>
|
||||||
|
e.target.value !== "" ? setSelectedLanguage(e.target.value) : ""
|
||||||
|
}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<SelectSection>
|
||||||
|
<SelectItem key="id">Indonesia</SelectItem>
|
||||||
|
<SelectItem key="en">English</SelectItem>
|
||||||
|
</SelectSection>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3 mt-3">
|
||||||
|
<div className="flex flex-row gap-2 items-center">
|
||||||
|
<p className="text-sm">Main Keyword</p>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
onPress={() => generateAll(getValues("mainKeyword"))}
|
||||||
|
>
|
||||||
|
Process
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="mainKeyword"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="mainKeyword"
|
||||||
|
placeholder=""
|
||||||
|
label=""
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
labelPlacement="outside"
|
||||||
|
className="w-full"
|
||||||
|
variant="bordered"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.mainKeyword && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.mainKeyword?.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="flex flex-row gap-2 items-center">
|
||||||
|
<p className="text-sm">Title</p>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
onPress={() => generateAll(getValues("mainKeyword"))}
|
||||||
|
>
|
||||||
|
Generate
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="title"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="title"
|
||||||
|
placeholder=""
|
||||||
|
label=""
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
labelPlacement="outside"
|
||||||
|
className="w-full"
|
||||||
|
variant="bordered"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.title && (
|
||||||
|
<p className="text-red-400 text-sm">{errors.title?.message}</p>
|
||||||
|
)}
|
||||||
|
<div className="flex flex-row gap-2 items-center">
|
||||||
|
<p className="text-sm">Additional Keyword</p>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
onPress={() => generateAll(getValues("mainKeyword"))}
|
||||||
|
>
|
||||||
|
Generate
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="additionalKeyword"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="additionalKeyword"
|
||||||
|
placeholder=""
|
||||||
|
label=""
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
labelPlacement="outside"
|
||||||
|
className="w-full"
|
||||||
|
variant="bordered"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.additionalKeyword && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.additionalKeyword?.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
className="my-5 w-full py-5 text-xs md:text-base"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Generate
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,288 @@
|
||||||
|
"use client";
|
||||||
|
import { AddIcon, TimesIcon } from "@/components/icons";
|
||||||
|
import { Input, Textarea } from "@nextui-org/input";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
CircularProgress,
|
||||||
|
Divider,
|
||||||
|
Select,
|
||||||
|
SelectItem,
|
||||||
|
SelectSection,
|
||||||
|
} from "@nextui-org/react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import { Suspense, useState } from "react";
|
||||||
|
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import { close, error, loading } from "@/config/swal";
|
||||||
|
import {
|
||||||
|
generateSpeechToText,
|
||||||
|
getTranscriptById,
|
||||||
|
} from "@/service/generate-article";
|
||||||
|
import { delay } from "@/utils/global";
|
||||||
|
|
||||||
|
// import OperatorArticleTable from "@/components/table/assistant/create-content/operator/operator-article-table";
|
||||||
|
// import OperatorTranscriptTable from "@/components/table/assistant/create-content/operator/operator-transcript-table";
|
||||||
|
// import OperatorArticleDraftTable from "@/components/table/assistant/create-content/operator/operator-draft-article-table";
|
||||||
|
// import OperatorDetailDraftArticle from "@/components/main/multipool-assistant/operator-detail-draft-article";
|
||||||
|
|
||||||
|
const createContentOnlineMediaSchema = z.object({
|
||||||
|
title: z.string().min(1, { message: "Required" }),
|
||||||
|
prompt: z.string().min(1, { message: "Required" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function SpeechToText(props: {
|
||||||
|
generateArticleFromTranscript: (transcriptId: number) => void;
|
||||||
|
}) {
|
||||||
|
const searchParam = useSearchParams();
|
||||||
|
const transcriptQuery = searchParam.get("transcriptId");
|
||||||
|
const roleId = Cookies.get("urie");
|
||||||
|
|
||||||
|
let [files, setFiles] = useState<File[]>([]);
|
||||||
|
const [handleFileStatus, setHandleFileStatus] = useState("");
|
||||||
|
const [detailTranscript, setDetailTranscript] = useState<string>("");
|
||||||
|
|
||||||
|
const [generatedTranscriptId, setGeneratedTranscriptId] = useState<number>();
|
||||||
|
const [advanceConfig, setAdvanceConfig] = useState(false);
|
||||||
|
const [language, setLanguage] = useState("id");
|
||||||
|
|
||||||
|
const formOptions = { resolver: zodResolver(createContentOnlineMediaSchema) };
|
||||||
|
type CreateContentSchema = z.infer<typeof createContentOnlineMediaSchema>;
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
setValue,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<CreateContentSchema>(formOptions);
|
||||||
|
|
||||||
|
const handleFileChange = (event: any) => {
|
||||||
|
if (files.length < 1) {
|
||||||
|
const newFiles: FileList | null = event.target.files;
|
||||||
|
if (newFiles) {
|
||||||
|
const allowedExtensions = [".mp3"];
|
||||||
|
let temp: File[] = [...files]; // Salin file-file yang sudah ada
|
||||||
|
for (let i = 0; i < newFiles.length; i++) {
|
||||||
|
const file = newFiles[i];
|
||||||
|
const fileExtension = file.name.split(".").pop()?.toLowerCase();
|
||||||
|
if (
|
||||||
|
fileExtension &&
|
||||||
|
allowedExtensions.includes(`.${fileExtension}`)
|
||||||
|
) {
|
||||||
|
temp.push(file);
|
||||||
|
} else {
|
||||||
|
alert("The supported File formats are .mp3");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFiles(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeFile = (name: string) => {
|
||||||
|
const arrayFile: File[] = [];
|
||||||
|
for (const element of files) {
|
||||||
|
if (element.name !== name) {
|
||||||
|
arrayFile.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFiles(arrayFile);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerateTranscript = (data: any) => {
|
||||||
|
if (files.length > 0) {
|
||||||
|
doGenerate(data);
|
||||||
|
setHandleFileStatus("");
|
||||||
|
} else {
|
||||||
|
setHandleFileStatus("Required");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const doGenerate = async (data: any) => {
|
||||||
|
loading();
|
||||||
|
let formData = new FormData();
|
||||||
|
console.log("data.", data.prompt);
|
||||||
|
formData.append("filename", files[0]);
|
||||||
|
formData.append("title", data.title);
|
||||||
|
formData.append("prompt", data.prompt);
|
||||||
|
formData.append("language", language);
|
||||||
|
formData.append("clientId", "humasClientIdTest");
|
||||||
|
formData.append("createdBy", "123");
|
||||||
|
|
||||||
|
const res = await generateSpeechToText(formData);
|
||||||
|
setGeneratedTranscriptId(res?.data?.data?.id);
|
||||||
|
close();
|
||||||
|
initFetch(res?.data?.data?.id);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkTranscriptStatus = async (id: number) => {
|
||||||
|
delay(7000).then(() => {
|
||||||
|
initFetch(id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initFetch = async (id?: number) => {
|
||||||
|
if (id) {
|
||||||
|
const res = await getTranscriptById(id);
|
||||||
|
console.log("stat", res?.data?.data);
|
||||||
|
if (res?.data?.data?.status === 0) {
|
||||||
|
setDetailTranscript("");
|
||||||
|
checkTranscriptStatus(id);
|
||||||
|
} else {
|
||||||
|
setDetailTranscript(res?.data?.data.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense>
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="w-full text-sm">
|
||||||
|
<form method="POST" onSubmit={handleSubmit(handleGenerateTranscript)}>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<p className="font-bold">Upload an Audio File</p>
|
||||||
|
<p>The supported File formats are .mp3</p>
|
||||||
|
<p>Max: 25mb</p>
|
||||||
|
{files.length < 1 && (
|
||||||
|
<label htmlFor="dropzone-file">
|
||||||
|
<div className="mt-3 w-[150px] flex justify-center items-center bg-success p-2 text-white rounded-lg gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
id="dropzone-file"
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
accept=".mp3"
|
||||||
|
className="hidden"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
/>
|
||||||
|
Add file <AddIcon />
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
{handleFileStatus !== "" && files.length < 1 && (
|
||||||
|
<p className="text-tiny text-danger">{handleFileStatus}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{files.length > 0 &&
|
||||||
|
files.map((list) => (
|
||||||
|
<div
|
||||||
|
key={list.name}
|
||||||
|
className="w-[350px] mt-3 flex flex-row items-center justify-between border-1 rounded-lg p-2"
|
||||||
|
>
|
||||||
|
{list.name}
|
||||||
|
<a
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => removeFile(list.name)}
|
||||||
|
>
|
||||||
|
<TimesIcon />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className="w-full md:w-1/2 pt-2 pb-4">
|
||||||
|
<p className="text-black text-sm mb-1">Language</p>
|
||||||
|
<Select
|
||||||
|
label=""
|
||||||
|
labelPlacement="outside"
|
||||||
|
selectedKeys={[language]}
|
||||||
|
className="w-full"
|
||||||
|
variant="bordered"
|
||||||
|
onChange={(e) =>
|
||||||
|
e.target.value == "" ? "" : setLanguage(e.target.value)
|
||||||
|
}
|
||||||
|
renderValue={(items) => {
|
||||||
|
return items.map((item) => (
|
||||||
|
<span
|
||||||
|
key={item.props?.value}
|
||||||
|
className="text-black text-xs"
|
||||||
|
>
|
||||||
|
{item.textValue}
|
||||||
|
</span>
|
||||||
|
));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectSection>
|
||||||
|
<SelectItem key="id">Indonesia</SelectItem>
|
||||||
|
<SelectItem key="en">English</SelectItem>
|
||||||
|
</SelectSection>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="w-full md:w-1/2 mt-3">
|
||||||
|
<p className="text-black text-sm mb-1">
|
||||||
|
Title<span className="text-red-500">*</span>
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="keyword"
|
||||||
|
label=""
|
||||||
|
className="mb-3"
|
||||||
|
placeholder="Enter title"
|
||||||
|
labelPlacement="outside"
|
||||||
|
variant="bordered"
|
||||||
|
{...register("title")}
|
||||||
|
/>
|
||||||
|
{errors.title?.message && (
|
||||||
|
<p className="text-tiny text-danger">
|
||||||
|
{errors.title?.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full md:w-1/2 mt-3">
|
||||||
|
<p className="text-black text-sm mb-1">
|
||||||
|
Keyword<span className="text-red-500">*</span>
|
||||||
|
</p>
|
||||||
|
<Textarea
|
||||||
|
label=""
|
||||||
|
id="seo"
|
||||||
|
variant="bordered"
|
||||||
|
disableAnimation
|
||||||
|
disableAutosize
|
||||||
|
minRows={10}
|
||||||
|
placeholder="Write your text"
|
||||||
|
{...register("prompt")}
|
||||||
|
/>
|
||||||
|
{errors.prompt?.message && (
|
||||||
|
<p className="text-tiny text-danger">
|
||||||
|
{errors.prompt?.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between mt-3">
|
||||||
|
<Button className="w-[200px]" color="primary" type="submit">
|
||||||
|
Generate
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{generatedTranscriptId && (
|
||||||
|
<div className="flex flex-col gap-3 my-3">
|
||||||
|
<p className="text-xl font-semibold">Result</p>
|
||||||
|
{detailTranscript === "" ? (
|
||||||
|
<CircularProgress />
|
||||||
|
) : (
|
||||||
|
<p>{detailTranscript}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{detailTranscript !== "" && (
|
||||||
|
<Button
|
||||||
|
className="w-[200px]"
|
||||||
|
color="primary"
|
||||||
|
onPress={() => {
|
||||||
|
generatedTranscriptId &&
|
||||||
|
props.generateArticleFromTranscript(generatedTranscriptId);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Generate Article
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -346,6 +346,7 @@ export default function FormArticle() {
|
||||||
<input
|
<input
|
||||||
id="dropzone-file"
|
id="dropzone-file"
|
||||||
type="file"
|
type="file"
|
||||||
|
className="hidden"
|
||||||
onChange={handleImageChange}
|
onChange={handleImageChange}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,9 @@ export const AdminLayout = ({ children }: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<div className="flex items-center justify-between h-screen p-1 md:p-4">
|
<div className="h-screen flex items-center flex-row p-1 md:p-4">
|
||||||
<Sidebar sidebarData={isOpen} updateSidebarData={updateSidebarData} />
|
<Sidebar sidebarData={isOpen} updateSidebarData={updateSidebarData} />
|
||||||
<div className={`h-full w-full flex flex-col gap-4`}>
|
<div className={`w-full h-full flex flex-col gap-4`}>
|
||||||
<Breadcrumb />
|
<Breadcrumb />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -221,114 +221,66 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`hidden md:flex h-full ${
|
className={`hidden md:flex h-full flex-grow ${
|
||||||
isOpen ? "min-w-[290px]" : "min-w-[80px]"
|
isOpen ? "min-w-[290px]" : "min-w-[80px]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`will-change relative flex h-full flex-col rounded-lg p-4 mb-0 bg-gray-100 dark:bg-stone-950 z-40 transition-width !ease-in-out ${
|
className={` flex h-full flex-col rounded-lg p-4 mb-0 bg-gray-100 dark:bg-stone-950 z-40 transition-width !ease-in-out justify-between ${
|
||||||
isOpen ? "w-[288px]" : "w-[80px]"
|
isOpen ? "w-[288px]" : "w-[80px]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{!isOpen && (
|
<div>
|
||||||
<div className="w-full flex justify-center items-center">
|
{!isOpen && (
|
||||||
<button
|
<div className="w-full flex justify-center items-center">
|
||||||
className="w-5 h-5 mb-3 text-zinc-400 dark:text-zinc-400 z-50 border-1 border-zinc-400 rounded-full flex justify-center items-center"
|
<button
|
||||||
onClick={toggleSidebar}
|
className="w-5 h-5 mb-3 text-zinc-400 dark:text-zinc-400 z-50 border-1 border-zinc-400 rounded-full flex justify-center items-center"
|
||||||
>
|
onClick={toggleSidebar}
|
||||||
<ChevronRightIcon />
|
>
|
||||||
</button>
|
<ChevronRightIcon />
|
||||||
</div>
|
</button>
|
||||||
)}
|
</div>
|
||||||
<div
|
|
||||||
className={`flex ${
|
|
||||||
isOpen ? "justify-between" : "justify-center"
|
|
||||||
} w-full items-center px-2`}
|
|
||||||
>
|
|
||||||
<div className="flex flex-row items-center gap-3 font-bold">
|
|
||||||
<img src="/logohumas.png" className="w-20" />
|
|
||||||
{/* {isOpen && <span>ACME</span>} */}
|
|
||||||
</div>
|
|
||||||
{isOpen && (
|
|
||||||
<button
|
|
||||||
className="w-5 h-5 text-zinc-400 dark:text-zinc-400 z-50 border-1 border-zinc-400 rounded-full flex justify-center items-center"
|
|
||||||
onClick={toggleSidebar}
|
|
||||||
>
|
|
||||||
<ChevronLeftIcon />
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
<div
|
||||||
{/* <div className={`flex ${isOpen ? "justify-between" : "justify-center"} w-full items-center px-2 mt-4 mb-4`}>
|
className={`flex ${
|
||||||
<div className='flex flex-row items-center gap-3 font-bold'>
|
isOpen ? "justify-between" : "justify-center"
|
||||||
<Dropdown placement="bottom-start">
|
} w-full items-center px-2`}
|
||||||
<DropdownTrigger>
|
>
|
||||||
<User
|
<Link
|
||||||
as="button"
|
href="/"
|
||||||
avatarProps={{
|
className="flex flex-row items-center gap-3 font-bold"
|
||||||
isBordered: true,
|
>
|
||||||
src: "https://i.pravatar.cc/150?u=a042581f4e29026024d",
|
<img src="/logohumas.png" className="w-20" />
|
||||||
}}
|
{/* {isOpen && <span>ACME</span>} */}
|
||||||
classNames={{
|
</Link>
|
||||||
base: `transition-transform gap-0`,
|
{isOpen && (
|
||||||
wrapper: `${isOpen && "pl-3"}`
|
<button
|
||||||
}}
|
className="w-5 h-5 text-zinc-400 dark:text-zinc-400 z-50 border-1 border-zinc-400 rounded-full flex justify-center items-center"
|
||||||
description={isOpen && "@tonyreichert"}
|
onClick={toggleSidebar}
|
||||||
name={isOpen && "Tony Reichert"}
|
>
|
||||||
/>
|
<ChevronLeftIcon />
|
||||||
</DropdownTrigger>
|
</button>
|
||||||
<DropdownMenu aria-label="User Actions" variant="flat">
|
)}
|
||||||
<DropdownItem key="profile" className="h-14 gap-2">
|
</div>
|
||||||
<p className="font-bold">Signed in as</p>
|
|
||||||
<p className="font-bold">@tonyreichert</p>
|
<SidebarMenu>
|
||||||
</DropdownItem>
|
{sideBarDummyData
|
||||||
<DropdownItem key="team_settings">Profile Settings</DropdownItem>
|
? sideBarDummyData?.map((list: any, index: number) =>
|
||||||
<DropdownItem key="analytics">
|
list.isGroup ? (
|
||||||
Analytics
|
<p
|
||||||
</DropdownItem>
|
key={list}
|
||||||
<DropdownItem key="help_and_feedback">
|
className={`font-bold mr-4 ${
|
||||||
Help & Feedback
|
!isOpen ? "text-center" : ""
|
||||||
</DropdownItem>
|
}`}
|
||||||
<DropdownItem key="logout" color="danger">
|
>
|
||||||
Log Out
|
{isOpen ? list.name : "..."}
|
||||||
</DropdownItem>
|
</p>
|
||||||
</DropdownMenu>
|
) : list.childMenu?.length < 1 ? (
|
||||||
</Dropdown>
|
<>
|
||||||
</div>
|
{isOpen ? (
|
||||||
</div> */}
|
|
||||||
<SidebarMenu>
|
|
||||||
{sideBarDummyData
|
|
||||||
? sideBarDummyData?.map((list: any, index: number) =>
|
|
||||||
list.isGroup ? (
|
|
||||||
<p
|
|
||||||
key={list}
|
|
||||||
className={`font-bold mr-4 ${!isOpen ? "text-center" : ""}`}
|
|
||||||
>
|
|
||||||
{isOpen ? list.name : "..."}
|
|
||||||
</p>
|
|
||||||
) : list.childMenu?.length < 1 ? (
|
|
||||||
<>
|
|
||||||
{isOpen ? (
|
|
||||||
<Link key={list.id} href={list.modulePathUrl}>
|
|
||||||
<div
|
|
||||||
className={`px-3.5 py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row gap-2 ${
|
|
||||||
pathname.includes(list.modulePathUrl)
|
|
||||||
? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold"
|
|
||||||
: "text-zinc-600 dark:text-zinc-400"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{list.icon} {isOpen && list.name}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<Tooltip
|
|
||||||
content={list.name}
|
|
||||||
placement="right"
|
|
||||||
delay={0}
|
|
||||||
closeDelay={0}
|
|
||||||
>
|
|
||||||
<Link key={list.id} href={list.modulePathUrl}>
|
<Link key={list.id} href={list.modulePathUrl}>
|
||||||
<div
|
<div
|
||||||
className={`py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row justify-center gap-1 ${
|
className={`px-3.5 py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row gap-2 ${
|
||||||
pathname.includes(list.modulePathUrl)
|
pathname.includes(list.modulePathUrl)
|
||||||
? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold"
|
? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold"
|
||||||
: "text-zinc-600 dark:text-zinc-400"
|
: "text-zinc-600 dark:text-zinc-400"
|
||||||
|
|
@ -337,32 +289,53 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
|
||||||
{list.icon} {isOpen && list.name}
|
{list.icon} {isOpen && list.name}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</Tooltip>
|
) : (
|
||||||
)}
|
<Tooltip
|
||||||
</>
|
content={list.name}
|
||||||
) : (
|
placement="right"
|
||||||
<SidebarCollapseItems
|
delay={0}
|
||||||
key={list.id}
|
closeDelay={0}
|
||||||
title={list.name}
|
>
|
||||||
isActive={pathname.includes(list.modulePathUrl)}
|
<Link key={list.id} href={list.modulePathUrl}>
|
||||||
icon={list.icon}
|
<div
|
||||||
items={[
|
className={`py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row justify-center gap-1 ${
|
||||||
list?.childMenu?.map((item: any) => (
|
pathname.includes(list.modulePathUrl)
|
||||||
<SidebarCollapseSubItems
|
? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold"
|
||||||
key={item.id}
|
: "text-zinc-600 dark:text-zinc-400"
|
||||||
title={item?.name}
|
}`}
|
||||||
isActive={pathname.includes(item.modulePathUrl)}
|
>
|
||||||
isParentActive={pathname.includes(list.modulePathUrl)}
|
{list.icon} {isOpen && list.name}
|
||||||
path={item.modulePathUrl}
|
</div>
|
||||||
icon={item.icon}
|
</Link>
|
||||||
/>
|
</Tooltip>
|
||||||
)),
|
)}
|
||||||
]}
|
</>
|
||||||
/>
|
) : (
|
||||||
|
<SidebarCollapseItems
|
||||||
|
key={list.id}
|
||||||
|
title={list.name}
|
||||||
|
isActive={pathname.includes(list.modulePathUrl)}
|
||||||
|
icon={list.icon}
|
||||||
|
items={[
|
||||||
|
list?.childMenu?.map((item: any) => (
|
||||||
|
<SidebarCollapseSubItems
|
||||||
|
key={item.id}
|
||||||
|
title={item?.name}
|
||||||
|
isActive={pathname.includes(item.modulePathUrl)}
|
||||||
|
isParentActive={pathname.includes(
|
||||||
|
list.modulePathUrl
|
||||||
|
)}
|
||||||
|
path={item.modulePathUrl}
|
||||||
|
icon={item.icon}
|
||||||
|
/>
|
||||||
|
)),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
: ""}
|
||||||
: ""}
|
</SidebarMenu>
|
||||||
</SidebarMenu>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`mt-12 p-2 flex ${
|
className={`mt-12 p-2 flex ${
|
||||||
isOpen ? "justify-start ml-2" : "justify-center"
|
isOpen ? "justify-start ml-2" : "justify-center"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
"use client";
|
||||||
|
import {
|
||||||
|
TableCell,
|
||||||
|
TableRow,
|
||||||
|
Table,
|
||||||
|
TableHeader,
|
||||||
|
TableColumn,
|
||||||
|
TableBody,
|
||||||
|
Pagination,
|
||||||
|
Checkbox,
|
||||||
|
Dropdown,
|
||||||
|
DropdownTrigger,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownItem,
|
||||||
|
Input,
|
||||||
|
} from "@nextui-org/react";
|
||||||
|
import { Button } from "@nextui-org/button";
|
||||||
|
import React, {
|
||||||
|
Key,
|
||||||
|
Suspense,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { SearchIcon, UserIcon } from "@/components/icons";
|
||||||
|
|
||||||
|
import { close, loading } from "@/config/swal";
|
||||||
|
|
||||||
|
import {
|
||||||
|
getListArticleDraft,
|
||||||
|
getListTranscript,
|
||||||
|
} from "@/service/generate-article";
|
||||||
|
import { textEllipsis } from "@/utils/global";
|
||||||
|
import { getListArticle } from "@/service/article";
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ name: "No", uid: "no" },
|
||||||
|
{ name: "Main Keyword", uid: "mainKeyword" },
|
||||||
|
{ name: "Additional Keywords", uid: "additionalKeywords" },
|
||||||
|
{ name: "Article", uid: "articleBody" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function ArticleDraftTable(props: {
|
||||||
|
articleContent: (data: string) => void;
|
||||||
|
}) {
|
||||||
|
const [selectedTableRow, setSelectedTableRow] = useState<any>(new Set([""]));
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const [totalPage, setTotalPage] = useState(1);
|
||||||
|
const [dataTableTranscript, setDataTableTranscript] = useState<any[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initFetch();
|
||||||
|
}, [page]);
|
||||||
|
|
||||||
|
const initFetch = async () => {
|
||||||
|
loading();
|
||||||
|
const res = await getListArticleDraft({
|
||||||
|
query: search,
|
||||||
|
page: page,
|
||||||
|
userId: "humasClientIdtest",
|
||||||
|
limit: 10,
|
||||||
|
status: [0, 2],
|
||||||
|
isSingle: true,
|
||||||
|
createdBy: "123123",
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
column: "id",
|
||||||
|
sort: "desc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
close();
|
||||||
|
setTotalPage(Math.ceil(res?.data?.total / 10));
|
||||||
|
const data = res?.data?.data;
|
||||||
|
const startIndex = 10 * (page - 1);
|
||||||
|
let iterate = 0;
|
||||||
|
const newData = data?.map((value: any) => {
|
||||||
|
iterate++;
|
||||||
|
value.no = startIndex + iterate;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
setDataTableTranscript(newData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCell = useCallback((article: any, columnKey: any) => {
|
||||||
|
const cellValue = article[columnKey];
|
||||||
|
|
||||||
|
switch (columnKey) {
|
||||||
|
case "articleBody":
|
||||||
|
return <p>{textEllipsis(cellValue, 200)}</p>;
|
||||||
|
default:
|
||||||
|
return cellValue;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSelected = (e: any) => {
|
||||||
|
setSelectedTableRow(new Set([e.currentKey]));
|
||||||
|
const now = findDataTable(e.currentKey);
|
||||||
|
props.articleContent(now?.articleBody ? now?.articleBody : "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
initFetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
const findDataTable = (data: any) => {
|
||||||
|
const temp = dataTableTranscript.find((item: any) => item.id == data);
|
||||||
|
return temp;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense>
|
||||||
|
<div className="mt-3 flex flex-row gap-3">
|
||||||
|
<div className="w-full">
|
||||||
|
<Input
|
||||||
|
type="title"
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
label=""
|
||||||
|
variant="bordered"
|
||||||
|
placeholder="Search"
|
||||||
|
labelPlacement="outside"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button color="primary" onPress={handleSearch}>
|
||||||
|
Search
|
||||||
|
<SearchIcon />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="flex flex-col items-center rounded-2xl">
|
||||||
|
<Table
|
||||||
|
selectionMode="multiple"
|
||||||
|
aria-label="transcript-table"
|
||||||
|
className="rounded-xl"
|
||||||
|
classNames={{
|
||||||
|
th: "bg-white text-black border-b-1",
|
||||||
|
base: "bg-white",
|
||||||
|
wrapper: "min-h-[50px] bg-transpararent text-black",
|
||||||
|
}}
|
||||||
|
checkboxesProps={{
|
||||||
|
classNames: {
|
||||||
|
wrapper:
|
||||||
|
"after:bg-foreground after:text-background text-background",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
selectedKeys={selectedTableRow}
|
||||||
|
onSelectionChange={handleSelected}
|
||||||
|
>
|
||||||
|
<TableHeader columns={columns}>
|
||||||
|
{(column) => (
|
||||||
|
<TableColumn key={column.uid}>{column.name}</TableColumn>
|
||||||
|
)}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody
|
||||||
|
items={dataTableTranscript}
|
||||||
|
emptyContent={"No data to display."}
|
||||||
|
>
|
||||||
|
{(item) => (
|
||||||
|
<TableRow key={item.id}>
|
||||||
|
{(columnKey) => (
|
||||||
|
<TableCell>{renderCell(item, columnKey)}</TableCell>
|
||||||
|
)}
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<div className="mt-2">
|
||||||
|
<Pagination
|
||||||
|
isCompact
|
||||||
|
showControls
|
||||||
|
showShadow
|
||||||
|
color="primary"
|
||||||
|
classNames={{
|
||||||
|
base: "bg-transparent",
|
||||||
|
wrapper: "bg-transparent",
|
||||||
|
}}
|
||||||
|
page={page}
|
||||||
|
total={totalPage}
|
||||||
|
onChange={(page) => setPage(page)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
"use client";
|
||||||
|
import {
|
||||||
|
TableCell,
|
||||||
|
TableRow,
|
||||||
|
Table,
|
||||||
|
TableHeader,
|
||||||
|
TableColumn,
|
||||||
|
TableBody,
|
||||||
|
Pagination,
|
||||||
|
Checkbox,
|
||||||
|
Dropdown,
|
||||||
|
DropdownTrigger,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownItem,
|
||||||
|
Input,
|
||||||
|
} from "@nextui-org/react";
|
||||||
|
import { Button } from "@nextui-org/button";
|
||||||
|
import React, {
|
||||||
|
Key,
|
||||||
|
Suspense,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { SearchIcon, UserIcon } from "@/components/icons";
|
||||||
|
|
||||||
|
import { close, loading } from "@/config/swal";
|
||||||
|
|
||||||
|
import { getListTranscript } from "@/service/generate-article";
|
||||||
|
import { textEllipsis } from "@/utils/global";
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ name: "No", uid: "no" },
|
||||||
|
{ name: "Title", uid: "title" },
|
||||||
|
{ name: "Prompt", uid: "prompt" },
|
||||||
|
{ name: "Content", uid: "content" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function TranscriptDraftTable(props: {
|
||||||
|
initTranscriptId?: string;
|
||||||
|
transriptContent: (data: string) => void;
|
||||||
|
}) {
|
||||||
|
const [selectedTableRow, setSelectedTableRow] = useState<any>(new Set([""]));
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const [totalPage, setTotalPage] = useState(1);
|
||||||
|
const [dataTableTranscript, setDataTableTranscript] = useState<any[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedTableRow(new Set([props.initTranscriptId]));
|
||||||
|
const now = findDataTable(Array.from(selectedTableRow)[0]);
|
||||||
|
props.transriptContent(now?.content ? now?.content : "");
|
||||||
|
}, [dataTableTranscript, props.initTranscriptId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initFetch();
|
||||||
|
}, [page]);
|
||||||
|
|
||||||
|
const initFetch = async () => {
|
||||||
|
loading();
|
||||||
|
const res = await getListTranscript({
|
||||||
|
query: search || "",
|
||||||
|
page: page,
|
||||||
|
clientId: "humasClientIdTest",
|
||||||
|
limit: 10,
|
||||||
|
createdBy: null,
|
||||||
|
|
||||||
|
status: [0, 2],
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
column: "id",
|
||||||
|
sort: "desc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
close();
|
||||||
|
setTotalPage(Math.ceil(res?.data?.total / 10));
|
||||||
|
const data = res?.data?.data;
|
||||||
|
const startIndex = 10 * (page - 1);
|
||||||
|
let iterate = 0;
|
||||||
|
const newData = data?.map((value: any) => {
|
||||||
|
iterate++;
|
||||||
|
value.no = startIndex + iterate;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
setDataTableTranscript(newData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCell = useCallback((article: any, columnKey: any) => {
|
||||||
|
const cellValue = article[columnKey];
|
||||||
|
|
||||||
|
switch (columnKey) {
|
||||||
|
case "content":
|
||||||
|
return <p>{textEllipsis(cellValue, 200)}</p>;
|
||||||
|
default:
|
||||||
|
return cellValue;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSelected = (e: any) => {
|
||||||
|
setSelectedTableRow(new Set([e.currentKey]));
|
||||||
|
const now = findDataTable(e.currentKey);
|
||||||
|
props.transriptContent(now?.content ? now?.content : "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
initFetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
const findDataTable = (data: any) => {
|
||||||
|
const temp = dataTableTranscript.find((item: any) => item.id == data);
|
||||||
|
return temp;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense>
|
||||||
|
<div className="mt-3 flex flex-row gap-3">
|
||||||
|
<div className="w-full">
|
||||||
|
<Input
|
||||||
|
type="title"
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
label=""
|
||||||
|
variant="bordered"
|
||||||
|
placeholder="Search"
|
||||||
|
labelPlacement="outside"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button color="primary" onPress={handleSearch}>
|
||||||
|
Search
|
||||||
|
<SearchIcon />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="flex flex-col items-center rounded-2xl">
|
||||||
|
<Table
|
||||||
|
selectionMode="multiple"
|
||||||
|
aria-label="transcript-table"
|
||||||
|
className="rounded-xl"
|
||||||
|
classNames={{
|
||||||
|
th: "bg-white text-black border-b-1",
|
||||||
|
base: "bg-white",
|
||||||
|
wrapper: "min-h-[50px] bg-transpararent text-black",
|
||||||
|
}}
|
||||||
|
checkboxesProps={{
|
||||||
|
classNames: {
|
||||||
|
wrapper:
|
||||||
|
"after:bg-foreground after:text-background text-background",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
selectedKeys={selectedTableRow}
|
||||||
|
onSelectionChange={handleSelected}
|
||||||
|
>
|
||||||
|
<TableHeader columns={columns}>
|
||||||
|
{(column) => (
|
||||||
|
<TableColumn key={column.uid}>{column.name}</TableColumn>
|
||||||
|
)}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody
|
||||||
|
items={dataTableTranscript}
|
||||||
|
emptyContent={"No data to display."}
|
||||||
|
>
|
||||||
|
{(item) => (
|
||||||
|
<TableRow key={item.id}>
|
||||||
|
{(columnKey) => (
|
||||||
|
<TableCell>{renderCell(item, columnKey)}</TableCell>
|
||||||
|
)}
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<div className="mt-2">
|
||||||
|
<Pagination
|
||||||
|
isCompact
|
||||||
|
showControls
|
||||||
|
showShadow
|
||||||
|
color="primary"
|
||||||
|
classNames={{
|
||||||
|
base: "bg-transparent",
|
||||||
|
wrapper: "bg-transparent",
|
||||||
|
}}
|
||||||
|
page={page}
|
||||||
|
total={totalPage}
|
||||||
|
onChange={(page) => setPage(page)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -34,7 +34,7 @@ export const Breadcrumb = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-[100px] gap-0 grid rounded-lg border-small">
|
<div className="h-[100px] rounded-lg border-small">
|
||||||
<div className="px-4 md:px-8">
|
<div className="px-4 md:px-8">
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div className="flex flex-row justify-between items-center">
|
||||||
<div className="pt-1">
|
<div className="pt-1">
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,8 @@
|
||||||
"tailwind-variants": "^0.1.18",
|
"tailwind-variants": "^0.1.18",
|
||||||
"tailwindcss": "3.3.5",
|
"tailwindcss": "3.3.5",
|
||||||
"typescript": "5.0.4",
|
"typescript": "5.0.4",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8",
|
||||||
|
"zustand": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@aashutoshrathi/word-wrap": {
|
"node_modules/@aashutoshrathi/word-wrap": {
|
||||||
|
|
@ -7290,6 +7291,34 @@
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zustand": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-pRET7Lao2z+n5R/HduXMio35TncTlSW68WsYBq2Lg1ASspsNGjpwLAsij3RpouyV6+kHMwwwzP0bZPD70/Jx/w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": ">=18.0.0",
|
||||||
|
"immer": ">=9.0.6",
|
||||||
|
"react": ">=18.0.0",
|
||||||
|
"use-sync-external-store": ">=1.2.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"immer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"use-sync-external-store": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@
|
||||||
"tailwind-variants": "^0.1.18",
|
"tailwind-variants": "^0.1.18",
|
||||||
"tailwindcss": "3.3.5",
|
"tailwindcss": "3.3.5",
|
||||||
"typescript": "5.0.4",
|
"typescript": "5.0.4",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8",
|
||||||
|
"zustand": "^5.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
import { httpGet, httpPost } from "@/services/http-config/disestages-services";
|
||||||
|
|
||||||
|
interface GenerateKeywordsAndTitleRequestData {
|
||||||
|
keyword: string;
|
||||||
|
style: string;
|
||||||
|
website: string;
|
||||||
|
connectToWeb: boolean;
|
||||||
|
lang: string;
|
||||||
|
pointOfView: string;
|
||||||
|
clientId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GenerateArticleRequest {
|
||||||
|
advConfig: string;
|
||||||
|
style: string;
|
||||||
|
website: string;
|
||||||
|
connectToWeb: boolean;
|
||||||
|
lang: string;
|
||||||
|
pointOfView: string;
|
||||||
|
title: string;
|
||||||
|
imageSource: string;
|
||||||
|
mainKeyword: string;
|
||||||
|
additionalKeywords: string;
|
||||||
|
targetCountry: null;
|
||||||
|
articleSize: string;
|
||||||
|
projectId: number;
|
||||||
|
createdBy: string;
|
||||||
|
clientId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type BulkArticleRequest = {
|
||||||
|
style: string;
|
||||||
|
website: string;
|
||||||
|
connectToWeb: boolean;
|
||||||
|
lang: string;
|
||||||
|
pointOfView: string;
|
||||||
|
imageSource: string;
|
||||||
|
targetCountry: null;
|
||||||
|
articleSize: string;
|
||||||
|
projectId: number;
|
||||||
|
data: { title: string; mainKeyword: string; additionalKeywords: string }[];
|
||||||
|
createdBy: string;
|
||||||
|
clientId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ContentRewriteRequest {
|
||||||
|
advConfig: string;
|
||||||
|
context: string | null;
|
||||||
|
style: string;
|
||||||
|
sentiment: string;
|
||||||
|
clientId: string;
|
||||||
|
createdBy: string;
|
||||||
|
contextType: string;
|
||||||
|
urlContext: string | null;
|
||||||
|
lang: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ContentBankRequest = {
|
||||||
|
query: string;
|
||||||
|
page: number;
|
||||||
|
userId: string;
|
||||||
|
limit: number;
|
||||||
|
status: number[];
|
||||||
|
isSingle: boolean;
|
||||||
|
createdBy: string;
|
||||||
|
sort: { column: string; sort: string }[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getGenerateTitle(
|
||||||
|
data: GenerateKeywordsAndTitleRequestData
|
||||||
|
) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpPost("ai-writer/generate-title", headers, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGenerateKeywords(
|
||||||
|
data: GenerateKeywordsAndTitleRequestData
|
||||||
|
) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpPost("ai-writer/generate-keywords", headers, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateDataArticle(data: GenerateArticleRequest) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpPost("ai-writer/save-article", headers, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function approveArticle(props: { id: number[] }) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpPost("ai-writer/approve-article", headers, props);
|
||||||
|
}
|
||||||
|
export async function rejectArticle(props: { id: number[] }) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpPost("ai-writer/reject-article", headers, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGenerateTopicKeywords(data: {
|
||||||
|
keyword: string;
|
||||||
|
count: number;
|
||||||
|
}) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpPost("ai-writer/generate-topic-keywords", headers, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveBulkArticle(data: BulkArticleRequest) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpPost("ai-writer/save-bulk-article", headers, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDetailArticle(id: number) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpGet(`ai-writer/article/findArticleById/${id}`, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateSpeechToText(data: any) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "multipart/form-data",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpPost("ai-writer/speech-to-text", headers, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTranscriptById(id: number) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpGet(`ai-writer/speech-to-text/findById/${id}`, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGenerateRewriter(data: ContentRewriteRequest) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpPost("ai-writer/create-rewriter", headers, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getListTranscript(data: any) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpPost("ai-writer/speech-to-text/datatable", headers, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getListArticleDraft(data: ContentBankRequest) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpPost("ai-writer/article/datatable", headers, data);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const baseURL = "https://disestages.com/api";
|
||||||
|
|
||||||
|
const axiosDisestagesInstance = axios.create({
|
||||||
|
baseURL,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default axiosDisestagesInstance;
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import axiosDisestagesInstance from "./disestages-instance";
|
||||||
|
|
||||||
|
export async function httpPost(pathUrl: any, headers: any, data?: any) {
|
||||||
|
const response = await axiosDisestagesInstance
|
||||||
|
.post(pathUrl, data, { headers })
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
return error.response;
|
||||||
|
});
|
||||||
|
console.log("Response base svc : ", response);
|
||||||
|
if (response?.status == 200 || response?.status == 201) {
|
||||||
|
return {
|
||||||
|
error: false,
|
||||||
|
message: "success",
|
||||||
|
data: response?.data,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
message: response?.data?.message || response?.data || null,
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function httpGet(pathUrl: any, headers: any) {
|
||||||
|
const response = await axiosDisestagesInstance
|
||||||
|
.get(pathUrl, { headers })
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
return error.response;
|
||||||
|
});
|
||||||
|
console.log("Response base svc : ", response);
|
||||||
|
if (response?.status == 200 || response?.status == 201) {
|
||||||
|
return {
|
||||||
|
error: false,
|
||||||
|
message: "success",
|
||||||
|
data: response?.data,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
message: response?.data?.message || response?.data || null,
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
interface targetStore {
|
||||||
|
articleIds: { singleArticle: number[]; bulkArticle: number[] };
|
||||||
|
setArticleIds: (newTarget: {
|
||||||
|
singleArticle: number[];
|
||||||
|
bulkArticle: number[];
|
||||||
|
}) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getInitialTarget = () => {
|
||||||
|
if (typeof localStorage !== "undefined") {
|
||||||
|
const stored = localStorage.getItem("generated-article");
|
||||||
|
const initial = stored
|
||||||
|
? JSON.parse(stored)
|
||||||
|
: { singleArticle: [], bulkArticle: [] };
|
||||||
|
return initial;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const generatedArticleIds = create<targetStore>((set) => ({
|
||||||
|
articleIds: getInitialTarget(),
|
||||||
|
setArticleIds: (newTarget: {
|
||||||
|
singleArticle: number[];
|
||||||
|
bulkArticle: number[];
|
||||||
|
}) => {
|
||||||
|
localStorage.setItem("generated-article", JSON.stringify(newTarget));
|
||||||
|
set({ articleIds: newTarget });
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default generatedArticleIds;
|
||||||
|
|
@ -70,3 +70,25 @@ const LoadScript = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LoadScript;
|
export default LoadScript;
|
||||||
|
|
||||||
|
export function delay(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function textEllipsis(
|
||||||
|
str: string,
|
||||||
|
maxLength: number,
|
||||||
|
{ side = "end", ellipsis = "..." } = {}
|
||||||
|
) {
|
||||||
|
if (str !== undefined && str?.length > maxLength) {
|
||||||
|
switch (side) {
|
||||||
|
case "start":
|
||||||
|
return ellipsis + str.slice(-(maxLength - ellipsis.length));
|
||||||
|
|
||||||
|
case "end":
|
||||||
|
default:
|
||||||
|
return str.slice(0, maxLength - ellipsis.length) + ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue