import {createContext} from "@/utils/context"; import {generateUUID} from "@/utils/uuids"; import {getCurrentTimeIso8601} from "@/utils/dates"; import {useContext} from "preact/hooks"; import {Settings} from "@/contexts/settings"; import {feedPosts, feedTopic} from "@/utils/model"; import {LogAction} from "@/contexts/log"; export type Post = { user: string; date: string; // date from AI if generated by AI tokens, ISO 8601, YYYY-MM-DDTHH:MM:SS generationDate: string | null; //"null" means not generated by AI, ISO 8601, YYYY-MM-DDTHH:MM:SS content: string; } export type Topic = { id: string; // UUID title: string; posts: Post[]; } export type TopicsContext = { generation: "done" | "pending" | "error"; import: "done" | "error"; topics: Topic[]; } const itemKey = "topics"; export const topicsCtx = createContext({ initialValue: () => { const storedTopics = localStorage.getItem(itemKey); return { generation: "done", import: "done", topics: storedTopics ? JSON.parse(storedTopics) as Topic[] : [] } as TopicsContext; }, controllers: (topicsContext: TopicsContext, setTopicsContext) => ({ effect: () => { // console.log("**") localStorage.setItem(itemKey, JSON.stringify(topicsContext.topics)); }, actions: { reset: (): void => { setTopicsContext({ generation: "done", import: "done", topics: [] }); }, addTopic: (user: string, title: string, text: string): string => { const id = generateUUID(); setTopicsContext({ ...topicsContext, topics: [...topicsContext.topics, { id: id, title, posts: [{ user: user, date: getCurrentTimeIso8601(), generationDate: null, content: text, }] }], }); return id; }, deleteTopic: (topicId: string): void => { setTopicsContext({ ...topicsContext, topics: topicsContext.topics.filter((topic) => topic.id !== topicId) }); }, addPost: (topicId: string, user: string, text: string): void => { const newPost: Post = { user: user, date: getCurrentTimeIso8601(), generationDate: null, content: text, } setTopicsContext({ ...topicsContext, topics: topicsContext.topics.map((topic) => topic.id === topicId ? { ...topic, posts: [...topic.posts, newPost] } : topic) }); }, deletePost: (topicId: string, postIndex: number): void => { setTopicsContext({ ...topicsContext, topics: topicsContext.topics.map((topic) => { if (topic.id !== topicId) { return topic; } // Delete all posts if the first is deleted const posts: Post[] = postIndex === 0 ? [] : topic.posts.filter((_, index) => index !== postIndex); return { ...topic, posts }; // Delete topic if it has not more posts }).filter((t) => t.posts.length > 0) }); }, generateTopic: async (settings: Settings, log: LogAction) => { const id = generateUUID(); setTopicsContext({ ...topicsContext, generation: "pending", }); log(`Topic: ${id} -> generation start.`) feedTopic(settings, log, id, (topic: Topic) => { if(topic.title.length < 1) return; setTopicsContext((topicsContext: TopicsContext) => { // console.log(topicsContext); const topicIndex = topicsContext.topics.findIndex((topic) => topic.id === id); // -1 if no topic found if(topicIndex < 0) { return { ...topicsContext, generation: "pending", topics: [...topicsContext.topics, topic] } satisfies TopicsContext } return { ...topicsContext, generation: "pending", // Replace the old topic with the new one topics: topicsContext.topics.map(oldTopic => oldTopic.id === id ? topic : oldTopic), } satisfies TopicsContext }); // console.log("feedTopic"); }).then(() => { // console.log("then"); // TODO: check if the topic has been generated setTopicsContext((topicsContext: TopicsContext) => ({ ...topicsContext, generation: "done", })) log(`Topic: ${id} -> generation done.`) }).catch((e: Error) => { setTopicsContext((topicsContext: TopicsContext) => ({ ...topicsContext, generation: "error", })) log(`Topic: ${id} -> generation error (${e.message}).`) }); }, generatePosts: async (settings: Settings, log: LogAction, initialTopic: Topic) => { setTopicsContext({ ...topicsContext, generation: "pending", }); log(`Topic: ${initialTopic.id} -> generation start.`) feedPosts(settings, log, initialTopic, (topic: Topic) => { setTopicsContext((topicsContext: TopicsContext) => { // console.log(topicsContext); const topicIndex = topicsContext.topics.findIndex((topic) => topic.id === initialTopic.id); // -1 if no topic found if(topicIndex < 0) { return { ...topicsContext, generation: "pending", topics: [...topicsContext.topics, topic] } satisfies TopicsContext } return { ...topicsContext, generation: "pending", // Replace the old topic with the new one topics: topicsContext.topics.map(oldTopic => oldTopic.id === initialTopic.id ? topic : oldTopic), } satisfies TopicsContext }); }).then(() => { setTopicsContext((topicsContext: TopicsContext) => ({ ...topicsContext, generation: "done", })) log(`Topic: ${initialTopic.id} -> generation done.`) }).catch((e: Error) => { setTopicsContext((topicsContext: TopicsContext) => ({ ...topicsContext, generation: "error", })) log(`Topic: ${initialTopic.id} -> generation error (${e.message}).`) }); }, importTopic: (json: string): void => { const error = (message: string): void => { throw new Error(message) } try { const object = JSON.parse(json); const id = generateUUID(); const topic: Topic = { id: id, title: typeof object.title == "string" ? object.title : error("title must be a string"), posts: object.posts instanceof Array ? object.posts.map((object: unknown): Post => ({ user: typeof object.user == "string" ? object.user : error("posts.user must be a string"), date: typeof object.date == "string" ? object.date : error("posts.date must be a string"), generationDate: typeof object.generationDate == "string" || object.generationDate === null ? object.generationDate : error("posts.generationDate must be null or string"), content: typeof object.content == "string" ? object.content : error("posts.content must be a string"), })) : error("posts must be a string"), } setTopicsContext({ ...topicsContext, topics: [...topicsContext.topics, topic], import: "done", }); } catch (e) { console.error(e); setTopicsContext({ ...topicsContext, import: "error", }); } } }, }) })