Experiment to rebuild Diffuse using web applets.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: more web workers + bit of clean up

+178 -129
+14 -26
src/pages/configurator/input/_applet.astro
··· 35 35 import type { Applet } from "@web-applets/sdk"; 36 36 import * as URI from "uri-js"; 37 37 38 - import type { Consult, ConsultGrouping, GroupConsult, Track } from "@applets/core/types.d.ts"; 38 + import type { Consult, GroupConsult, Track } from "@applets/core/types.d.ts"; 39 + import type { Actions } from "@scripts/configurator/input/worker"; 40 + import type { Scheme } from "@scripts/configurator/input/types"; 41 + import { CONNECTIONS } from "@scripts/configurator/input/constants"; 39 42 import { applet, register } from "@scripts/applet/common"; 40 - import { groupTracksPerScheme } from "@scripts/common"; 43 + import { endpoint, groupTracksPerScheme } from "@scripts/common"; 41 44 42 45 //////////////////////////////////////////// 43 46 // SETUP 44 47 //////////////////////////////////////////// 48 + const worker = endpoint<Actions>( 49 + new SharedWorker(new URL("../../../scripts/configurator/input/worker", import.meta.url), { 50 + type: "module", 51 + }).port, 52 + ); 53 + 54 + // Register applet 45 55 const context = register(); 46 56 47 57 //////////////////////////////////////////// 48 58 // CONNECTIONS 49 59 ////////////////////////////////////////////// Applet connections 50 - const CONNECTIONS = { 51 - "file+local": "/input/native-fs", 52 - opensubsonic: "/input/opensubsonic", 53 - s3: "/input/s3", 54 - }; 55 - 56 - type Scheme = keyof typeof CONNECTIONS; 57 - 58 60 const connections: Record<Scheme, Promise<Applet> | undefined> = { 59 61 "file+local": undefined, 60 62 opensubsonic: undefined, ··· 89 91 }; 90 92 91 93 const contextualize = async (tracks: Track[]) => { 92 - const groups = await groupTracks(tracks); 94 + const groups = await worker.call.groupTracks(tracks); 93 95 const promises = Object.entries(groups).map( 94 96 async ([scheme, tracksGroup]: [string, Track[]]) => { 95 97 if (!isSupportedScheme(scheme) || tracksGroup.length === 0) return; ··· 122 124 }; 123 125 124 126 const list = async (cachedTracks: Track[] = []) => { 125 - const groups = await groupTracks(cachedTracks); 127 + const groups = await worker.call.groupTracks(cachedTracks); 126 128 127 129 const promises = Object.entries(groups).map( 128 130 async ([scheme, cachedTracksGroup]: [string, Track[]]) => { ··· 157 159 context.setActionHandler("groupConsult", groupConsult); 158 160 context.setActionHandler("list", list); 159 161 context.setActionHandler("resolve", resolve); 160 - 161 - //////////////////////////////////////////// 162 - // 🛠️ 163 - //////////////////////////////////////////// 164 - async function groupTracks(tracks: Track[]) { 165 - return groupTracksPerScheme( 166 - tracks, 167 - Object.fromEntries( 168 - Object.entries(CONNECTIONS).map(([k, _v]) => { 169 - return [k, []]; 170 - }), 171 - ), 172 - ); 173 - } 174 162 </script>
src/pages/constituent/pilot/audio/_applet.astro src/pages/theme/pilot/audio/_applet.astro
src/pages/constituent/pilot/audio/_manifest.json src/pages/theme/pilot/audio/_manifest.json
src/pages/constituent/pilot/audio/index.astro src/pages/theme/pilot/audio/index.astro
src/pages/constituent/pilot/audio/types.d.ts src/pages/theme/pilot/audio/types.d.ts
+1 -3
src/pages/orchestrator/queue-audio/_applet.astro
··· 1 1 <script> 2 - import type { ManagedOutput } from "@applets/core/types.d.ts"; 3 2 import { applet, inputUrl, makeConnect, register } from "@scripts/applet/common"; 4 3 5 4 //////////////////////////////////////////// ··· 15 14 // Applet connections 16 15 const configurator = { 17 16 input: await applet("/configurator/input"), 18 - output: await applet<ManagedOutput>("/configurator/output"), 19 17 }; 20 18 21 19 const engine = { ··· 76 74 url: await inputUrl(configurator.input, activeTrack.uri).then((a) => a?.url), 77 75 }, 78 76 ] 79 - : // NOTE: This probably isn't correct, keep preloads? 77 + : // TODO: This probably isn't correct, keep preloads? 80 78 [], 81 79 play: activeTrack && isPlaying ? { audioId: activeTrack.id } : undefined, 82 80 },
-1
src/pages/orchestrator/queue-tracks/_applet.astro
··· 6 6 // SETUP 7 7 //////////////////////////////////////////// 8 8 import type * as QueueEngine from "@applets/engine/queue/types.d.ts"; 9 - import { groupTracksPerScheme } from "@scripts/common"; 10 9 11 10 // Register applet 12 11 const context = register();
+11 -96
src/pages/processor/search/_applet.astro
··· 1 1 <script> 2 - import * as Orama from "@orama/orama"; 3 - // import { pluginQPS } from "@orama/plugin-qps"; 4 - 2 + import type { Actions } from "@scripts/processor/search/worker"; 5 3 import type { Track } from "@applets/core/types"; 6 - import type { State } from "./types.d.ts"; 7 4 import { register } from "@scripts/applet/common"; 5 + import { endpoint } from "@scripts/common"; 8 6 9 7 //////////////////////////////////////////// 10 8 // SETUP 11 9 //////////////////////////////////////////// 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: [ ... ], 10 + const worker = endpoint<Actions>( 11 + new SharedWorker(new URL("../../../scripts/processor/search/worker", import.meta.url), { 12 + type: "module", 13 + }).port, 14 + ); 34 15 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 - }); 16 + // Register applet 17 + const context = register(); 75 18 76 19 //////////////////////////////////////////// 77 20 // ACTIONS ··· 80 23 context.setActionHandler("supply", supply); 81 24 82 25 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); 26 + return worker.call.search(term); 89 27 } 90 28 91 29 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); 30 + return worker.call.supply(tracks); 112 31 } 113 - 114 - //////////////////////////////////////////// 115 - // 🛠️ 116 - //////////////////////////////////////////// 117 32 </script>
src/pages/processor/search/types.d.ts src/scripts/processor/search/types.d.ts
+1 -1
src/pages/theme/pilot/index.astro
··· 10 10 <div class="filler" style="flex: 1;"></div> 11 11 12 12 <!-- Theme applets --> 13 - <iframe id="applet__ui__audio" src="../../constituent/pilot/audio/"></iframe> 13 + <iframe id="applet__ui__audio" src="./audio/"></iframe> 14 14 </Page>
+5
src/scripts/configurator/input/constants.ts
··· 1 + export const CONNECTIONS = { 2 + "file+local": "/input/native-fs", 3 + opensubsonic: "/input/opensubsonic", 4 + s3: "/input/s3", 5 + };
+3
src/scripts/configurator/input/types.d.ts
··· 1 + import type { CONNECTIONS } from "./constants"; 2 + 3 + export type Scheme = keyof typeof CONNECTIONS;
+25
src/scripts/configurator/input/worker.ts
··· 1 + import { expose, groupTracksPerScheme } from "@scripts/common"; 2 + import { CONNECTIONS } from "./constants"; 3 + import type { Track } from "@applets/core/types"; 4 + 5 + //////////////////////////////////////////// 6 + // ACTIONS 7 + //////////////////////////////////////////// 8 + const actions = expose({ 9 + groupTracks, 10 + }); 11 + 12 + export type Actions = typeof actions; 13 + 14 + // Actions 15 + 16 + function groupTracks(tracks: Track[]) { 17 + return groupTracksPerScheme( 18 + tracks, 19 + Object.fromEntries( 20 + Object.entries(CONNECTIONS).map(([k, _v]) => { 21 + return [k, []]; 22 + }), 23 + ), 24 + ); 25 + }
+17
src/scripts/processor/search/constants.ts
··· 1 + export const SCHEMA = { 2 + id: "string" as const, 3 + kind: "string" as const, 4 + tags: { 5 + album: "string" as const, 6 + artist: "string" as const, 7 + genre: "string" as const, 8 + title: "string" as const, 9 + year: "number" as const, 10 + }, 11 + 12 + // TODO: 13 + // isFavorite: "boolean" as const, 14 + // inPlaylists: [ ... ], 15 + 16 + embeddings: "vector[512]" as const, 17 + };
+99
src/scripts/processor/search/worker.ts
··· 1 + import * as Orama from "@orama/orama"; 2 + // import { pluginQPS } from "@orama/plugin-qps"; 3 + 4 + import type { Track } from "@applets/core/types"; 5 + import { expose } from "@scripts/common"; 6 + import { SCHEMA } from "./constants"; 7 + import type { State } from "./types"; 8 + 9 + //////////////////////////////////////////// 10 + // SETUP 11 + //////////////////////////////////////////// 12 + 13 + let state: State = { 14 + inserted: new Set<string>(), 15 + }; 16 + 17 + // TODO: Generate embeddings plugin 18 + // 19 + // I tried this and getting some bundler/vite errors about a default import. 20 + // 21 + // const plugin = await pluginEmbeddings({ 22 + // embeddings: { 23 + // defaultProperty: "embeddings", 24 + // onInsert: { 25 + // generate: true, 26 + // // Properties to use for generating embeddings at insert time. 27 + // // These properties will be concatenated and used to generate embeddings. 28 + // properties: ["album", "artist", "title", "year", "kind", "genre"], 29 + // // verbose: true, 30 + // }, 31 + // }, 32 + // }); 33 + // 34 + // TODO: 35 + // 36 + // Does not work either. 37 + // `TypeError: a is undefined` 38 + // 39 + // pluginQPS() 40 + 41 + const PLUGINS: Orama.OramaPlugin[] = []; 42 + 43 + // Search through tracks 44 + const db = Orama.create({ 45 + schema: SCHEMA, 46 + plugins: PLUGINS, 47 + 48 + // components: { 49 + // TODO: 50 + // https://docs.orama.com/open-source/usage/insert#remote-document-storing 51 + // documentStore: { ... } 52 + // }, 53 + }); 54 + 55 + //////////////////////////////////////////// 56 + // ACTIONS 57 + //////////////////////////////////////////// 58 + const actions = expose({ 59 + search, 60 + supply, 61 + }); 62 + 63 + export type Actions = typeof actions; 64 + 65 + // Actions 66 + 67 + async function search(term: string): Promise<Track[]> { 68 + const results = await Orama.search(db, { 69 + // mode: "hybrid", 70 + term, 71 + }); 72 + 73 + return results.hits.map((hit) => hit.document as unknown as Track); 74 + } 75 + 76 + async function supply(tracks: Track[]) { 77 + // TODO: Generate a hash based on the track itself, 78 + // so we can detect changes to tags or other data. 79 + 80 + const ids = []; 81 + const tracksMap: Record<string, Track> = {}; 82 + 83 + tracks.forEach((track) => { 84 + ids.push(track.id); 85 + tracksMap[track.id] = track; 86 + }); 87 + 88 + const currentSet = state.inserted; 89 + const newSet = new Set(tracks.map((t) => t.id)); 90 + 91 + const removedIds = currentSet.difference(newSet); 92 + const newIds = newSet.difference(currentSet); 93 + const newTracks = Array.from(newIds).map((id) => tracksMap[id]); 94 + 95 + await Orama.removeMultiple(db, Array.from(removedIds)); 96 + await Orama.insertMultiple(db, newTracks); 97 + 98 + state.inserted = newSet; 99 + }
+2 -2
src/scripts/theme/pilot/index.ts
··· 6 6 import type * as AudioEngine from "@applets/engine/audio/types.d.ts"; 7 7 import type * as QueueEngine from "@applets/engine/queue/types.d.ts"; 8 8 9 - import type * as AudioUI from "@applets/constituent/pilot/audio/types"; 9 + import type * as AudioUI from "@applets/theme/pilot/audio/types"; 10 10 11 11 const engine = { 12 12 audio: await applet<AudioEngine.State>("/engine/audio"), ··· 20 20 }; 21 21 22 22 const ui = { 23 - audio: await applet<AudioUI.State>("/constituent/pilot/audio/", { setHeight: true }), 23 + audio: await applet<AudioUI.State>("/theme/pilot/audio/", { setHeight: true }), 24 24 }; 25 25 26 26 ////////////////////////////////////////////