Experiment to rebuild Diffuse using web applets.
1<script>
2 import * as Orama from "@orama/orama";
3 // import { pluginQPS } from "@orama/plugin-qps";
4
5 import type { Track } from "@applets/core/types";
6 import type { State } from "./types.d.ts";
7 import { register } from "@scripts/applet/common";
8
9 ////////////////////////////////////////////
10 // SETUP
11 ////////////////////////////////////////////
12 const context = register<State>();
13
14 // Initial data
15 context.data = {
16 inserted: new Set(),
17 };
18
19 // Schema
20 const SCHEMA = {
21 id: "string" as const,
22 kind: "string" as const,
23 tags: {
24 album: "string" as const,
25 artist: "string" as const,
26 genre: "string" as const,
27 title: "string" as const,
28 year: "number" as const,
29 },
30
31 // TODO:
32 // isFavorite: "boolean" as const,
33 // inPlaylists: [ ... ],
34
35 embeddings: "vector[512]" as const,
36 };
37
38 // TODO: Generate embeddings plugin
39 //
40 // I tried this and getting some bundler/vite errors about a default import.
41 //
42 // const plugin = await pluginEmbeddings({
43 // embeddings: {
44 // defaultProperty: "embeddings",
45 // onInsert: {
46 // generate: true,
47 // // Properties to use for generating embeddings at insert time.
48 // // These properties will be concatenated and used to generate embeddings.
49 // properties: ["album", "artist", "title", "year", "kind", "genre"],
50 // // verbose: true,
51 // },
52 // },
53 // });
54 //
55 // TODO:
56 //
57 // Does not work either.
58 // `TypeError: a is undefined`
59 //
60 // pluginQPS()
61
62 const PLUGINS: Orama.OramaPlugin[] = [];
63
64 // Search through tracks
65 const db = Orama.create({
66 schema: SCHEMA,
67 plugins: PLUGINS,
68
69 // components: {
70 // TODO:
71 // https://docs.orama.com/open-source/usage/insert#remote-document-storing
72 // documentStore: { ... }
73 // },
74 });
75
76 ////////////////////////////////////////////
77 // ACTIONS
78 ////////////////////////////////////////////
79 context.setActionHandler("search", search);
80 context.setActionHandler("supply", supply);
81
82 async function search(term: string): Promise<Track[]> {
83 const results = await Orama.search(db, {
84 // mode: "hybrid",
85 term,
86 });
87
88 return results.hits.map((hit) => hit.document as unknown as Track);
89 }
90
91 async function supply(tracks: Track[]) {
92 // TODO: Generate a hash based on the track itself,
93 // so we can detect changes to tags or other data.
94
95 const ids = [];
96 const tracksMap: Record<string, Track> = {};
97
98 tracks.forEach((track) => {
99 ids.push(track.id);
100 tracksMap[track.id] = track;
101 });
102
103 const currentSet = context.data.inserted;
104 const newSet = new Set(tracks.map((t) => t.id));
105
106 const removedIds = currentSet.difference(newSet);
107 const newIds = newSet.difference(currentSet);
108 const newTracks = Array.from(newIds).map((id) => tracksMap[id]);
109
110 await Orama.removeMultiple(db, Array.from(removedIds));
111 await Orama.insertMultiple(db, newTracks);
112 }
113
114 ////////////////////////////////////////////
115 // 🛠️
116 ////////////////////////////////////////////
117</script>