Skip to content

Commit

Permalink
feat(kb): Rename and delete KB
Browse files Browse the repository at this point in the history
  • Loading branch information
RezaRahemtola committed Aug 7, 2024
1 parent f84a895 commit cb2ef64
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 848 deletions.
849 changes: 40 additions & 809 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@aleph-sdk/client": "^1.0.6",
"@aleph-sdk/ethereum": "^1.0.3",
"@aleph-sdk/message": "^1.0.7",
"@libertai/libertai-js": "0.0.9",
"@libertai/libertai-js": "file:../libertai-js/libertai-libertai-js-0.0.10.tgz",
"@quasar/extras": "^1.16.12",
"@tanstack/vue-query": "^5.51.21",
"@wagmi/vue": "^0.0.34",
Expand Down
27 changes: 27 additions & 0 deletions src/components/dialog/KnowledgeBaseRenameDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<template>
<ltai-dialog title="Rename knowledge base" @save="emit('save', newName)">
<q-card-section horizontal>
<q-card-section>
<span>Name</span>
<q-input v-model="newName" bg-color="secondary" input-class="text-light q-px-sm" outlined></q-input>
</q-card-section>
</q-card-section>
</ltai-dialog>
</template>
<script lang="ts" setup>
import LtaiDialog from 'components/libertai/LtaiDialog.vue';
import { ref, watch } from 'vue';
const props = defineProps<{ name: string }>();
const emit = defineEmits<{ save: [name: string] }>();
// Form values
const newName = ref('');
watch(
() => props.name,
() => (newName.value = props.name),
{ immediate: true },
);
</script>
26 changes: 12 additions & 14 deletions src/pages/Chat.vue
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ import dayjs from 'dayjs';
import LtaiIcon from 'components/libertai/LtaiIcon.vue';
import { searchDocuments } from 'src/utils/knowledge/embedding';
import { useKnowledgeStore } from 'stores/knowledge';
import { KnowledgeSearchResult } from 'src/types/knowledge';
const $q = useQuasar();
const route = useRoute();
Expand Down Expand Up @@ -215,24 +216,17 @@ async function generatePersonaMessage() {
// Set loading state
isLoadingRef.value = true;
let searchResultMessages: Message[] = [];
let knowledgeSearchResults: KnowledgeSearchResult[] = [];
// Finding related knowledge document chunks
if (knowledgeBaseIds.length > 0) {
const documents = knowledgeStore.getDocumentsFrom(knowledgeBaseIds);
const lastUserMessage = messages.findLast((message) => message.author === 'user')!;
const searchResults = await searchDocuments(lastUserMessage.content, documents);
console.log(searchResults);
searchResultMessages = searchResults.map(
(result): Message => ({
role: 'search-result',
content: result.content,
}),
);
knowledgeSearchResults = await searchDocuments(lastUserMessage.content, documents);
}
// Expand all the messages to inline any compatible attachments
const expandedMessages = messages
const allMessages = messages
.map((message: UIMessage): Message[] => {
const ret = [];
// Push any attachments as messages ahead of the message itself
Expand All @@ -249,11 +243,15 @@ async function generatePersonaMessage() {
})
.flat();
// Append the search results to the messages
const allMessages: Message[] = [...expandedMessages, ...searchResultMessages];
// Generate a stream of responses from the AI
for await (const output of inferenceEngine.generateAnswer(allMessages, model, persona, username, false)) {
for await (const output of inferenceEngine.generateAnswer(
allMessages,
model,
persona,
knowledgeSearchResults.map((result) => result.content),
username,
false,
)) {
const stopped = output.stopped;
let content = output.content;
if (!stopped) {
Expand Down
103 changes: 90 additions & 13 deletions src/pages/KnowledgeBase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,31 @@
<ltai-icon name="svguse:icons.svg#arrow-left" />
</q-btn>
<div>
<h4 class="text-h4 text-semibold">{{ knowledgeBaseRef.name }}</h4>
<div class="tw-flex tw-space-x-4">
<h4 class="text-h4 text-semibold">{{ knowledgeBaseRef.name }}</h4>

<q-btn-dropdown class="tw-p-1" dropdown-icon="more_horiz" unelevated>
<q-list>
<q-item v-close-popup clickable @click="showRenameKnowledgeBase = true">
<q-item-section avatar>
<ltai-icon class="tw-mx-auto" name="svguse:icons.svg#pencil" />
</q-item-section>
<q-item-section>
<q-item-label>Rename</q-item-label>
</q-item-section>
</q-item>
<q-item v-close-popup clickable @click="showDeleteKnowledgeBaseConfirmation = true">
<q-item-section avatar>
<ltai-icon class="tw-mx-auto" name="svguse:icons.svg#delete" />
</q-item-section>
<q-item-section>
<q-item-label>Delete</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>

<div class="tw-mt-4 tw-flex md:tw-justify-end">
<q-btn
class="border-primary-highlight"
Expand Down Expand Up @@ -81,7 +105,8 @@
</q-btn-dropdown>
</div>
</div>
<!-- Dialogs-->

<!-- Document dialogs-->
<knowledge-base-rename-document-dialog
v-model="showRenameDocument"
:name="selectedDocument?.name ?? ''"
Expand All @@ -93,7 +118,23 @@
@save="deleteDocument(selectedDocument!)"
>
<q-card-section class="row">
<span>Are you sure you want to delete the the document {{ selectedDocument!.name }}?</span>
<span>Are you sure you want to delete the document {{ selectedDocument!.name }}?</span>
</q-card-section>
</ltai-dialog>

<!-- Knowledge base dialogs -->
<knowledge-base-rename-dialog
v-model="showRenameKnowledgeBase"
:name="knowledgeBaseRef.name"
@save="(newName: string) => renameKnowledgeBase(newName)"
/>
<ltai-dialog
v-model="showDeleteKnowledgeBaseConfirmation"
title="Delete knowledge base"
@save="deleteKnowledgeBase"
>
<q-card-section class="row">
<span>Are you sure you want to delete the knowledge base "{{ knowledgeBaseRef.name }}" ?</span>
</q-card-section>
</ltai-dialog>
</div>
Expand All @@ -113,6 +154,7 @@ import LtaiDialog from 'components/libertai/LtaiDialog.vue';
import KnowledgeBaseRenameDocumentDialog from 'components/dialog/KnowledgeBaseRenameDocumentDialog.vue';
import { processDocument } from 'src/utils/knowledge/document';
import { decryptFile, encryptFile } from 'src/utils/encryption';
import KnowledgeBaseRenameDialog from 'components/dialog/KnowledgeBaseRenameDialog.vue';
const $q = useQuasar();
const route = useRoute();
Expand All @@ -125,6 +167,8 @@ const knowledgeStore = useKnowledgeStore();
const knowledgeBaseRef = ref<KnowledgeBase | undefined>(undefined);
const knowledgeBaseIdentifierRef = ref<KnowledgeBaseIdentifier | undefined>(undefined);
const selectedDocument = ref<KnowledgeDocument | undefined>(undefined);
const showRenameKnowledgeBase = ref(false);
const showDeleteKnowledgeBaseConfirmation = ref(false);
const showRenameDocument = ref(false);
const showDeleteDocumentConfirmation = ref(false);
Expand Down Expand Up @@ -228,11 +272,7 @@ const downloadDocument = async (document: KnowledgeDocument) => {
};
const renameDocument = async (document: KnowledgeDocument, newName: string) => {
if (
knowledgeBaseRef.value === undefined ||
knowledgeBaseIdentifierRef.value === undefined ||
accountStore.alephStorage === null
) {
if (knowledgeBaseRef.value === undefined || knowledgeBaseIdentifierRef.value === undefined) {
return;
}
Expand All @@ -256,6 +296,23 @@ const renameDocument = async (document: KnowledgeDocument, newName: string) => {
};
const deleteDocument = async (document: KnowledgeDocument) => {
if (knowledgeBaseRef.value === undefined || knowledgeBaseIdentifierRef.value === undefined) {
return;
}
try {
await knowledgeStore.deleteKnowledgeDocument(document, knowledgeBaseRef.value, knowledgeBaseIdentifierRef.value);
knowledgeBaseRef.value.documents = knowledgeBaseRef.value.documents.filter((d) => d.id !== document.id);
} catch (error) {
$q.notify({
message: (error as Error)?.message ?? 'Document deletion failed, please try again',
color: 'negative',
});
}
};
const renameKnowledgeBase = async (newName: string) => {
if (
knowledgeBaseRef.value === undefined ||
knowledgeBaseIdentifierRef.value === undefined ||
Expand All @@ -265,18 +322,38 @@ const deleteDocument = async (document: KnowledgeDocument) => {
}
try {
await accountStore.alephStorage.deleteFile(document.store.item_hash);
knowledgeBaseRef.value.documents = knowledgeBaseRef.value.documents.filter((d) => d.id !== document.id);
await knowledgeStore.updateKnowledgeBase(
JSON.parse(JSON.stringify(knowledgeBaseRef.value)),
JSON.parse(JSON.stringify({ ...knowledgeBaseRef.value, name: newName })),
knowledgeBaseIdentifierRef.value,
);
knowledgeBaseRef.value.name = newName;
} catch (error) {
$q.notify({
message: (error as Error)?.message ?? 'Document deletion failed, please try again',
message: (error as Error)?.message ?? 'Knowledge base renaming failed, please try again',
color: 'negative',
});
}
};
const deleteKnowledgeBase = async () => {
if (
knowledgeBaseRef.value === undefined ||
knowledgeBaseIdentifierRef.value === undefined ||
accountStore.alephStorage === null
) {
return;
}
const success = await knowledgeStore.deleteKnowledgeBase(knowledgeBaseRef.value, knowledgeBaseIdentifierRef.value);
if (!success) {
$q.notify({
message: 'Knowledge base deletion failed, please try again',
color: 'negative',
});
return;
}
await router.push({ path: '/knowledge-base' });
};
</script>
36 changes: 36 additions & 0 deletions src/stores/knowledge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,41 @@ export const useKnowledgeStore = defineStore('knowledge', {
return knowledgeBase;
});
},

async deleteKnowledgeDocument(
document: KnowledgeDocument,
knowledgeBase: KnowledgeBase,
kbIdentifier: KnowledgeBaseIdentifier,
updateKnowledgeBase: boolean = true,
) {
const { alephStorage } = useAccountStore();
if (alephStorage === null) {
return;
}

await alephStorage.deleteFile(document.store.item_hash);
if (updateKnowledgeBase) {
await this.updateKnowledgeBase(knowledgeBase, kbIdentifier);
}
},

async deleteKnowledgeBase(knowledgeBase: KnowledgeBase, kbIdentifier: KnowledgeBaseIdentifier): Promise<boolean> {
const { alephStorage } = useAccountStore();
if (alephStorage === null) {
return false;
}
knowledgeBase.documents.forEach((document) => {
this.deleteKnowledgeDocument(document, knowledgeBase, kbIdentifier, false);
});

const success = await alephStorage.deleteKnowledgeBase(kbIdentifier, this.knowledgeBaseIdentifiers);

if (!success) {
return success;
}
this.knowledgeBases = this.knowledgeBases.filter((kb) => kb.id !== knowledgeBase.id);
this.knowledgeBaseIdentifiers = this.knowledgeBaseIdentifiers.filter((kbi) => kbi.id !== kbIdentifier.id);
return success;
},
},
});
38 changes: 38 additions & 0 deletions src/utils/aleph-persistent-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,4 +279,42 @@ export class AlephPersistentStorage {
return undefined;
}
}

async deleteKnowledgeBase(
kbIdentifier: KnowledgeBaseIdentifier,
currentKbIdentifiers: KnowledgeBaseIdentifier[],
): Promise<boolean> {
try {
await this.subAccountClient.forget({
hashes: [kbIdentifier.post_hash],
});

const newKbIdentifiers: KnowledgeBaseIdentifier[] = [
...currentKbIdentifiers.filter((kbi) => kbi.id !== kbIdentifier.id),
].map((kbi) => ({
...kbi,
encryption: {
key: eciesEncrypt(this.encryptionPrivateKey.publicKey.toHex(), Buffer.from(kbi.encryption.key)).toString(
BUFFER_ENCODING,
),
iv: eciesEncrypt(this.encryptionPrivateKey.publicKey.toHex(), Buffer.from(kbi.encryption.iv)).toString(
BUFFER_ENCODING,
),
},
}));

await this.subAccountClient.createAggregate({
key: LIBERTAI_KNOWLEDGE_BASE_IDENTIFIERS_KEY,
content: {
data: newKbIdentifiers,
},
address: this.account.address,
channel: LIBERTAI_CHANNEL,
});
return true;
} catch (error) {
console.error(`Deleting knowledge base failed: ${error}`);
return false;
}
}
}
2 changes: 1 addition & 1 deletion src/utils/knowledge/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { generateChunks } from 'src/utils/knowledge/embedding';
export const processDocument = async (file: File): Promise<Omit<KnowledgeDocument, 'store'>> => {
const fileInfo = await extractFileContent(file);

const chunks = await generateChunks(fileInfo.content);
const chunks = await generateChunks(fileInfo.type, fileInfo.content);

return { ...fileInfo, id: uuidv4(), name: file.name, size: file.size, chunks };
};
Loading

0 comments on commit cb2ef64

Please sign in to comment.