Skip to content

Commit 3edbb08

Browse files
committed
restructure indexedDB
1 parent 7aebf45 commit 3edbb08

File tree

10 files changed

+141
-92
lines changed

10 files changed

+141
-92
lines changed

src/components/exchange/exchange.ts

+21-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { VectorDatabase } from '../../database/vector.ts'
2+
import { parseURI } from "../../database/uri.ts"
23

34
type MetaData = {
4-
f: string
5+
u: string
56
d: number
67
}
78

@@ -45,7 +46,7 @@ export const exportFile = async (
4546
}
4647

4748
const meta: MetaData = {
48-
f: item.path,
49+
u: item.uri,
4950
d: item.embedding.length,
5051
}
5152
const json = JSON.stringify(meta)
@@ -85,12 +86,18 @@ const fileReader = (file: File) => ({
8586
},
8687
async readNext(version: number): Promise<Entry> {
8788
// version -> parser-function
88-
const parser = {
89+
const parser: Record<number, () => Promise<Entry>> = {
8990
1: async (): Promise<Entry> => {
90-
const meta = (await this.readNextJson()) as MetaData
91+
const meta = (await this.readNextJson()) as {
92+
f: string
93+
d: number
94+
}
9195
const raw = await this.readNBytes(meta.d * 4)
9296
return {
93-
Meta: meta,
97+
Meta: {
98+
u: `file://` + meta.f,
99+
d: meta.d,
100+
},
94101
Embedding: new Float32Array(raw),
95102
}
96103
},
@@ -125,13 +132,18 @@ export const importFile = async (
125132
break
126133
}
127134

128-
if (await vectorDB.exists(entry.Meta.f)) {
129-
const persisted = await vectorDB.getByPath(entry.Meta.f)
135+
if (await vectorDB.exists(entry.Meta.u)) {
136+
const persisted = await vectorDB.getByURI(entry.Meta.u)
130137
await vectorDB.delete(persisted.id)
131138
}
139+
const parsedURI = parseURI(entry.Meta.u)
140+
if (parsedURI === null) {
141+
throw new Error('Unknown URI type')
142+
}
143+
132144
await vectorDB.save({
133-
directory: entry.Meta.f.split('/')[0],
134-
path: entry.Meta.f,
145+
collection: parsedURI.directory,
146+
uri: entry.Meta.u,
135147
embedding: entry.Embedding,
136148
})
137149
}

src/components/image/ImageDeletion.vue

+21-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<div>
3-
<v-btn @click="showDialog = true" color="error" variant="elevated" block v-show="fileNames.length > 0">
3+
<v-btn @click="showDialog = true" color="error" variant="elevated" block v-show="images.length > 0">
44
<v-icon icon="mdi-delete"></v-icon>
55
Delete selected
66
</v-btn>
@@ -12,12 +12,12 @@
1212
<v-card-text v-show="progress.total === 0">
1313
<v-row dense>
1414
<v-col cols="12" class="image-container" :style="`column-count: ${columnCount};`">
15-
<div v-for="name in fileNames" :key="name" @click="onClickImage(name)" class="cursor-pointer">
15+
<div v-for="image in images" :key="image.uri.rawURI" @click="onClickImage(image.uri.rawURI)" class="cursor-pointer">
1616
<ImageItem
1717
:base-dir="baseDir"
18-
:name="name"
18+
:image="image"
1919
style="margin-bottom: 0.5em"
20-
:class="selected[name] ? 'image-selected border-lg' : ''"
20+
:class="selected[image.uri.rawURI] ? 'image-selected border-lg' : ''"
2121
/>
2222
</div>
2323
</v-col>
@@ -47,9 +47,10 @@
4747

4848
<script lang="ts">
4949
import { defineComponent } from 'vue'
50-
import ImageItem from './ImageItem.vue'
50+
import ImageItem, { Image } from "./ImageItem.vue"
5151
import ProgressBar from '../progress/Bar.vue'
5252
import { delayProgress } from "../progress/delayed.ts"
53+
import { URI } from "../../database/uri.ts"
5354
5455
export default defineComponent({
5556
name: 'ImageDeletion',
@@ -60,8 +61,8 @@ export default defineComponent({
6061
type: Object as () => FileSystemDirectoryHandle,
6162
required: true,
6263
},
63-
fileNames: {
64-
type: Array as () => string[],
64+
images: {
65+
type: Array as () => Image[],
6566
required: true,
6667
},
6768
},
@@ -72,7 +73,7 @@ export default defineComponent({
7273
current: 0,
7374
total: 0,
7475
},
75-
selected: {} as { [key: string]: boolean },
76+
selected: {} as Record<URI, boolean>,
7677
}
7778
},
7879
computed: {
@@ -92,23 +93,23 @@ export default defineComponent({
9293
return 4
9394
},
9495
toDelete() {
95-
return this.fileNames.filter((name) => !this.selected[name])
96+
return this.images.filter((image) => !this.selected[image.uri.rawURI])
9697
},
9798
},
9899
methods: {
99-
onClickImage(name: string) {
100-
if (this.selected[name]) {
101-
delete this.selected[name]
100+
onClickImage(uri: URI) {
101+
if (this.selected[uri]) {
102+
delete this.selected[uri]
102103
} else {
103-
this.selected[name] = true
104+
this.selected[uri] = true
104105
}
105106
},
106-
async deleteDatabaseEntry(fileName: string) {
107-
const dbEntry = await this.$vectorDB.getByPath(this.baseDir.name + '/' + fileName)
107+
async deleteDatabaseEntry(image: Image) {
108+
const dbEntry = await this.$vectorDB.getByURI(image.uri.rawURI)
108109
await this.$vectorDB.delete(dbEntry.id)
109110
},
110-
async deleteFile(fileName: string) {
111-
const file = await this.baseDir.getFileHandle(fileName)
111+
async deleteFile(image: Image) {
112+
const file = await this.baseDir.getFileHandle(image.uri.name)
112113
await file.remove()
113114
},
114115
async deleteSelected() {
@@ -117,15 +118,15 @@ export default defineComponent({
117118
118119
const delayedProgression = delayProgress((i) => (this.progress.current = i))
119120
try {
120-
for (let fileName of this.toDelete) {
121+
for (let image of this.toDelete) {
121122
try {
122-
await this.deleteDatabaseEntry(fileName)
123+
await this.deleteDatabaseEntry(image)
123124
} catch (e) {
124125
console.warn(e)
125126
}
126127
127128
try {
128-
await this.deleteFile(fileName)
129+
await this.deleteFile(image)
129130
} catch (e) {
130131
console.warn(e)
131132
}

src/components/image/ImageItem.vue

+5-4
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010

1111
<script lang="ts">
1212
import { defineComponent } from 'vue'
13+
import { ParsedURI } from "../../database/uri.ts"
1314
1415
export interface Image {
15-
name: string
16+
uri: ParsedURI
1617
similarity: number
1718
}
1819
@@ -23,8 +24,8 @@ export default defineComponent({
2324
type: FileSystemDirectoryHandle,
2425
required: true,
2526
},
26-
name: {
27-
type: String,
27+
image: {
28+
type: Object as () => Image,
2829
required: true,
2930
},
3031
class: {
@@ -43,7 +44,7 @@ export default defineComponent({
4344
immediate: true,
4445
async handler() {
4546
try {
46-
const file = await this.baseDir.getFileHandle(this.name)
47+
const file = await this.baseDir.getFileHandle(this.image.uri.name)
4748
this.url = URL.createObjectURL(await file.getFile())
4849
} catch (e: Error) {
4950
if (e.name !== 'NotFoundError') {

src/components/image/ImagePaging.vue

+23-29
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88

99
<v-row dense>
1010
<v-col cols="12" class="image-container" :style="`column-count: ${columnCount};`">
11-
<div v-for="image in displayItems" :key="image.name" @click="onClickImage(image.name)" class="cursor-pointer">
11+
<div v-for="image in displayItems" :key="image.uri.rawURI" @click="onClickImage(image.uri.rawURI)" class="cursor-pointer">
1212
<ImageItem
1313
:base-dir="baseDir"
14-
:name="image.name"
14+
:image="image"
1515
style="margin-bottom: 1em"
16-
:class="selected[image.name] ? 'image-selected border-lg' : ''"
16+
:class="selected[image.uri.rawURI] ? 'image-selected border-lg' : ''"
1717
>
1818
<span v-if="search.showSimilarity" class="text-caption">
1919
{{ image.similarity }}
@@ -29,12 +29,12 @@
2929
</v-container>
3030
</v-app-bar>
3131

32-
<v-app-bar location="bottom" elevation="0" v-if="selectedPaths.length > 0" density="compact">
32+
<v-app-bar location="bottom" elevation="0" v-if="selectedImages.length > 0" density="compact">
3333
<v-container>
3434
<v-row>
35-
<v-col cols="8"></v-col>
36-
<v-col cols="4">
37-
<ImageDeletion :base-dir="baseDir" :file-names="selectedPaths" @onDeleted="onDeleted" />
35+
<v-col cols="6" sm="8"></v-col>
36+
<v-col cols="6" sm="4">
37+
<ImageDeletion :base-dir="baseDir" :images="selectedImages" @onDeleted="onDeleted" />
3838
</v-col>
3939
</v-row>
4040
</v-container>
@@ -49,6 +49,7 @@ import { ImageResult } from '../text/TextEmbedding.vue'
4949
import ImageDeletion from './ImageDeletion.vue'
5050
import { mapState } from "pinia"
5151
import { useSettingsStore } from "../../store/settings.ts"
52+
import { URI } from "../../database/uri.ts"
5253
5354
export default defineComponent({
5455
name: 'ImagePaging',
@@ -70,8 +71,8 @@ export default defineComponent({
7071
data() {
7172
return {
7273
page: 1,
73-
selected: {} as { [key: string]: boolean },
74-
deleted: {} as { [key: string]: boolean },
74+
selected: {} as { [key: URI]: boolean },
75+
deleted: {} as { [key: URI]: boolean },
7576
}
7677
},
7778
computed: {
@@ -95,10 +96,7 @@ export default defineComponent({
9596
return 1
9697
},
9798
view() {
98-
return this.images.filter((i) => {
99-
const name = i.path.substring(this.baseDir.name.length + 1)
100-
return !this.deleted[name]
101-
})
99+
return this.images.filter((i) => !this.deleted[i.uri.rawURI])
102100
},
103101
pageCount() {
104102
return Math.ceil(this.view.length / this.limitToUse)
@@ -114,32 +112,28 @@ export default defineComponent({
114112
115113
const imageItems = [] as Image[]
116114
for (let result of this.view.slice(this.start, this.end)) {
117-
if (result.path.startsWith(`${this.baseDir.name}/`)) {
118-
const image = {
119-
name: result.path.substring(this.baseDir.name.length + 1),
120-
similarity: result.similarity,
121-
}
122-
imageItems.push(image)
115+
if(result.uri.directory === this.baseDir.name) {
116+
imageItems.push(result)
123117
}
124118
}
125119
return imageItems
126120
},
127-
selectedPaths() {
128-
return Object.keys(this.selected)
121+
selectedImages(): Image[] {
122+
return this.images.filter((i) => this.selected[i.uri.rawURI])
129123
},
130124
},
131125
methods: {
132-
onClickImage(name: string) {
133-
if (this.selected[name]) {
134-
delete this.selected[name]
126+
onClickImage(uri: URI) {
127+
if (this.selected[uri]) {
128+
delete this.selected[uri]
135129
} else {
136-
this.selected[name] = true
130+
this.selected[uri] = true
137131
}
138132
},
139-
onDeleted(deleted: string[]) {
140-
for (let path of deleted) {
141-
this.deleted[path] = true
142-
delete this.selected[path]
133+
onDeleted(deleted: Image[]) {
134+
for (let deletedImage of deleted) {
135+
this.deleted[deletedImage.uri.rawURI] = true
136+
delete this.selected[deletedImage.uri.rawURI]
143137
}
144138
},
145139
},

src/components/progress/Bar.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<v-progress-linear v-if="!total || total > 0" v-model="current" :max="total" height="36" color="primary">
2+
<v-progress-linear v-if="total && total > 0" v-model="current" :max="total" height="36" color="primary">
33
<strong>
44
<template v-if="!hideSteps">
55
<span>{{ current }}</span>

src/components/text/TextEmbedding.vue

+3-2
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ import ProgressBar from '../progress/Bar.vue'
2929
import ProgressDialog from '../progress/Dialog.vue'
3030
import { useSettingsStore } from '../../store/settings.ts'
3131
import { delayProgress } from '../progress/delayed.ts'
32+
import { ParsedURI, parseURI } from "../../database/uri.ts"
3233
3334
export interface ImageResult {
34-
path: string
35+
uri: ParsedURI
3536
similarity: number
3637
}
3738
@@ -91,7 +92,7 @@ export default defineComponent({
9192
9293
if (similarity >= this.similarityThresholdToUse) {
9394
candidates.push({
94-
path: entry.path,
95+
uri: parseURI(entry.uri),
9596
similarity,
9697
} as ImageResult)
9798
}

src/components/vision/VisionEmbedding.vue

+13-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { useAiStore } from '../../store/ai.ts'
2323
import ProgressDialog from '../progress/Dialog.vue'
2424
import { delayProgress } from '../progress/delayed.ts'
2525
import { VectorEntryKey } from '../../database/vector.ts'
26+
import { localFileURI, parseURI } from "../../database/uri.ts"
2627
2728
const validImageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.tiff']
2829
@@ -84,7 +85,8 @@ export default defineComponent({
8485
// remove directory name from file name
8586
fileName.substring(this.directory.name.length + 1),
8687
)
87-
const exists = await this.$vectorDB.exists(fileName)
88+
const fileUri = localFileURI(this.directory.name, file.name)
89+
const exists = await this.$vectorDB.exists(fileUri)
8890
8991
if (!exists) {
9092
files.push(file)
@@ -116,8 +118,8 @@ export default defineComponent({
116118
const result = await this.visionModel(imageInputs)
117119
118120
await this.$vectorDB.save({
119-
directory: this.directory.name,
120-
path: `${this.directory.name}/${file.name}`,
121+
collection: this.directory.name,
122+
uri: localFileURI(this.directory.name, file.name),
121123
embedding: result.image_embeds[0].data,
122124
})
123125
@@ -146,7 +148,14 @@ export default defineComponent({
146148
const result = [] as VectorEntryKey[]
147149
const delayedProgression = delayProgress((i) => (this.progress.current = i))
148150
await this.$vectorDB.iterate(this.directory.name, (entry) => {
149-
if (!fileNameMap[entry.path]) {
151+
const parsedURI = parseURI(entry.uri)
152+
if(parsedURI === null || parsedURI.type !== 'localFile') {
153+
//skip non-local files
154+
return true
155+
}
156+
157+
const fileName = parsedURI.directory + "/" + parsedURI.name
158+
if (!fileNameMap[fileName]) {
150159
result.push(entry.id)
151160
}
152161
delayedProgression.add(1)

0 commit comments

Comments
 (0)