feat: ditch triplit, move to postgresql + drizzle orm
This commit is contained in:
+196
-58
@@ -1,75 +1,213 @@
|
||||
import type { Entity } from "@triplit/client";
|
||||
import type schema from "#triplit/schema";
|
||||
import { nanoid } from "nanoid";
|
||||
import { assert } from "~~/utils/assert";
|
||||
import { attempt } from "~~/types/result";
|
||||
import * as schema from '~~/drizzle/schema';
|
||||
|
||||
export type Agent = Readonly<Entity<typeof schema, 'agents'> & { topics: Readonly<Entity<typeof schema, 'topics'>>[] }>;
|
||||
export type Topic = typeof schema.topics.$inferSelect;
|
||||
export type Agent = typeof schema.agents.$inferSelect;
|
||||
export type AgentWithTopics = Agent & { topics: Topic[] };
|
||||
|
||||
export const useAgents = () => {
|
||||
const nuxtApp = useNuxtApp();
|
||||
const triplit = useTriplitClient();
|
||||
export const useAgents = async () => {
|
||||
const agents = useState<AgentWithTopics[]>('agents_state', () => []);
|
||||
const loaded = useState('agents_loaded', () => false);
|
||||
|
||||
// dont leaking between different users/requests
|
||||
if (!nuxtApp._agentsState) {
|
||||
nuxtApp._agentsState = {
|
||||
list: ref<Agent[]>([]),
|
||||
initPromise: null as Promise<void> | null,
|
||||
};
|
||||
}
|
||||
const { refresh } = await useFetch<AgentWithTopics[]>('/api/agents', {
|
||||
key: 'agents_request',
|
||||
immediate: !loaded.value,
|
||||
onRequest() {
|
||||
loaded.value = true;
|
||||
},
|
||||
onResponse({ response }) {
|
||||
if (response.ok) {
|
||||
agents.value = response._data ?? [];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const state = nuxtApp._agentsState as {
|
||||
list: Ref<Agent[]>;
|
||||
initPromise: Promise<void> | null;
|
||||
};
|
||||
|
||||
const init = (): Promise<void> => {
|
||||
if (state.initPromise) return state.initPromise;
|
||||
|
||||
state.initPromise = (async () => {
|
||||
const query = triplit.query('agents')
|
||||
.Include('topics', (rel) => rel('topics').Order('createdAt', 'DESC'))
|
||||
.Order('createdAt', 'ASC');
|
||||
|
||||
const { results } = await useQuery('agents', triplit, query)
|
||||
watch(results, (newAgents) => {
|
||||
if (newAgents && newAgents.length > 0) {
|
||||
state.list.value = newAgents as unknown as Agent[];
|
||||
}
|
||||
}, { immediate: true, flush: 'sync' });
|
||||
})();
|
||||
|
||||
return state.initPromise;
|
||||
};
|
||||
|
||||
const getAgent = (id: MaybeRef<string>) => {
|
||||
return computed(() => state.list.value.find((agent) => agent.id === toRef(id).value) || null);
|
||||
}
|
||||
|
||||
const createAgent = async () => {
|
||||
const triplit = useTriplitClient();
|
||||
const createAgent = async (navigate: boolean = true) => {
|
||||
const { user } = useAuth();
|
||||
if (!user.value) throw new Error('No user');
|
||||
if (!user.value) {
|
||||
console.error('No user');
|
||||
return null;
|
||||
}
|
||||
|
||||
const id = nanoid();
|
||||
await triplit.insert('agents', {
|
||||
id,
|
||||
name: 'New Agent',
|
||||
const agentId = nanoid();
|
||||
|
||||
const agent = {
|
||||
id: agentId,
|
||||
userId: user.value.id,
|
||||
name: 'New Agent',
|
||||
systemPrompt: 'You are a helpful assistant.',
|
||||
defaultModelId: null,
|
||||
imageUrl: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
} as AgentWithTopics;
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// @ts-ignore - stack depth
|
||||
const res = await attempt($fetch('/api/agent', {
|
||||
method: 'POST',
|
||||
body: agent,
|
||||
onRequest() {
|
||||
agents.value = [...(agents.value), { ...agent, topics: [] as Topic[], createdAt: new Date() }];
|
||||
if (navigate) {
|
||||
router.push(`/agent/${agentId}`);
|
||||
}
|
||||
},
|
||||
onRequestError() {
|
||||
if (navigate) {
|
||||
const route = useRoute();
|
||||
if (route.params.id === agentId) {
|
||||
router.push('/');
|
||||
}
|
||||
}
|
||||
agents.value = agents.value.filter(a => a.id !== agentId);
|
||||
},
|
||||
onResponseError() {
|
||||
if (navigate) {
|
||||
const route = useRoute();
|
||||
if (route.params.id === agentId) {
|
||||
router.push('/');
|
||||
}
|
||||
}
|
||||
agents.value = agents.value.filter(a => a.id !== agentId);
|
||||
},
|
||||
async onResponse() {
|
||||
await refresh();
|
||||
}
|
||||
}));
|
||||
|
||||
if (!res.ok) return null;
|
||||
|
||||
return agent;
|
||||
};
|
||||
|
||||
const patchAgentLocally = (id: string, updates: Partial<Agent>) => {
|
||||
if (!agents.value) return null;
|
||||
|
||||
agents.value = agents.value.map(a =>
|
||||
a.id === id ? { ...a, ...updates } : a
|
||||
);
|
||||
};
|
||||
|
||||
const patchTopicLocally = (id: string, updates: Partial<Topic>) => {
|
||||
if (!agents.value) return null;
|
||||
|
||||
agents.value = agents.value.map(a =>
|
||||
a.topics.find(t => t.id === id) ? {
|
||||
...a, topics: a.topics.map(t =>
|
||||
t.id === id ? { ...t, ...updates } : t
|
||||
)
|
||||
} : a
|
||||
);
|
||||
};
|
||||
|
||||
const updateAgent = async (id: string, updates: Partial<Agent>) => {
|
||||
const agent = agents.value.find(a => a.id === id);
|
||||
if (!agent) return;
|
||||
|
||||
await $fetch(`/api/agent/${id}`, {
|
||||
method: 'PATCH',
|
||||
body: updates,
|
||||
onRequest() {
|
||||
agents.value = agents.value.map(a =>
|
||||
a.id === id ? { ...a, ...updates } : a
|
||||
);
|
||||
},
|
||||
onRequestError() {
|
||||
agents.value = agents.value.map(a =>
|
||||
a.id === id ? agent : a
|
||||
);
|
||||
},
|
||||
onResponseError() {
|
||||
agents.value = agents.value.map(a =>
|
||||
a.id === id ? agent : a
|
||||
);
|
||||
},
|
||||
async onResponse() {
|
||||
await refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const getAgent = (id: MaybeRef<string>) => {
|
||||
return computed(() => agents.value?.find((agent) => agent.id === unref(id)) || null);
|
||||
}
|
||||
|
||||
const deleteAgent = async (id: string) => {
|
||||
let agent = agents.value.find(a => a.id === id);
|
||||
if (!agent) return;
|
||||
|
||||
await $fetch(`/api/agent/${id}`, {
|
||||
method: 'DELETE',
|
||||
onRequest() {
|
||||
agents.value = agents.value.filter(a => a.id !== id);
|
||||
},
|
||||
onResponseError() {
|
||||
agents.value = [...agents.value.filter(a => a.id !== id), agent];
|
||||
},
|
||||
async onResponse() {
|
||||
await refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: since topics are contained within each agent struct, all topic
|
||||
// actions must be done through the agents composable
|
||||
const createTopic = async (agentId: string) => {
|
||||
const topicId = nanoid();
|
||||
const topic = {
|
||||
id: topicId,
|
||||
name: 'New Topic',
|
||||
agentId,
|
||||
};
|
||||
|
||||
await $fetch(`/api/topic`, {
|
||||
method: 'POST',
|
||||
body: topic,
|
||||
onRequest() {
|
||||
agents.value = agents.value.map(a =>
|
||||
a.id === agentId ? { ...a, topics: [{ ...topic, createdAt: new Date() }, ...a.topics] } : a
|
||||
);
|
||||
},
|
||||
onResponseError() {
|
||||
agents.value = agents.value.map(a =>
|
||||
a.id === agentId ? { ...a, topics: a.topics.filter(t => t.id !== topicId) } : a
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
assert('flush' in triplit);
|
||||
await triplit.flush();
|
||||
return topic;
|
||||
}
|
||||
|
||||
const deleteTopic = async (agentId: string, topicId: string) => {
|
||||
const targetTopic = agents.value.flatMap(agent => agent.topics).find(topic => topic.id === topicId);
|
||||
if (!targetTopic) return;
|
||||
|
||||
await $fetch(`/api/topic/${topicId}`, {
|
||||
method: 'DELETE',
|
||||
onRequest() {
|
||||
agents.value = agents.value.map(a =>
|
||||
a.id === agentId ? { ...a, topics: a.topics.filter(t => t.id !== topicId) } : a
|
||||
);
|
||||
},
|
||||
onResponseError() {
|
||||
agents.value = agents.value.map(a =>
|
||||
a.id === agentId ? { ...a, topics: [...a.topics.filter(t => t.id !== topicId), targetTopic] } : a
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return state.list.value.find((agent) => agent.id === id)!;
|
||||
};
|
||||
return {
|
||||
init,
|
||||
agents: state.list,
|
||||
agents,
|
||||
refresh,
|
||||
createAgent,
|
||||
createTopic,
|
||||
getAgent,
|
||||
createAgent
|
||||
patchAgentLocally,
|
||||
patchTopicLocally,
|
||||
updateAgent,
|
||||
deleteAgent,
|
||||
deleteTopic
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user