diff --git a/src/app/notebook/cellTypes/CodeBlock.ts b/src/app/notebook/cellTypes/CodeBlock.ts index c8508ae..c5890f1 100644 --- a/src/app/notebook/cellTypes/CodeBlock.ts +++ b/src/app/notebook/cellTypes/CodeBlock.ts @@ -30,7 +30,7 @@ export class CodeBlock extends InteractiveBlock { private language: Language; private readonly outputCell: string; private code: string; - private languages = ["javascript", "python", "html", 'sql', 'javascript webworker', "cpp"]; + private languages = [{value: "javascript main thread", name: "javascript"}, {value: "python", name: "python"}, {value: "html", name: "html"}, {value: "sql", name: "sql"}, {value: "javascript", name: "javascript web worker"}, {value: "cpp", name: "cpp"}]; constructor(private editorJsTool: EditorJsTool) { super(editorJsTool); @@ -42,13 +42,13 @@ export class CodeBlock extends InteractiveBlock { private languageFactory(language: string): Language { return match(language) - .with("javascript", () => new JavaScriptMainThread(this.code, this.editorJsTool, this.cell)) + .with("javascript main thread", () => new JavaScriptMainThread(this.code, this.editorJsTool, this.cell)) .with("python", () => new Python(this.code, this.editorJsTool, this.cell)) .with("html", () => new Html(this.code, this.editorJsTool, this.cell)) .with("sql", () => new Sql(this.code, this.editorJsTool, this.cell)) .with("cpp", () => new Cpp(this.code, this.editorJsTool, this.cell)) .with( - "javascript webworker", () => new JavaScript(this.code, this.editorJsTool, this.cell) + "javascript", () => new JavaScript(this.code, this.editorJsTool, this.cell) ) .otherwise( () => new JavaScript(this.code, this.editorJsTool,this.cell) @@ -279,9 +279,9 @@ export class CodeBlock extends InteractiveBlock { languagesSelect.classList.add("small"); for (const language of this.languages) { const option = document.createElement("option"); - option.value = language; - option.innerText = language; - if(language === this.language.name) { + option.value = language.value; + option.innerText = language.name; + if(language.value === this.language.name) { option.selected = true; } languagesSelect.appendChild(option); diff --git a/src/app/notebook/shell/DatabaseManager.ts b/src/app/notebook/shell/DatabaseManager.ts index edf94fe..d341b30 100644 --- a/src/app/notebook/shell/DatabaseManager.ts +++ b/src/app/notebook/shell/DatabaseManager.ts @@ -1,4 +1,14 @@ -import {BehaviorSubject, filter, firstValueFrom, map, Observable, shareReplay, Subscriber, switchMap} from "rxjs"; +import { + BehaviorSubject, + filter, + first, + firstValueFrom, + map, + Observable, + shareReplay, + Subscriber, + switchMap +} from "rxjs"; import {OutputData} from "@editorjs/editorjs"; import {addRxPlugin, createRxDatabase, RxCollection, RxDatabaseBase, RxDocument, RxDumpDatabaseAny} from 'rxdb'; import {getRxStorageDexie} from 'rxdb/plugins/storage-dexie'; @@ -16,6 +26,7 @@ import {enforceOptions} from "broadcast-channel"; import {randomCouchString} from "rxdb/plugins/utils"; import {DocumentObserver} from "./documentObserver"; import {Injectable} from "@angular/core"; +import {EditorJS} from "./editorJS"; addRxPlugin(RxDBLeaderElectionPlugin); @@ -206,7 +217,9 @@ export class DatabaseManager { this.database$.next(this._database); // @ts-ignore globalThis.environment = DocumentObserver.setup(this.database$); - collections.blocks.postInsert(async (plainData, rxDocument) =>{ + // @ts-ignore + globalThis.editor = new EditorJS({topic: this.topic, peer: this._uuid, db: this.database$}); + collections.blocks.postInsert(async (plainData, rxDocument) => { await this.updateHistory(); return this.increaseIndexes(plainData.index); }, false); @@ -337,6 +350,11 @@ export class DatabaseManager { return this._database?.exportJSON(); } + async exportCurrentNotebook() { + // @ts-ignore + return + } + importDatabase(json: RxDumpDatabaseAny) { return this._database?.importJSON(json); } @@ -499,7 +517,7 @@ export class DatabaseManager { // @ts-ignore return block?.updateCRDT({ selector: { - index: { $ne: index } + index: {$ne: index} }, ifMatch: { $set: { diff --git a/src/app/notebook/shell/editorJS.ts b/src/app/notebook/shell/editorJS.ts new file mode 100644 index 0000000..10a0218 --- /dev/null +++ b/src/app/notebook/shell/editorJS.ts @@ -0,0 +1,159 @@ +import {firstValueFrom, map, Observable, switchMap} from "rxjs"; +import {RxDatabase} from "rxdb/dist/types/types"; +import {OutputData} from "@editorjs/editorjs"; +import {BlockAPI} from "@editorjs/editorjs/types/api/block"; +import {BlockToolData, ToolConfig} from "@editorjs/editorjs/types/tools"; +import {randomCouchString} from "rxdb"; + +interface Environment { + db: Observable; + topic?: string; + peer?: string; +} + +export class EditorJS { + public static version: "2.26.5"; + public readonly blocks = new Blocks(this.environment); + + constructor(private environment: Environment) { + } + + get isReady(): Promise { + return new Promise((resolve) => resolve(true)); + } + + save(): Promise { + return firstValueFrom(this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].find({ + selector: { + topic: { + $eq: this.environment.topic ?? "" + } + } + }) + .exec()), + map(blocks => { + return blocks.map(doc => { + delete doc._data.crdts + delete doc._data._deleted + delete doc._data._deleted + delete doc._data._attachments + delete doc._data._rev + delete doc._data._meta + return doc._data + }) + }), + map(blocks => ({ + version: EditorJS.version, blocks + })))); + } +} + +class Blocks { + constructor(private environment: Environment) { + } + + get get$() { + return this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].find({ + selector: { + topic: { + $eq: this.environment.topic ?? "" + } + }, + sort: [{index: 'asc'}] + }).$)); + } + + getById(id: string): Observable { + return this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].findOne(id).$)); + } + + getByIndex(index: number): Observable { + return this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].findOne({ + selector: { + index: { + $eq: index + }, + topic: { + $eq: this.environment.topic ?? "" + } + } + }).$)); + } + + insert(type?: string, data?: BlockToolData, config?: ToolConfig, index?: number, needToFocus?: boolean, replace?: boolean, id?: string) { + return this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].insertCRDT({ + ifMatch: { + $set: { + type, + data, + config, + index, + createdBy: (this.environment.peer ?? "") + "worker", + updatedBy: (this.environment.peer ?? "") + "worker", + id: id ?? randomCouchString(7), + topic: this.environment.topic ?? "" + } + } + }))); + }; + + upsert(id?: string, data?: BlockToolData, type?: string, index?: number, config?: ToolConfig, needToFocus?: boolean, replace?: boolean) { + return this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].insertCRDT({ + selector: { + id: {$exists: true} + }, + ifMatch: { + $set: { + id, + data, + updatedBy: (this.environment.peer ?? "") + "worker" + } + }, + ifNotMatch: { + $set: { + type, + data, + config, + index, + createdBy: (this.environment.topic ?? "") + "worker", + updatedBy: (this.environment.peer ?? "") + "worker", + id: id ?? randomCouchString(7), + topic: this.environment.topic ?? "" + } + } + }))); + } + + update(id: string, data: BlockToolData) { + return this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].insertCRDT({ + selector: { + id: {$exists: true} + }, + ifMatch: { + $set: { + id, + data, + updatedBy: (this.environment.peer ?? "") + "worker" + } + } + }))); + } + + delete(id: string) { + return this.environment.db.pipe( + switchMap((db: RxDatabase) => db["blocks"].findOne(id).exec()), + switchMap(document => document?.updateCRDT({ + selector: { + id: {$exists: true} + }, + ifMatch: { + $set: { + id, + updatedBy: (this.environment.peer ?? "") + "worker", + _deleted: true + } + } + })) + ); + } +} diff --git a/src/app/notebook/shell/process.worker.ts b/src/app/notebook/shell/process.worker.ts index 0db6090..f762f34 100644 --- a/src/app/notebook/shell/process.worker.ts +++ b/src/app/notebook/shell/process.worker.ts @@ -47,8 +47,17 @@ import {fromFetch} from "rxjs/fetch"; import {isMatching, match, P, Pattern} from 'ts-pattern'; import * as protocols from './protocols'; import * as _ from 'lodash'; -import { OpenAI } from "langchain/llms/openai"; -import {MessagesPlaceholder, PromptTemplate} from "langchain/prompts"; +import {OpenAI} from "langchain/llms/openai"; +import { + ChatPromptTemplate, + FewShotPromptTemplate, + HumanMessagePromptTemplate, + LengthBasedExampleSelector, + MessagesPlaceholder, + PromptTemplate, + SemanticSimilarityExampleSelector, + SystemMessagePromptTemplate +} from "langchain/prompts"; import {ComputeEngine} from "@cortex-js/compute-engine"; import {addRxPlugin, createRxDatabase} from "rxdb"; import {getRxStorageDexie} from "rxdb/plugins/storage-dexie"; @@ -56,58 +65,44 @@ import {getCRDTSchemaPart, RxDBcrdtPlugin} from "rxdb/plugins/crdt"; import {enforceOptions} from 'broadcast-channel'; import {RxDBLeaderElectionPlugin} from 'rxdb/plugins/leader-election'; import * as duckdb from '@duckdb/duckdb-wasm'; -import ajv from 'ajv'; import {AsyncDuckDB, AsyncDuckDBConnection} from '@duckdb/duckdb-wasm'; +import ajv from 'ajv'; import * as arrow from "apache-arrow"; -import {RxDatabase} from "rxdb/dist/types/types"; -import {OutputData} from "@editorjs/editorjs"; -import { JsonSpec } from "langchain/tools"; -import { JsonToolkit, createJsonAgent } from "langchain/agents"; -import {AgentExecutor, ChatAgent, initializeAgentExecutorWithOptions} from "langchain/agents"; +import {JsonSpec, SerpAPI, ZapierNLAWrapper} from "langchain/tools"; +import { + AgentExecutor, + ChatAgent, + createJsonAgent, + initializeAgentExecutorWithOptions, + JsonToolkit, + ZapierToolKit +} from "langchain/agents"; import { - StructuredOutputParser, - OutputFixingParser, CombiningOutputParser, CommaSeparatedListOutputParser, - RegexParser + OutputFixingParser, + RegexParser, + StructuredOutputParser } from "langchain/output_parsers"; -import { SerpAPI } from "langchain/tools"; -import { - SystemMessagePromptTemplate, - HumanMessagePromptTemplate, - ChatPromptTemplate, - LengthBasedExampleSelector, - FewShotPromptTemplate, - SemanticSimilarityExampleSelector -} from "langchain/prompts"; -import { Calculator } from "langchain/tools/calculator"; -import { ChatOpenAI } from "langchain/chat_models/openai"; -import { HumanChatMessage, SystemChatMessage, AIChatMessage } from "langchain/schema"; -import {BlockAPI} from "@editorjs/editorjs/types/api/block"; -import { BufferMemory } from "langchain/memory"; -import { MemoryVectorStore } from "langchain/vectorstores/memory"; -import { OpenAIEmbeddings } from "langchain/embeddings/openai"; -import { ZapierNLAWrapper } from "langchain/tools"; -import { - ZapierToolKit, -} from "langchain/agents"; +import {Calculator} from "langchain/tools/calculator"; +import {ChatOpenAI} from "langchain/chat_models/openai"; +import {AIChatMessage, HumanChatMessage, SystemChatMessage} from "langchain/schema"; +import {BufferMemory} from "langchain/memory"; +import {MemoryVectorStore} from "langchain/vectorstores/memory"; +import {OpenAIEmbeddings} from "langchain/embeddings/openai"; import {ConversationChain, LLMChain} from "langchain/chains"; -import {BlockToolData, ToolConfig} from "@editorjs/editorjs/types/tools"; -import {randomCouchString} from "rxdb"; -import {precompileJS} from "./precompile"; import {DocumentObserver} from "./documentObserver"; import SWIPL, {Query, SWIPLModule} from "swipl-wasm"; -import { create as createIPFSHttpClient } from 'ipfs-http-client'; -import { create as createIPFSCoreClient } from 'ipfs-core'; -import { createHelia } from 'helia'; +import {create as createIPFSHttpClient} from 'ipfs-http-client'; +import {create as createIPFSCoreClient} from 'ipfs-core'; +import {createHelia} from 'helia'; // @ts-ignore import workerpool from 'workerpool'; // @ts-ignore import OrbitDB from 'orbit-db'; - - +import {exec} from './exec'; +import {EditorJS} from "./editorJS"; import Indexed = Immutable.Seq.Indexed; -import { exec } from './exec'; addRxPlugin(RxDBLeaderElectionPlugin); addRxPlugin(RxDBcrdtPlugin); @@ -505,116 +500,6 @@ async function buildTree(transformer: MatTreeTransformer) { return new MatTree(payload, transformer); } -class Blocks { - constructor(private environment: { db: Observable, currentUrl: URL }) { - } - - get get$() { - return this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].find({ - selector: { - topic: { - $eq: this.environment.currentUrl.searchParams.get("t") ?? "" - } - }, - sort: [{index: 'asc'}] - }).$)); - } - - getById(id: string): Observable { - return this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].findOne(id).$)); - } - - getByIndex(index: number): Observable { - return this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].findOne({ - selector: { - index: { - $eq: index - }, - topic: { - $eq: this.environment.currentUrl.searchParams.get("t") ?? "" - } - } - }).$)); - } - - insert(type?: string, data?: BlockToolData, config?: ToolConfig, index?: number, needToFocus?: boolean, replace?: boolean, id?: string) { - return this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].insertCRDT({ - ifMatch: { - $set: { - type, - data, - config, - index, - createdBy: (this.environment.currentUrl.searchParams.get("p") ?? "") + "worker", - updatedBy: (this.environment.currentUrl.searchParams.get("p") ?? "") + "worker", - id: id ?? randomCouchString(7), - topic: this.environment.currentUrl.searchParams.get("t") ?? "" - } - } - }))); - }; - - upsert(id?: string, data?: BlockToolData, type?: string, index?: number, config?: ToolConfig, needToFocus?: boolean, replace?: boolean) { - return this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].insertCRDT({ - selector: { - id: {$exists: true} - }, - ifMatch: { - $set: { - id, - data, - updatedBy: (this.environment.currentUrl.searchParams.get("p") ?? "") + "worker" - } - }, - ifNotMatch: { - $set: { - type, - data, - config, - index, - createdBy: (this.environment.currentUrl.searchParams.get("p") ?? "") + "worker", - updatedBy: (this.environment.currentUrl.searchParams.get("p") ?? "") + "worker", - id: id ?? randomCouchString(7), - topic: this.environment.currentUrl.searchParams.get("t") ?? "" - } - } - }))); - } - - update(id: string, data: BlockToolData) { - return this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].insertCRDT({ - selector: { - id: {$exists: true} - }, - ifMatch: { - $set: { - id, - data, - updatedBy: (this.environment.currentUrl.searchParams.get("p") ?? "") + "worker" - } - } - }))); - } - - delete(id: string) { - return this.environment.db.pipe( - switchMap((db: RxDatabase) => db["blocks"].findOne(id).exec()), - switchMap(document => document?.updateCRDT({ - selector: { - id: {$exists: true} - }, - ifMatch: { - $set: { - id, - updatedBy: (this.environment.currentUrl.searchParams.get("p") ?? "") + "worker", - _deleted: true - } - } - })) - ); - } -} - class HostOperatingSystem { constructor(private environment: any) {} exec(command: string) { @@ -661,31 +546,6 @@ class HostFilesystem { } } -class EditorJS { - public static version: "2.26.5"; - public readonly blocks = new Blocks(this.environment); - - constructor(private environment: { db: Observable, currentUrl: URL }) { - } - - get isReady(): Promise { - return new Promise((resolve) => resolve(true)); - } - - save(): Promise { - return firstValueFrom(this.environment.db.pipe(switchMap((db: RxDatabase) => db["blocks"].find({ - selector: { - topic: { - $eq: this.environment.currentUrl.searchParams.get("t") ?? "" - } - } - }) - .exec()), map(blocks => ({ - version: EditorJS.version, blocks - })))); - } -} - interface GitHubCommit { owner: string; repo: string; @@ -1273,6 +1133,8 @@ class ProcessWorker { href: payload.href }; this.environment.currentUrl = new URL(payload.href); + this.environment.topic = this.environment.currentUrl.searchParams.get("t"); + this.environment.peer = this.environment.currentUrl.searchParams.get("p"); return this.environmentObserver.init(); } } diff --git a/src/app/notebook/shell/shell.ts b/src/app/notebook/shell/shell.ts index 1203bcb..ba7896c 100644 --- a/src/app/notebook/shell/shell.ts +++ b/src/app/notebook/shell/shell.ts @@ -16,7 +16,7 @@ enum JobStatus { function downloadFile(blobParts?: any, options?: any) { let blob = new Blob(blobParts, options); - downloadBlob(blob); + downloadBlob(blob, options); } function downloadBlob(blob: Blob, options?: any) { @@ -71,8 +71,9 @@ export class Shell { this.databaseManager.removeAllBlocks().then(); }); environment.addEventListener('shell.ExportNotebook', (event: CustomEvent) => { - this.databaseManager.exportDatabase()?.then((data) => { - downloadFile([JSON.stringify(data)], {type: 'application/json', filename: 'database.json'}); + // @ts-ignore + globalThis.editor.save()?.then((data) => { + downloadFile([JSON.stringify(data)], {type: 'application/json', filename: `${url.read('n', 'EvaNotebook')}.json`}); event.detail?.port?.postMessage({ event: event.detail?.payload?.event, payload: data });