feat(prompts): unify sources and add personal library

This commit is contained in:
yu
2026-07-21 01:08:11 +08:00
parent c81bb8b651
commit 49851f2b5f
19 changed files with 736 additions and 539 deletions
+14 -14
View File
@@ -8,7 +8,7 @@ export type PromptSourceSchedule = {
lastFetchedAt: string;
};
const PROMPT_SOURCE_STORE_KEY = "infinite-canvas:prompt_source_store";
const PROMPT_SOURCE_STORE_KEY = "infinite-canvas:prompt_source_store_v2";
const defaultSchedule: PromptSourceSchedule = {
intervalMinutes: 30,
@@ -38,13 +38,14 @@ export const usePromptSourceStore = create<PromptSourceStore>()(
(set) => ({
sources: DEFAULT_PROMPT_SOURCES,
schedule: defaultSchedule,
addSource: () => {
const source = createPromptSource();
set((state) => ({ sources: [...state.sources, source] }));
return source;
},
saveSource: (source) => set((state) => ({ sources: state.sources.map((item) => (item.id === source.id ? source : item)) })),
removeSource: (id) => set((state) => ({ sources: state.sources.filter((item) => item.id !== id) })),
addSource: () => createPromptSource(),
saveSource: (source) =>
set((state) => ({
sources: state.sources.some((item) => item.id === source.id)
? state.sources.map((item) => (item.id === source.id && !item.builtIn ? createPromptSource(source) : item))
: [...state.sources, createPromptSource(source)],
})),
removeSource: (id) => set((state) => ({ sources: state.sources.filter((item) => item.id !== id || item.builtIn) })),
toggleSource: (id, enabled) => set((state) => ({ sources: state.sources.map((item) => (item.id === id ? { ...item, enabled } : item)) })),
updateSchedule: (key, value) => set((state) => ({ schedule: { ...state.schedule, [key]: value } })),
}),
@@ -53,12 +54,11 @@ export const usePromptSourceStore = create<PromptSourceStore>()(
partialize: (state) => ({ sources: state.sources, schedule: state.schedule }),
merge: (persisted, current) => {
const persistedState = (persisted || {}) as Partial<PromptSourceStore>;
const sources = Array.isArray(persistedState.sources) && persistedState.sources.length ? persistedState.sources.map((item) => createPromptSource(item)) : DEFAULT_PROMPT_SOURCES;
return {
...current,
sources,
schedule: { ...defaultSchedule, ...(persistedState.schedule || {}) },
};
const savedSources = Array.isArray(persistedState.sources) ? persistedState.sources : [];
const enabledById = new Map(savedSources.map((source) => [source.id, source.enabled]));
const builtIn = DEFAULT_PROMPT_SOURCES.map((source) => ({ ...source, enabled: enabledById.get(source.id) ?? source.enabled }));
const custom = savedSources.filter((source) => !source.builtIn).map((source) => createPromptSource(source));
return { ...current, sources: [...builtIn, ...custom], schedule: { ...defaultSchedule, ...(persistedState.schedule || {}) } };
},
},
),
+53
View File
@@ -0,0 +1,53 @@
import { nanoid } from "nanoid";
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
import { localForageStorage } from "@/lib/localforage-storage";
export type PersonalPrompt = {
id: string;
title: string;
prompt: string;
description: string;
coverUrl: string;
referenceImageUrls: string[];
tags: string[];
createdAt: string;
updatedAt: string;
imageMode?: string;
imageModel?: string;
imageSize?: string;
imageCount?: number;
};
export type PersonalPromptInput = Omit<PersonalPrompt, "id" | "createdAt" | "updatedAt">;
type PromptStore = {
hydrated: boolean;
prompts: PersonalPrompt[];
addPrompt: (prompt: PersonalPromptInput) => string;
updatePrompt: (id: string, prompt: PersonalPromptInput) => void;
removePrompt: (id: string) => void;
};
export const usePromptStore = create<PromptStore>()(
persist(
(set) => ({
hydrated: false,
prompts: [],
addPrompt: (prompt) => {
const id = nanoid();
const now = new Date().toISOString();
set((state) => ({ prompts: [{ ...prompt, id, createdAt: now, updatedAt: now }, ...state.prompts] }));
return id;
},
updatePrompt: (id, prompt) => set((state) => ({ prompts: state.prompts.map((item) => (item.id === id ? { ...item, ...prompt, updatedAt: new Date().toISOString() } : item)) })),
removePrompt: (id) => set((state) => ({ prompts: state.prompts.filter((item) => item.id !== id) })),
}),
{
name: "infinite-canvas:prompt_store",
storage: createJSONStorage(() => localForageStorage),
onRehydrateStorage: () => () => usePromptStore.setState({ hydrated: true }),
},
),
);