import { v4 as randomUUID, v4 as uuidv4 } from "uuid"; import { Op } from "./operator"; import { atom, computed } from "nanostores"; type Id = string; type IdAble = { id: Id, } // only vibes and styles will have a name. export enum Category { subject = "subject", style = "style", vibes = "vibes", medium = "medium", } export function categoryHasName(cat : Category) { return (cat === Category.style || cat === Category.vibes) } const N_CATEGORIES = Object.keys(Category).length; export function catI(c: Category | string): number { return Object.keys(Category).indexOf(c); } export type LibraryItem = { id: Id, name?: string, prompt: string, category: Category, } export type Library = Array; export type Nugget = IdAble & { item: LibraryItem, score: number, } export type Operation = IdAble & { op: Op, items: Array } export type PromptItem = Operation | Nugget export function itemIsOperation(item: PromptItem): boolean { return "op" in item; } export function itemIsNugget(item: PromptItem): boolean { return !itemIsOperation(item); } export type Composition = Array; export const $library = atom([]); export function addItemToLibrary(item: LibraryItem) { $library.set([ ...$library.get(), item, ]); } export function removeItemFromLibrary(item: LibraryItem) { $library.set($library.get().filter(i => (i.id !== item.id))); } export const $composition = atom([]) export function insertIntoComposition(item: LibraryItem) { $composition.set([ ...$composition.get(), { id: randomUUID(), item, score: 0 } as Nugget, ]); } export function removeFromComposition(item: PromptItem) { $composition.set([ ...$composition.get().filter(i => (i.id !== item.id)) ]); } function nuggetDelta(nuggetId : Id, delta : number) { $composition.set($composition.get().map(item => { if ((item.id === nuggetId) && ("score" in item)) { const o = {...item, score: item.score + delta}; return o; } return item; } )); } export function increaseNuggetScore(nuggetId: Id, amount: number = 1) { return nuggetDelta(nuggetId, amount) } export function decreaseNuggetScore(nuggetId: Id, amount: number = -1) { return nuggetDelta(nuggetId, amount); } export function changeOperationOp(operationId: Id, op: Op) { $composition.set([ ...$composition.get().map(item => { return (item.id == operationId) ? { ...item, op: op } : item; } ), ]); } /** * utility method to remove & return a prompt item by ID * @param id PromptItem ID */ export function popPromptItem (id : Id) { const found = $composition.get().find(item => item.id === id); $composition.set($composition.get().filter(item => item.id !== id)); return found; } export function lassoNuggets (n1id : Id, n2id : Id, op : Op) { const n1 = popPromptItem(n1id); const n2 = popPromptItem(n2id); $composition.set([...$composition.get(), { id: uuidv4(), op, items: [n1, n2], } as Operation]) } export function addToOperation(nId : Id, opId : Id) { const comp = $composition.get(); const nugget = comp.find(i => i.id === nId); $composition.set($composition.get().map(i => { if (![nId, opId].includes(i.id)) { return i; } // if this is the nugget... if (i.id === nId) { return null; } // if this is the operation... if (i.id == opId && "op" in i) { return { ...i, items: [ ...i.items, nugget ] } } return i; }).filter(i => i != null) as Composition); } export function nuggetToText(nugget: Nugget) { const absScore = Math.abs(nugget.score); const neg = nugget.score < 0; const pos = nugget.score > 0; const sign = pos ? "+" : (neg ? "-" : ""); const prompt = nugget.item.prompt; const signs = new Array(absScore).fill(sign).join("") if (prompt.includes(" ")) { return "(" + nugget.item.prompt + ")" + signs; } return nugget.item.prompt + signs; } export function operationToText(operation: Operation): string { return "(" + operation.items.map(nuggetToText).join(", ") + ")." + operation.op + "()"; } export const $textComposition = computed($composition, (composition) => { const JOINER = ", "; return composition.map(item => { return "op" in item ? operationToText(item) : nuggetToText(item); }).join(JOINER); }); export type SlottedComposition = PromptItem[][]; /** * This is necessary since a "composition" needs to have prompt items in * different columns. * * There are n columns (or slots) where n is the number of categories. */ export const $slottedComposition = computed($composition, (composition) => { const slotted = new Array(N_CATEGORIES) as SlottedComposition; composition.forEach(nugget => { if ("op" in nugget) { if (!nugget.items.length) return null; const cat = nugget.items[0].item.category; if (!slotted[catI(cat)]) { slotted[catI(cat)] = []; } slotted[catI(cat)].push(nugget); } else { const cat = nugget.item.category; if (!slotted[catI(cat)]) { slotted[catI(cat)] = []; } slotted[catI(cat)].push(nugget); } }) return slotted; });